/* $NetBSD$ */

/*-
 * Copyright (c) 2008 Stephen Borrill <sborrill@NetBSD.org>
 * 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;
}
