/* $NetBSD$ */ /*- * Copyright (c) 2008 Stephen Borrill * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * TODO: support more cameras with similar sensors and bridges (e.g. ov518) */ #include __KERNEL_RCSID(0, "$NetBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ov51x.h" #define PRI_OV51X PRI_BIO #define OV51X_NXFERS 1 #define OV51X_FRAMES 4 #define OV51X_DEBUG 1 #ifdef OV51X_DEBUG #define DPRINTF(x) do { if (ov51xdebug) logprintf x; } while (0) #define DPRINTFN(n,x) do { if (ov51xdebug>(n)) logprintf x; } while (0) int ov51xdebug = 20; #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif /* Sensor types */ enum { OVBRIDGE_511, OVBRIDGE_511PLUS } ovbridge; enum { OVSENSOR_7610, OVSENSOR_7620, OVSENSOR_7620AE } ovsensor; struct ov51x_softc; struct ov51x_isoc_xfer { struct ov51x_softc *ix_sc; usbd_xfer_handle ix_xfer; uint8_t *ix_buf; uint16_t *ix_frlengths; int ix_busy; }; struct ov51x_softc { USBBASEDEVICE sc_dev; usbd_device_handle sc_udev; usbd_interface_handle sc_iface; char *sc_devname; device_t sc_videodev; int sc_alton; int sc_altoff; int sc_bufsize; int sc_status; usbd_pipe_handle sc_isoc_pipe; uint32_t sc_isoc_nframes; uint32_t sc_isoc_uframe_len; int sc_isoc_buflen; int sc_isoc; struct ov51x_isoc_xfer sc_ix[OV51X_NXFERS]; uint8_t sc_model; uint8_t sc_sensor; uint8_t sc_bridge; char sc_dying; }; static int ov51x_match(device_t, cfdata_t, void *); static void ov51x_attach(device_t, device_t, void *); static int ov51x_detach(device_t, int); static void ov51x_childdet(device_t, device_t); static int ov51x_activate(device_t, enum devact); static void ov51x_init(struct ov51x_softc *); static void ov51x_stop(struct ov51x_softc *); static void ov51x_start(struct ov51x_softc *); static void ov51x_led(struct ov51x_softc *, bool); static uint8_t ov51x_getreg(struct ov51x_softc *, uint16_t); static void ov51x_setreg(struct ov51x_softc *, uint16_t, uint8_t); static uint8_t ov51x_i2c_getreg(struct ov51x_softc *, uint16_t); static void ov51x_i2c_setreg(struct ov51x_softc *, uint16_t, uint8_t); static int ov51x_init_pipes(struct ov51x_softc *); static int ov51x_close_pipes(struct ov51x_softc *); static int ov51x_isoc_start(struct ov51x_softc *, struct ov51x_isoc_xfer *); /* video(9) API */ static int ov51x_open(void *, int); static void ov51x_close(void *); static const char * ov51x_get_devname(void *); static int ov51x_enum_format(void *, uint32_t, struct video_format *); static int ov51x_get_format(void *, struct video_format *); static int ov51x_set_format(void *, struct video_format *); static int ov51x_try_format(void *, struct video_format *); static int ov51x_start_transfer(void *); static int ov51x_stop_transfer(void *); CFATTACH_DECL2_NEW(ov51x, sizeof(struct ov51x_softc), ov51x_match, ov51x_attach, ov51x_detach, ov51x_activate, NULL, ov51x_childdet); static const struct video_hw_if ov51x_hw_if = { .open = ov51x_open, .close = ov51x_close, .get_devname = ov51x_get_devname, .enum_format = ov51x_enum_format, .get_format = ov51x_get_format, .set_format = ov51x_set_format, .try_format = ov51x_try_format, .start_transfer = ov51x_start_transfer, .stop_transfer = ov51x_stop_transfer, }; USB_MATCH(ov51x) { USB_IFMATCH_START(ov51x, uaa); if (uaa->class != UICLASS_VENDOR) return UMATCH_NONE; if (uaa->vendor == USB_VENDOR_OMNIVISION) { switch (uaa->product) { case USB_PRODUCT_OMNIVISION_OV511: case USB_PRODUCT_OMNIVISION_OV511PLUS: if (uaa->ifaceno != 0) return UMATCH_NONE; return UMATCH_VENDOR_PRODUCT; } } return UMATCH_NONE; } USB_ATTACH(ov51x) { USB_IFATTACH_START(ov51x, sc, uaa); usbd_device_handle dev = uaa->device; usb_endpoint_descriptor_t *ed = NULL, *ed_isoc = NULL; usbd_status err; uint8_t neps; int i; USB_ATTACH_SETUP; sc->sc_devname = usbd_devinfo_alloc(dev, 0); aprint_naive("\n"); sc->sc_dev = self; sc->sc_udev = dev; sc->sc_iface = uaa->iface; sc->sc_dying = 0; switch (uaa->vendor) { case USB_VENDOR_OMNIVISION: switch (uaa->product) { case USB_PRODUCT_OMNIVISION_OV511: aprint_normal_dev(sc->sc_dev, "Omnivision OV511\n"); sc->sc_bridge = OVBRIDGE_511; sc->sc_bufsize = 993; sc->sc_alton = OV511_ALT_SIZE_993; sc->sc_altoff = OV511_ALT_SIZE_0; break; case USB_PRODUCT_OMNIVISION_OV511PLUS: aprint_normal_dev(sc->sc_dev, "Omnivision OV511+\n"); sc->sc_bridge = OVBRIDGE_511PLUS; sc->sc_bufsize = 961; sc->sc_alton = OV511PLUS_ALT_SIZE_961; sc->sc_altoff = OV511PLUS_ALT_SIZE_0; break; } } DPRINTF(("size: %d alton:%d altoff:%d\n", sc->sc_bufsize, sc->sc_alton, sc->sc_altoff)); /* Select alternate with packet size zero to probe for endpoints */ DPRINTF(("Set interface %d\n", sc->sc_altoff)); err = usbd_set_interface(sc->sc_iface, sc->sc_altoff); if (err) aprint_error_dev(sc->sc_dev, "couldn't set interface: %s\n", usbd_errstr(err)); /* search for isoc endpoint */ neps = 0; usbd_endpoint_count(sc->sc_iface, &neps); /* OV511/511+ only have one endpoint anyway */ DPRINTF(("Found %d endpoints\n", neps)); for (i = 0; i < neps; i++) { ed = usbd_interface2endpoint_descriptor(sc->sc_iface, i); if (ed == NULL) { aprint_error_dev(sc->sc_dev, "couldn't get ep %d\n", i); sc->sc_dying = 1; USB_ATTACH_ERROR_RETURN; } if (UE_GET_DIR(ed->bEndpointAddress) != UE_DIR_IN || UE_GET_XFERTYPE(ed->bmAttributes) != UE_ISOCHRONOUS) continue; /* found a suitable isoc endpoint */ ed_isoc = ed; break; } if (ed_isoc == NULL) { aprint_error_dev(sc->sc_dev, "couldn't find isoc endpoint\n"); sc->sc_dying = 1; USB_ATTACH_ERROR_RETURN; } ed = ed_isoc; sc->sc_isoc = ed->bEndpointAddress; aprint_normal_dev(sc->sc_dev, "using endpoint address 0x%02x\n", sc->sc_isoc); sc->sc_isoc_nframes = OV51X_FRAMES; sc->sc_isoc_uframe_len = sc->sc_bufsize; for (i = 0; i < OV51X_NXFERS; i++) { sc->sc_ix[i].ix_sc = sc; sc->sc_ix[i].ix_busy = 0; sc->sc_ix[i].ix_frlengths = kmem_alloc( sizeof(sc->sc_ix[i].ix_frlengths[0]) * sc->sc_isoc_nframes, KM_SLEEP); if (sc->sc_ix[i].ix_frlengths == NULL) { aprint_error_dev(sc->sc_dev, "couldn't allocate frlengths\n"); sc->sc_dying = 1; USB_ATTACH_ERROR_RETURN; } } sc->sc_isoc_buflen = sc->sc_isoc_nframes * sc->sc_isoc_uframe_len; sc->sc_videodev = video_attach_mi(&ov51x_hw_if, sc->sc_dev); if (sc->sc_videodev == NULL) { aprint_error_dev(sc->sc_dev, "couldn't attach video layer\n"); sc->sc_dying = 1; USB_ATTACH_ERROR_RETURN; } usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, USBDEV(sc->sc_dev)); USB_ATTACH_SUCCESS_RETURN; } USB_DETACH(ov51x) { USB_DETACH_START(ov51x, sc); int i; sc->sc_dying = 1; if (sc->sc_videodev != NULL) { config_detach(sc->sc_videodev, flags); sc->sc_videodev = NULL; } for (i = 0; i < OV51X_NXFERS; i++) if (sc->sc_ix[i].ix_xfer != NULL) { usbd_free_xfer(sc->sc_ix[i].ix_xfer); sc->sc_ix[i].ix_xfer = NULL; } if (sc->sc_isoc_pipe != NULL) { usbd_abort_pipe(sc->sc_isoc_pipe); usbd_close_pipe(sc->sc_isoc_pipe); sc->sc_isoc_pipe = NULL; } for (i = 0; i < OV51X_NXFERS; i++) if (sc->sc_ix[i].ix_frlengths != NULL) { kmem_free(sc->sc_ix[i].ix_frlengths, sizeof(sc->sc_ix[i].ix_frlengths[0] * sc->sc_isoc_nframes)); sc->sc_ix[i].ix_frlengths = NULL; } if (sc->sc_devname) { usbd_devinfo_free(sc->sc_devname); sc->sc_devname = NULL; } usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, USBDEV(sc->sc_dev)); return 0; } int ov51x_activate(device_ptr_t self, enum devact act) { struct ov51x_softc *sc = device_private(self); int rv; rv = 0; switch (act) { case DVACT_ACTIVATE: break; case DVACT_DEACTIVATE: sc->sc_dying = 1; break; } return rv; } static void ov51x_childdet(device_t self, device_t child) { struct ov51x_softc *sc = device_private(self); if (sc->sc_videodev) { KASSERT(sc->sc_videodev == child); sc->sc_videodev = NULL; } } #define OVFRAME_SKIPPING 0 #define OVFRAME_READING 1 static void ov51x_isoc_decode(struct ov51x_softc *sc, uint8_t *pbuf, uint32_t len) { struct video_payload payload; static int expectedfrm; /* int n, zero; zero = 1; for (n = 0; n < len - 1 && zero; n++) { if (pbuf[n] != 0) zero = 0; } if (zero) printf("All zeros\n"); else printf("Not all zeros (%d)\n", n); */ /* * Start (SOF) and end of frame (EOF) headers have bytes 0 - 7 set to * zero. Bit 7 is set in byte 8 for EOF. EOF has width and height in * bytes 9 and 10. The frames end with a packet number which counts up * from 1 to 255 before looping back to 1. Packet number 0 is only * used in the SOF and EOF packets */ payload.frameno = 0; /* if (pbuf[0] == 0 && pbuf[1] == 0 && pbuf[2] == 0 && pbuf[3] == 0 && pbuf[4] == 0 && pbuf[5] == 0 && pbuf[6] == 0 && pbuf[7] == 0) { DPRINTF(("All zero header, flags %02x packet number:%u\n", pbuf[8], pbuf[len - 1])); } */ if (pbuf[0] == 0 && pbuf[1] == 0 && pbuf[2] == 0 && pbuf[3] == 0 && pbuf[4] == 0 && pbuf[5] == 0 && pbuf[6] == 0 && pbuf[7] == 0 && pbuf[len - 1] == 0) { DPRINTF(("Potential SOF/EOF\n")); if ((pbuf[8] & 0x80) == 0 && sc->sc_status == OVFRAME_SKIPPING) { /* Found SOF */ DPRINTF(("SOF\n")); sc->sc_status = OVFRAME_READING; expectedfrm = 1; payload.data = pbuf + 9; payload.size = len - 10; payload.end_of_frame = 0; video_submit_payload (sc->sc_videodev, &payload); } else if ((pbuf[8] & 0x80) == 0x80 && sc->sc_status == OVFRAME_READING) { /* Found EOF */ sc->sc_status = OVFRAME_SKIPPING; DPRINTF(("end of frame %ux%u\n", pbuf[9], pbuf[10])); payload.data = pbuf + 11; payload.size = len - 12; payload.end_of_frame = 1; video_submit_payload (sc->sc_videodev, &payload); } else { /* status is wrong. abort this frame */ DPRINTF(("Status wrong - skipping frame\n")); sc->sc_status = OVFRAME_SKIPPING; return; } } else if (sc->sc_status == OVFRAME_READING) { if (pbuf[len - 1] != expectedfrm) { /* Frame out of order. abort the capture */ aprint_error_dev(sc->sc_dev, "frame out of order. Expected %d, got %d\n", expectedfrm, pbuf[len - 1]); sc->sc_status = OVFRAME_SKIPPING; return; } DPRINTF(("frame %d\n", expectedfrm)); if (++expectedfrm == 256) expectedfrm = 1; payload.data = pbuf; payload.size = len - 1; payload.end_of_frame = 0; video_submit_payload (sc->sc_videodev, &payload); } else { DPRINTF(("waiting for SOF\n")); } } static void ov51x_isoc(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status err) { struct ov51x_isoc_xfer *ix = priv; struct ov51x_softc *sc = ix->ix_sc; usbd_pipe_handle isoc = sc->sc_isoc_pipe; uint8_t *buf; uint32_t len; int i; ix->ix_busy = 0; if (err) { if (err == USBD_STALLED) { DPRINTF(("ov51x clear stall\n")); usbd_clear_endpoint_stall_async(isoc); goto restart; } else { printf("ov51x_isoc: stopping: %s\n", usbd_errstr(err)); return; } } usbd_get_xfer_status(xfer, NULL, NULL, &len, NULL); if (err) printf("ov51x_isoc: err=%s\n", usbd_errstr(err)); if (len == 0) goto restart; buf = ix->ix_buf; for (i = 0; i < sc->sc_isoc_nframes; i++, buf += sc->sc_isoc_uframe_len) { if (ix->ix_frlengths[i] == 0) continue; ov51x_isoc_decode(sc, buf, ix->ix_frlengths[i]); } restart: ov51x_isoc_start(sc, ix); } static void ov51x_init(struct ov51x_softc *sc) { /* Reset OV511 - we currently don't support other models */ ov51x_setreg(sc, OVREG51x_SYS_RESET, OV511_RESET_ALL); ov51x_setreg(sc, OVREG51x_SYS_RESET, 0); /* Initialise and switch on clocks */ ov51x_setreg(sc, OVREG511_SYS_INIT, 0x1); /* Read camera model */ sc->sc_model = ov51x_getreg(sc, OVREG511_SYS_CUST_ID); /* set I2C write slave ID for OV7610 */ ov51x_setreg(sc, OVREG51x_I2C_W_SID, OV7610_I2C_WRITE_ID); /* set I2C read slave ID for OV7610 */ ov51x_setreg(sc, OVREG51x_I2C_SID, OV7610_I2C_READ_ID); /* SJB Why set FIFO size here? */ /* ov51x_setreg(sc, OVREG51x_FIFO_PSIZE, 0x1); ov51x_setreg(sc, OVREG511_FIFO_OPTS, 0x0); */ /* Put the camera on hold */ ov51x_setreg(sc, OVREG51x_SYS_RESET, 0x3d); ov51x_setreg(sc, OVREG51x_SYS_RESET, 0x0); /* set YUV 4:2:0 format, Y channel low-pass filter */ ov51x_setreg(sc, OVREG511_CAM_M400, 0x01); ov51x_setreg(sc, OVREG511_CAM_M420_YFIR, 0x03); /* disable both software and hardware snapshots */ ov51x_setreg(sc, OVREG511_SYS_SNAP, 0x0); /* disable compression */ ov51x_setreg(sc, OVREG511_REG_CE_EN, 0x0); /* This returns 0 if we have an OV7620 sensor */ sc->sc_sensor = (ov51x_i2c_getreg(sc, OV7610_REG_COMI) == 0 ? OVSENSOR_7620: OVSENSOR_7610); /* Insert packet number and disable compressed non-zero */ ov51x_setreg(sc, OVREG511_FIFO_OPTS, 0x03); /* set up the OV7610/OV7620 */ if(sc->sc_sensor == OVSENSOR_7610) { aprint_normal_dev(sc->sc_dev, "OV7610 sensor\n"); ov51x_i2c_setreg(sc, OV7610_REG_EC, 0xff); ov51x_i2c_setreg(sc, OV7610_REG_FD, 0x06); ov51x_i2c_setreg(sc, OV7610_REG_COMH, 0x24); ov51x_i2c_setreg(sc, OV7610_REG_EHSL, 0xac); ov51x_i2c_setreg(sc, OV7610_REG_COMA, 0x00); ov51x_i2c_setreg(sc, OV7610_REG_COMH, 0x24); ov51x_i2c_setreg(sc, OV7610_REG_RWB, 0x85); ov51x_i2c_setreg(sc, OV7610_REG_COMD, 0x01); ov51x_i2c_setreg(sc, 0x23, 0x00); ov51x_i2c_setreg(sc, OV7610_REG_ECW, 0x10); ov51x_i2c_setreg(sc, OV7610_REG_ECB, 0x8a); ov51x_i2c_setreg(sc, OV7610_REG_COMG, 0xe2); ov51x_i2c_setreg(sc, OV7610_REG_EHSH, 0x00); ov51x_i2c_setreg(sc, OV7610_REG_EXBK, 0xfe); ov51x_i2c_setreg(sc, 0x30, 0x71); ov51x_i2c_setreg(sc, 0x31, 0x60); ov51x_i2c_setreg(sc, 0x32, 0x26); ov51x_i2c_setreg(sc, OV7610_REG_YGAM, 0x20); ov51x_i2c_setreg(sc, OV7610_REG_BADJ, 0x48); ov51x_i2c_setreg(sc, OV7610_REG_COMA, 0x24); ov51x_i2c_setreg(sc, OV7610_REG_SYN_CLK, 0x01); ov51x_i2c_setreg(sc, OV7610_REG_BBS, 0x24); ov51x_i2c_setreg(sc, OV7610_REG_RBS, 0x24); } else { aprint_normal_dev(sc->sc_dev, "OV7620 sensor\n"); ov51x_i2c_setreg(sc, OV7610_REG_GC, 0x00); ov51x_i2c_setreg(sc, OV7610_REG_RWB, 0x05); ov51x_i2c_setreg(sc, OV7610_REG_EC, 0xff); ov51x_i2c_setreg(sc, OV7610_REG_COMB, 0x01); ov51x_i2c_setreg(sc, OV7610_REG_FD, 0x06); ov51x_i2c_setreg(sc, OV7610_REG_COME, 0x1c); ov51x_i2c_setreg(sc, OV7610_REG_COMF, 0x90); ov51x_i2c_setreg(sc, OV7610_REG_ECW, 0x2e); ov51x_i2c_setreg(sc, OV7610_REG_ECB, 0x7c); ov51x_i2c_setreg(sc, OV7610_REG_COMH, 0x24); ov51x_i2c_setreg(sc, OV7610_REG_EHSH, 0x04); ov51x_i2c_setreg(sc, OV7610_REG_EHSL, 0xac); ov51x_i2c_setreg(sc, OV7610_REG_EXBK, 0xfe); ov51x_i2c_setreg(sc, OV7610_REG_COMJ, 0x93); ov51x_i2c_setreg(sc, OV7610_REG_BADJ, 0x48); ov51x_i2c_setreg(sc, OV7610_REG_COMK, 0x81); ov51x_i2c_setreg(sc, OV7610_REG_GAM, 0x04); } /* Line and pixel divisor set to 1 */ ov51x_setreg(sc, OVREG511_CAM_PXDIV, 0x00); ov51x_setreg(sc, OVREG511_CAM_LNDIV, 0x00); /* XXX This needs to be moved to the set_format stuff */ /* Pixel and line count to 640x480 */ ov51x_setreg(sc, OVREG511_CAM_PXCNT, (640 / 8) - 1); ov51x_setreg(sc, OVREG511_CAM_LNCNT, (480 / 8) - 1); /* vid = 0x3d */ /* set sensor for 640x480 */ ov51x_i2c_setreg(sc, OV7610_REG_SYN_CLK, 0x06); ov51x_i2c_setreg(sc, OV7610_REG_HE, 0x3a + (640>>2)); ov51x_i2c_setreg(sc, OV7610_REG_VE, 5 + (480>>1)); ov51x_i2c_setreg(sc, OV7610_REG_COMA, 0x24); ov51x_i2c_setreg(sc, OV7610_REG_COMC, 0x04); ov51x_i2c_setreg(sc, OV7610_REG_COML, 0x1e); /* reset the device again (not regs or OV511) */ ov51x_setreg(sc, OVREG51x_SYS_RESET, OV511_RESET_NOREGS); ov51x_setreg(sc, OVREG51x_SYS_RESET, 0x00); } static void ov51x_stop(struct ov51x_softc *sc) { usbd_status err; ov51x_led(sc, false); DPRINTF(("ov51x_stop\n")); /* give it time to settle */ usbd_delay_ms(sc->sc_udev, 1000); /* Put device on hold */ ov51x_setreg(sc, OVREG51x_SYS_RESET, 0x3d); DPRINTF(("Set interface %d\n", sc->sc_altoff)); err = usbd_set_interface(sc->sc_iface, sc->sc_altoff); if (err) aprint_error_dev(sc->sc_dev, "error stopping stream: %s\n", usbd_errstr(err)); } static void ov51x_start(struct ov51x_softc *sc) { usbd_status err; DPRINTF(("ov51x_start\n")); ov51x_led(sc, true); /* Set multiplier for FIFO size*/ ov51x_setreg(sc, OVREG51x_FIFO_PSIZE, sc->sc_bufsize/32); DPRINTF(("Set interface %d\n", sc->sc_alton)); err = usbd_set_interface(sc->sc_iface, sc->sc_alton); if (err) aprint_error_dev(sc->sc_dev, "couldn't set interface: %s\n", usbd_errstr(err)); ov51x_setreg(sc, OVREG51x_SYS_RESET, OV511_RESET_NOREGS); ov51x_setreg(sc, OVREG51x_SYS_RESET, 0); } static void ov51x_led(struct ov51x_softc *sc, bool enabled) { /* LED doesn't work on OV511, only OV511+ */ DPRINTF(("ov51x_led: %s\n", (enabled ? "yes" : "no"))); if (sc->sc_bridge == OVBRIDGE_511PLUS) ov51x_setreg(sc, OVREG511_SYS_LED_CTL, enabled ? 0x1 : 0); } static uint8_t ov51x_getreg(struct ov51x_softc *sc, uint16_t reg) { usb_device_request_t req; usbd_status err; uint8_t buf; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = 2; USETW(req.wValue, 0x0000); USETW(req.wIndex, reg); USETW(req.wLength, 1); err = usbd_do_request(sc->sc_udev, &req, &buf); if (err) { aprint_error_dev(sc->sc_dev, "couldn't read reg 0x%04x: %s\n", reg, usbd_errstr(err)); return 0xff; } return buf; } static void ov51x_setreg(struct ov51x_softc *sc, uint16_t reg, uint8_t val) { usb_device_request_t req; usbd_status err; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = 2; USETW(req.wValue, 0x0000); USETW(req.wIndex, reg); USETW(req.wLength, 1); err = usbd_do_request(sc->sc_udev, &req, &val); if (err) aprint_error_dev(sc->sc_dev, "couldn't write reg 0x%04x: %s\n", reg, usbd_errstr(err)); /* cval = ov51x_getreg(sc, reg); DPRINTF(("reg: %04x = %02x -> %02x\n", reg, val, cval)); */ } static void ov51x_i2c_setreg(struct ov51x_softc *sc, uint16_t reg, uint8_t val) { int status = 0; int retries = OV7610_I2C_RETRIES; while (--retries >= 0) { ov51x_setreg(sc, OVREG51x_I2C_SADDR_3, reg); ov51x_setreg(sc, OVREG51x_I2C_DATA, val); ov51x_setreg(sc, OVREG511_I2C_CTL, 0x1); /* wait until bus idle */ do { status = ov51x_getreg(sc, OVREG511_I2C_CTL); } while ((status & 0x01) == 0); /* OK if ACK */ if((status & 0x02) == 0) return; } aprint_error_dev(sc->sc_dev, "i2c write timeout reg 0x%04x\n", reg); } static uint8_t ov51x_i2c_getreg(struct ov51x_softc *sc, uint16_t reg) { int status = 0; uint8_t val = 0; int retries = OV7610_I2C_RETRIES; while (--retries >= 0) { /* wait until bus idle */ do { status = ov51x_getreg(sc, OVREG511_I2C_CTL); } while ((status & 0x01) == 0); /* perform a dummy write cycle to set the register */ ov51x_setreg(sc, OVREG51x_I2C_SADDR_3, reg); /* initiate the dummy write */ ov51x_setreg(sc, OVREG511_I2C_CTL, 0x03); /* wait until bus idle */ do { status = ov51x_getreg(sc, OVREG511_I2C_CTL); } while ((status & 0x01) == 0); if ((status & 0x2) == 0) break; } if(retries < 0) { aprint_error_dev(sc->sc_dev, "i2c read timeout reg 0x%04x\n", reg); return 0; } retries = OV7610_I2C_RETRIES; while (--retries >= 0) { /* initiate read */ ov51x_setreg(sc, OVREG511_I2C_CTL, 0x05); /* wait until bus idle */ do { status = ov51x_getreg(sc, OVREG511_I2C_CTL); } while ((status & 0x01) == 0); if ((status & 0x2) == 0) break; /* abort I2C bus before retrying */ ov51x_setreg(sc, OVREG511_I2C_CTL, 0x10); } if(retries < 0) { aprint_error_dev(sc->sc_dev, "i2c read timeout reg 0x%04x\n", reg); return 0; } /* retrieve data */ val = ov51x_getreg(sc, OVREG51x_I2C_DATA); /* issue another read for some weird reason */ ov51x_setreg(sc, OVREG511_I2C_CTL, 0x05); return val; } static int ov51x_init_pipes(struct ov51x_softc *sc) { usbd_status err; int i; if (sc->sc_dying) return EIO; err = usbd_open_pipe(sc->sc_iface, sc->sc_isoc, USBD_EXCLUSIVE_USE, &sc->sc_isoc_pipe); if (err) { aprint_error_dev(sc->sc_dev, "couldn't open isoc pipe: %s\n", usbd_errstr(err)); return ENOMEM; } for (i = 0; i < OV51X_NXFERS; i++) { sc->sc_ix[i].ix_xfer = usbd_alloc_xfer(sc->sc_udev); if (sc->sc_ix[i].ix_xfer == NULL) { aprint_error_dev(sc->sc_dev, "usbd_alloc_xfer failed\n"); return ENOMEM; } sc->sc_ix[i].ix_buf = usbd_alloc_buffer(sc->sc_ix[i].ix_xfer, sc->sc_isoc_buflen); if (sc->sc_ix[i].ix_buf == NULL) { aprint_error_dev(sc->sc_dev, "usbd_alloc_buffer failed\n"); return ENOMEM; } } return 0; } int ov51x_close_pipes(struct ov51x_softc *sc) { int i; if (sc->sc_isoc_pipe != NULL) { usbd_abort_pipe(sc->sc_isoc_pipe); usbd_close_pipe(sc->sc_isoc_pipe); sc->sc_isoc_pipe = NULL; } for (i = 0; i < OV51X_NXFERS; i++) if (sc->sc_ix[i].ix_buf != NULL) { usbd_free_xfer(sc->sc_ix[i].ix_xfer); sc->sc_ix[i].ix_xfer = NULL; } return 0; } /* video(9) API implementations */ static int ov51x_open(void *opaque, int flags) { struct ov51x_softc *sc = opaque; if (sc->sc_dying) return EIO; ov51x_init(sc); return 0; } static void ov51x_close(void *opaque) { } static const char * ov51x_get_devname(void *opaque) { struct ov51x_softc *sc = opaque; switch(sc->sc_model) { case 0: if (sc->sc_bridge == OVBRIDGE_511PLUS) return "Generic OV511+ (no ID)"; else return "Generic OV511 (no ID)"; break; case 1: return "Mustek WCam 3X"; break; case 3: return "D-Link DSB-C300"; break; case 4: return "Generic OV511/OV7610"; break; case 5: return "Puretek PT-6007"; break; case 6: return "Lifeview USB Life TV (NTSC)"; break; case 21: return "Creative Labs WebCam 3"; break; case 36: return "Koala-Cam"; break; case 100: return "Lifeview RoboCam"; break; case 102: return "AverMedia InterCam Elite"; break; case 112: return "MediaForte MV300"; break; default: if (sc->sc_bridge == OVBRIDGE_511PLUS) return "Unknown OV511+"; else return "Unknown OV511"; break; } } static int ov51x_enum_format(void *opaque, uint32_t index, struct video_format *format) { if (index != 0) return EINVAL; return ov51x_get_format(opaque, format); } static int ov51x_get_format(void *opaque, struct video_format *format) { format->pixel_format = VIDEO_FORMAT_YUV420; format->width = 640; format->height = 480; format->aspect_x = 4; format->aspect_y = 3; format->sample_size = format->width * format->height * 2; format->stride = format->width * 2; format->color.primaries = VIDEO_COLOR_PRIMARIES_UNSPECIFIED; format->color.gamma_function = VIDEO_GAMMA_FUNCTION_UNSPECIFIED; format->color.matrix_coeff = VIDEO_MATRIX_COEFF_UNSPECIFIED; format->interlace_flags = VIDEO_INTERLACE_ON; format->priv = 0; return 0; } static int ov51x_set_format(void *opaque, struct video_format *format) { /* if(small) { vs.width = 320; vs.height = 240; ov51x_setreg(sc, OVREG511_CAM_PXCNT, 0x27); ov51x_setreg(sc, OVREG511_CAM_LNCNT, 0x1D); ov51x_i2c_setreg(sc, OV7610_REG_SYN_CLK, 0x01); ov51x_i2c_setreg(sc, OV7610_REG_COMA, 0x04); ov51x_i2c_setreg(sc, OV7610_REG_COMC, 0x24); ov51x_i2c_setreg(sc, OV7610_REG_COML, 0x9e); } else { vs.width = 640; vs.height = 480; ov51x_setreg(sc, OVREG511_CAM_PXCNT, 0x4F); ov51x_setreg(sc, OVREG511_CAM_LNCNT, 0x3D); ov51x_i2c_setreg(sc, OV7610_REG_SYN_CLK, 0x06); ov51x_i2c_setreg(sc, OV7610_REG_HE, 0x3a + (640>>2)); ov51x_i2c_setreg(sc, OV7610_REG_VE, 5 + (480>>1)); ov51x_i2c_setreg(sc, OV7610_REG_COMA, 0x24); ov51x_i2c_setreg(sc, OV7610_REG_COMC, 0x04); ov51x_i2c_setreg(sc, OV7610_REG_COML, 0x1e); } */ #if notyet if (format->pixel_format != VIDEO_FORMAT_NV12) return EINVAL; if (format->width != 640 || format->height != 480) return EINVAL; #endif /* XXX */ return ov51x_get_format(opaque, format); } static int ov51x_try_format(void *opaque, struct video_format *format) { return ov51x_get_format(opaque, format); } static int ov51x_isoc_start(struct ov51x_softc *sc, struct ov51x_isoc_xfer *ix) { int i; ix->ix_busy = 1; for (i = 0; i < sc->sc_isoc_nframes; i++) ix->ix_frlengths[i] = sc->sc_isoc_uframe_len; /* start isoc */ usbd_setup_isoc_xfer(ix->ix_xfer, sc->sc_isoc_pipe, ix, ix->ix_frlengths, sc->sc_isoc_nframes, USBD_NO_COPY | USBD_SHORT_XFER_OK, ov51x_isoc); usbd_transfer(ix->ix_xfer); return 0; } static void ov51x_isoc_startall(struct ov51x_softc *sc) { int i; /* Reset frame status */ sc->sc_status = OVFRAME_SKIPPING; if (sc->sc_dying) return; for (i = 0; i < OV51X_NXFERS; i++) { if (sc->sc_ix[i].ix_busy) continue; ov51x_isoc_start(sc, &sc->sc_ix[i]); } } static int ov51x_start_transfer(void *opaque) { struct ov51x_softc *sc = opaque; int s; s = splusb(); ov51x_start(sc); ov51x_init_pipes(sc); ov51x_isoc_startall(sc); splx(s); return 0; } static int ov51x_stop_transfer(void *opaque) { struct ov51x_softc *sc = opaque; /* stop isoc */ ov51x_close_pipes(sc); ov51x_stop(sc); return 0; }