/* $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;
}