/* $NetBSD$ */
/*-
* Copyright (c) 2008 Stephen Borrill <
[email protected]>
* 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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kmem.h>
#include <sys/fcntl.h>
#include <sys/conf.h>
#include <sys/poll.h>
#include <sys/bus.h>
#include <sys/mutex.h>
#include <sys/condvar.h>
#include <uvm/uvm_extern.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdi_util.h>
#include <dev/usb/usbdevs.h>
#include <dev/video_if.h>
#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;
}