/*
 * USB WinTV Video driver
 *
 * Supports Nogatech NT1003 based Video Boxes.
 * The original file was cpia.c from kernel 2.3.51
 * (C) Copyright 1999-2000 Johannes Erdfelt, jerdfelt@valinux.com
 * (C) Copyright 1999 Randy Dunlap
 * modified 2000 by Joerg Heckenbach, heckenbach@fgan.de
 */

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/list.h>
#include <linux/malloc.h>
#include <linux/mm.h>
#include <linux/smp_lock.h>
#include <linux/videodev.h>
#include <linux/vmalloc.h>
#include <linux/wrapper.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/usb.h>

#include <asm/io.h>

#include "wintv.h"

static int debug = 0;
MODULE_PARM(debug, "i");

/* Video Size 384 x 288 x 3 bytes for RGB */
/* 384 because xawtv tries to grab 384 even though we tell it 352 is our max */
#define MAX_FRAME_SIZE (384 * 288 * 3)

/*******************************/
/* Memory management functions */
/*******************************/

#define MDEBUG(x)	do { } while(0)		/* Debug memory management */

static struct usb_driver wintv_driver;

/* Given PGD from the address space's page table, return the kernel
 * virtual mapping of the physical memory mapped at ADR.
 */
static inline unsigned long uvirt_to_kva(pgd_t *pgd, unsigned long adr)
{
	unsigned long ret = 0UL;
	pmd_t *pmd;
	pte_t *ptep, pte;

	if (!pgd_none(*pgd)) {
		pmd = pmd_offset(pgd, adr);
		if (!pmd_none(*pmd)) {
			ptep = pte_offset(pmd, adr);
			pte = *ptep;
			if (pte_present(pte))
				ret = page_address(pte_page(pte)) | (adr & (PAGE_SIZE-1));
		}
	}
	MDEBUG(printk("uv2kva(%lx-->%lx)", adr, ret));
	return ret;
}

static inline unsigned long uvirt_to_bus(unsigned long adr)
{
	unsigned long kva, ret;

	kva = uvirt_to_kva(pgd_offset(current->mm, adr), adr);
	ret = virt_to_bus((void *)kva);
	MDEBUG(printk("uv2b(%lx-->%lx)", adr, ret));
	return ret;
}

static inline unsigned long kvirt_to_bus(unsigned long adr)
{
	unsigned long va, kva, ret;

	va = VMALLOC_VMADDR(adr);
	kva = uvirt_to_kva(pgd_offset_k(va), va);
	ret = virt_to_bus((void *)kva);
	MDEBUG(printk("kv2b(%lx-->%lx)", adr, ret));
	return ret;
}

/* Here we want the physical address of the memory.
 * This is used when initializing the contents of the
 * area and marking the pages as reserved.
 */
static inline unsigned long kvirt_to_pa(unsigned long adr)
{
	unsigned long va, kva, ret;

	va = VMALLOC_VMADDR(adr);
	kva = uvirt_to_kva(pgd_offset_k(va), va);
	ret = __pa(kva);
	MDEBUG(printk("kv2pa(%lx-->%lx)", adr, ret));
	return ret;
}

static void *rvmalloc(unsigned long size)
{
	void *mem;
	unsigned long adr, page;

	/* Round it off to PAGE_SIZE */
	size += (PAGE_SIZE - 1);
	size &= ~(PAGE_SIZE - 1);

	mem = vmalloc(size);
	if (!mem)
		return NULL;

	memset(mem, 0, size); /* Clear the ram out, no junk to the user */
	adr = (unsigned long) mem;
	while (size > 0) {
		page = kvirt_to_pa(adr);
		mem_map_reserve(MAP_NR(__va(page)));
		adr += PAGE_SIZE;
		if (size > PAGE_SIZE)
			size -= PAGE_SIZE;
		else
			size = 0;
	}

	return mem;
}

static void rvfree(void *mem, unsigned long size)
{
	unsigned long adr, page;

	if (!mem)
		return;

	size += (PAGE_SIZE - 1);
	size &= ~(PAGE_SIZE - 1);

	adr=(unsigned long) mem;
	while (size > 0) {
		page = kvirt_to_pa(adr);
		mem_map_unreserve(MAP_NR(__va(page)));
		adr += PAGE_SIZE;
		if (size > PAGE_SIZE)
			size -= PAGE_SIZE;
		else
			size = 0;
	}
	vfree(mem);
}

static int usb_wintv_get_version(struct usb_device *dev, void *buf)
{
	return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
		USB_REQ_WINTV_GET_VERSION,
		USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
		0, 0, buf, 4, HZ);
}

#ifdef NOTUSED
static int usb_wintv_get_pnp_id(struct usb_device *dev, void *buf)
{
	return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
		USB_REQ_WINTV_GET_PNP_ID,
		USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
		0, 0, buf, 6, HZ);
}
#endif

#ifdef NOTUSED
static int usb_wintv_get_camera_status(struct usb_device *dev, void *buf)
{
	return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
		USB_REQ_WINTV_GET_CAMERA_STATUS,
		USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
		0, 0, buf, 8, HZ);
}
#endif

static int usb_wintv_goto_hi_power(struct usb_device *dev)
{
/* This really switches on the power LED */
	char value = 0x20;
	return usb_control_msg(dev, usb_sndctrlpipe(dev, 1),
		0x33, USB_TYPE_VENDOR | USB_RECIP_ENDPOINT,
		0, 0, &value, sizeof(value), HZ);
}

static int usb_wintv_get_vp_version(struct usb_device *dev, void *buf)
{
/* This reads the version byte of the SAA7111A */
        char value = 0x20;
        usb_control_msg(dev, usb_sndctrlpipe(dev, 1),
                0x33, USB_TYPE_VENDOR | USB_RECIP_ENDPOINT,
                0, 0x07, &value, sizeof(value), HZ);
 
        value = 0x48; /*Slave-Address for write*/
        usb_control_msg(dev, usb_sndctrlpipe(dev, 1),
                0x33, USB_TYPE_VENDOR | USB_RECIP_ENDPOINT,
                0, 0x08, &value, sizeof(value), HZ);
 
        value = 0x12; /*Bytes to write | SER_GO*/
        usb_control_msg(dev, usb_sndctrlpipe(dev, 1),
                0x33, USB_TYPE_VENDOR | USB_RECIP_ENDPOINT,
                0, 0x09, &value, sizeof(value), HZ);
 
        value = 0x11;
        usb_control_msg(dev, usb_sndctrlpipe(dev, 1),
                0x33, USB_TYPE_VENDOR | USB_RECIP_ENDPOINT,
                0, 0x09, &value, sizeof(value), HZ);
 
        value = 0x49;
        usb_control_msg(dev, usb_sndctrlpipe(dev, 1),
                0x33, USB_TYPE_VENDOR | USB_RECIP_ENDPOINT,
                0, 0x08, &value, sizeof(value), HZ);
 
        value = 0x19;
        usb_control_msg(dev, usb_sndctrlpipe(dev, 1),
                0x33, USB_TYPE_VENDOR | USB_RECIP_ENDPOINT,
                0, 0x09, &value, sizeof(value), HZ);
 
 
        usb_control_msg(dev, usb_rcvctrlpipe(dev, 1),
                0x33, USB_TYPE_VENDOR | USB_RECIP_ENDPOINT,
                0, 0x0A, &value, sizeof(value), HZ);
        return value;                                                                                                   
}

static int usb_wintv_set_sensor_fps(struct usb_device *dev, int sensorbaserate, int sensorclkdivisor)
{
	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
		USB_REQ_WINTV_SET_SENSOR_FPS,
		USB_TYPE_VENDOR | USB_RECIP_DEVICE,
		(sensorbaserate << 8) + sensorclkdivisor, 0, NULL, 0, HZ);
}

#ifdef NOTUSED
static int usb_wintv_grab_frame(struct usb_device *dev, int streamstartline)
{
	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
		USB_REQ_WINTV_GRAB_FRAME, USB_TYPE_VENDOR | USB_RECIP_DEVICE,
		streamstartline << 8, 0, NULL, 0, HZ);
}
#endif

static int usb_wintv_upload_frame(struct usb_device *dev, int forceupload)
{
	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
		USB_REQ_WINTV_UPLOAD_FRAME, USB_TYPE_VENDOR | USB_RECIP_DEVICE,
		forceupload, 0, NULL, 0, HZ);
}

static int usb_wintv_set_grab_mode(struct usb_device *dev, int continuousgrab)
{


}

static int usb_wintv_set_format(struct usb_device *dev, int size, int subsample, int order)
{
	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
		USB_REQ_WINTV_SET_FORMAT,
		USB_TYPE_VENDOR | USB_RECIP_DEVICE,
		(subsample << 8) + size, order, NULL, 0, HZ);
}

static int usb_wintv_set_roi(struct usb_device *dev, int colstart, int colend, int rowstart, int rowend)
{
	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
		USB_REQ_WINTV_SET_ROI,
		USB_TYPE_VENDOR | USB_RECIP_DEVICE,
		(colend << 8) + colstart, (rowend << 8) + rowstart,
		NULL, 0, HZ);
}

static int usb_wintv_set_compression(struct usb_device *dev, int compmode, int decimation)
{
	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
		USB_REQ_WINTV_SET_COMPRESSION,
		USB_TYPE_VENDOR | USB_RECIP_DEVICE,
		(decimation << 8) + compmode, 0, NULL, 0, HZ);
}

#ifdef NOTUSED
static int usb_wintv_set_compression_target(struct usb_device *dev, int target, int targetfr, int targetq)
{
	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
		USB_REQ_WINTV_SET_COMPRESSION_TARGET,
		USB_TYPE_VENDOR | USB_RECIP_DEVICE,
		(targetfr << 8) + target, targetq, NULL, 0, HZ);
}
#endif

#ifdef NOTUSED
static int usb_wintv_initstreamcap(struct usb_device *dev, int skipframes, int streamstartline)
{
	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
		USB_REQ_WINTV_INIT_STREAM_CAP,
		USB_TYPE_VENDOR | USB_RECIP_DEVICE,
		(streamstartline << 8) + skipframes, 0, NULL, 0, HZ);
}

static int usb_wintv_finistreamcap(struct usb_device *dev)
{
	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
		USB_REQ_WINTV_FINI_STREAM_CAP,
		USB_TYPE_VENDOR | USB_RECIP_DEVICE, 0, 0, NULL, 0, HZ);
}

static int usb_wintv_startstreamcap(struct usb_device *dev)
{
	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
		USB_REQ_WINTV_START_STREAM_CAP,
		USB_TYPE_VENDOR | USB_RECIP_DEVICE, 0, 0, NULL, 0, HZ);
}

static int usb_wintv_endstreamcap(struct usb_device *dev)
{
	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
		USB_REQ_WINTV_END_STREAM_CAP,
		USB_TYPE_VENDOR | USB_RECIP_DEVICE, 0, 0, NULL, 0, HZ);
}
#endif

/* How much data is left in the scratch buf? */
#define scratch_left(x)	(wintv->scratchlen - (int)((char *)x - (char *)wintv->scratch))

static void wintv_parse_data(struct usb_wintv *wintv)
{
	struct wintv_frame *frame, *pframe;
	unsigned char *data = wintv->scratch;
	unsigned long left;
	long copylen = 0;

	/* Grab the current frame and the previous frame */
	frame = &wintv->frame[wintv->curframe];
	pframe = &wintv->frame[(wintv->curframe - 1 + WINTV_NUMFRAMES) % WINTV_NUMFRAMES];

	while (1) {
		if (!scratch_left(data))
			goto out;

		switch (frame->scanstate) {
		case STATE_SCANNING:
		{
			struct wintv_frame_header *header;

			/* We need at least 2 bytes for the magic value */
			if (scratch_left(data) < 2)
				goto out;

			header = (struct wintv_frame_header *)data;

			if (be16_to_cpup(&header->magic) == WINTV_MAGIC) {
				frame->scanstate = STATE_HEADER;
				break;
			}

			/* Woops, lost the header, find the end of the frame */
			if (scratch_left(data) < 4)
				goto out;

			/* See if we found the end of the frame */
			while (scratch_left(data) >= 4) {
				if (*((__u32 *)data) == 0xFFFFFFFF) {
					data += 4;
					if (debug >= 1)
						printk(KERN_INFO "wintv: EOF while scanning for magic\n");
					goto error;
				}
				data++;
			}
			break;
		}
		case STATE_HEADER:
			/* We need at least 64 bytes for the header */
			if (scratch_left(data) <
			    sizeof(struct wintv_frame_header))
				goto out;

			memcpy(&frame->header, data,
				sizeof(struct wintv_frame_header));

			/* Skip over the header */
			data += sizeof(struct wintv_frame_header);

			frame->hdrwidth = (frame->header.col_end -
				frame->header.col_start) * 8;
			frame->hdrheight = (frame->header.row_end -
				frame->header.row_start) * 4;
			if (debug >= 2) {
				printk(KERN_DEBUG "wintv: frame size %dx%d\n",
					frame->hdrwidth, frame->hdrheight);
				printk(KERN_DEBUG "wintv: frame %scompressed\n",
					frame->header.comp_enable ? "" : "not ");
			}

			frame->scanstate = STATE_LINES;
			frame->curline = 0;
			break;

		case STATE_LINES:
		{
			unsigned char *f, *end;
			unsigned int len;
			int i;
			int y, u, y1, v, r, g, b;

			/* We want at least 2 bytes for the length */
			if (scratch_left(data) < 2)
				goto out;

			/* Grab the length */
			len = data[0] + (data[1] << 8);

			/* Check to make sure it's nothing outrageous */
			if (len > (frame->hdrwidth * 2) + 1) {
				if (debug >= 1)
					printk(KERN_DEBUG "wintv: bad length, resynching (expected %d, got %d)\n", (frame->hdrwidth * 2) + 1, len);
				goto error;
			}

			/* Make sure there's enough data for the entire line */
			if (scratch_left(data + 2) < len)
				goto out;

			/* Skip over the length */
			data += 2;

			/* Is the end of the line there */
			if (data[len - 1] != 0xFD) {
				if (debug >= 1)
					printk(KERN_DEBUG "wintv: lost synch\n");
				goto error;
			}

			/* Start at the beginning */
			end = data + len - 1;

			f = frame->data + (frame->width * 3 * frame->curline);

			if (frame->header.comp_enable) {
				unsigned char *fp;

				/* We use the previous frame as a reference */
				fp = pframe->data +
					(frame->width * 3 * frame->curline);

				while (data < end) {
					if (*data & 1) {
						/* Compress RLE data */
						i = *data >> 1;
						memcpy(f, fp, i * 3);
						copylen += (i * 3);
						f += (i * 3);
						fp += (i * 3);
						data++;
					} else {
						/* Raw data */

#define LIMIT(x) ((((x)>0xffffff)?0xff0000:(((x)<=0xffff)?0:(x)&0xff0000))>>16)

y =  *data++ - 16;
u =  *data++ - 128;
y1 = *data++ - 16;
v =  *data++ - 128;
r = 104635 * v;
g = -25690 * u + -53294 * v;
b = 132278 * u;
y  *= 76310;
y1 *= 76310;
*f++ = LIMIT(b + y); *f++ = LIMIT(g + y); *f++ = LIMIT(r + y);
*f++ = LIMIT(b + y1); *f++ = LIMIT(g + y1); *f++ = LIMIT(r + y1);
						fp += 6;
						copylen += 6;
					}
				}
			} else {
				/* Raw data */
				while (data < end) {
y =  *data++ - 16;
u =  *data++ - 128;
y1 = *data++ - 16;
v =  *data++ - 128;
r = 104635 * v;
g = -25690 * u + -53294 * v;
b = 132278 * u;
y  *= 76310;
y1 *= 76310;
*f++ = LIMIT(b + y); *f++ = LIMIT(g + y); *f++ = LIMIT(r + y);
*f++ = LIMIT(b + y1); *f++ = LIMIT(g + y1); *f++ = LIMIT(r + y1);
copylen += 6;
				}
			}

			/* Skip the last byte */
			data++;

			if (++frame->curline >= frame->hdrheight)
				goto nextframe;

			break;
		} /* end case STATE_LINES */
		} /* end switch (scanstate) */
	} /* end while (1) */

nextframe:
	if (debug >= 1)
		printk(KERN_DEBUG "wintv: marking as success\n");

	if (scratch_left(data) >= 4 && *((__u32 *)data) == 0xFFFFFFFF)
		data += 4;

	frame->grabstate = FRAME_DONE;

	goto wakeup;

error:
	if (debug >= 1)
		printk(KERN_DEBUG "wintv: marking as error\n");

	frame->grabstate = FRAME_ERROR;

	/* Get a fresh frame since this frame may have been important */
	wintv->compress = 0;

	copylen = 0;

wakeup:
	wintv->curframe = -1;

	/* This will cause the process to request another frame. */
	if (waitqueue_active(&frame->wq))
		wake_up_interruptible(&frame->wq);

out:
	/* Grab the remaining */
	left = scratch_left(data);
	memmove(wintv->scratch, data, left);
	wintv->scratchlen = left;

	/* Update the frame's uncompressed length. */
	frame->scanlength += copylen;
}

/*
 * Make all of the blocks of data contiguous
 */
static int wintv_compress_isochronous(struct usb_wintv *wintv, urb_t *urb)
{
	unsigned char *cdata, *data;
	int i, totlen = 0;

	data = wintv->scratch + wintv->scratchlen;
	for (i = 0; i < urb->number_of_packets; i++) {
		int n = urb->iso_frame_desc[i].actual_length;
		int st = urb->iso_frame_desc[i].status;
		
		cdata = urb->transfer_buffer + urb->iso_frame_desc[i].offset;

		if (st && debug >= 1)
			printk(KERN_DEBUG "wintv data error: [%d] len=%d, status=%X\n",
				i, n, st);

		if ((wintv->scratchlen + n) > SCRATCH_BUF_SIZE) {
			printk(KERN_DEBUG "wintv: scratch buf overflow!scr_len: %d, n: %d\n",wintv->scratchlen, n );
			return totlen;
		}

		if (n) {
			memmove(data, cdata, n);
			data += n;
			totlen += n;
			wintv->scratchlen += n;
		}
	}

	return totlen;
}

static void wintv_isoc_irq(struct urb *urb)
{
	int len;
	struct usb_wintv *wintv = urb->context;
	struct wintv_sbuf *sbuf;
	int i;

	if (!wintv->dev)
		return;

	if (!wintv->streaming) {
		if (debug >= 1)
			printk(KERN_DEBUG "wintv: oops, not streaming, but interrupt\n");
		return;
	}
	
	sbuf = &wintv->sbuf[wintv->cursbuf];

	/* Copy the data received into our scratch buffer */
	len = wintv_compress_isochronous(wintv, urb);

	/* If we don't have a frame we're current working on, complain */
	if (wintv->scratchlen) {
		if (wintv->curframe < 0) {
			if (debug >= 1)
				printk(KERN_DEBUG "wintv: received data, but no frame available\n");
		} else
			wintv_parse_data(wintv);
	}

	for (i = 0; i < FRAMES_PER_DESC; i++) {
		sbuf->urb->iso_frame_desc[i].status = 0;
		sbuf->urb->iso_frame_desc[i].actual_length = 0;
	}

	/* Move to the next sbuf */
	wintv->cursbuf = (wintv->cursbuf + 1) % WINTV_NUMSBUF;

	return;
}

static int wintv_init_isoc(struct usb_wintv *wintv)
{
	urb_t *urb;
	int fx, err;

	wintv->compress = 0;
	wintv->curframe = -1;
	wintv->cursbuf = 0;
	wintv->scratchlen = 0;

	/* Alternate interface 3 is is the biggest frame size */
	if (usb_set_interface(wintv->dev, wintv->iface, 3) < 0) {
		printk(KERN_ERR "usb_set_interface error\n");
		return -EBUSY;
	}

	/* We double buffer the Iso lists */
	urb = usb_alloc_urb(FRAMES_PER_DESC);
	
	if (!urb) {
		printk(KERN_ERR "wintv_init_isoc: usb_init_isoc ret %d\n",
			0);
		return -ENOMEM;
	}
	wintv->sbuf[0].urb = urb;
	urb->dev = wintv->dev;
	urb->context = wintv;
	urb->pipe = usb_rcvisocpipe(wintv->dev, 1);
	urb->transfer_flags = USB_ISO_ASAP;
	urb->transfer_buffer = wintv->sbuf[0].data;
 	urb->complete = wintv_isoc_irq;
 	urb->number_of_packets = FRAMES_PER_DESC;
 	urb->transfer_buffer_length = FRAME_SIZE_PER_DESC * FRAMES_PER_DESC;
 	for (fx = 0; fx < FRAMES_PER_DESC; fx++) {
 		urb->iso_frame_desc[fx].offset = FRAME_SIZE_PER_DESC * fx;
		urb->iso_frame_desc[fx].length = FRAME_SIZE_PER_DESC;
	}
	urb = usb_alloc_urb(FRAMES_PER_DESC);
	if (!urb) {
		printk(KERN_ERR "wintv_init_isoc: usb_init_isoc ret %d\n",
			0);
		return -ENOMEM;
	}
	wintv->sbuf[1].urb = urb;
	urb->dev = wintv->dev;
	urb->context = wintv;
	urb->pipe = usb_rcvisocpipe(wintv->dev, 1);
	urb->transfer_flags = USB_ISO_ASAP;
	urb->transfer_buffer = wintv->sbuf[1].data;
 	urb->complete = wintv_isoc_irq;
 	urb->number_of_packets = FRAMES_PER_DESC;
 	urb->transfer_buffer_length = FRAME_SIZE_PER_DESC * FRAMES_PER_DESC;
 	for (fx = 0; fx < FRAMES_PER_DESC; fx++) {
 		urb->iso_frame_desc[fx].offset = FRAME_SIZE_PER_DESC * fx;
		urb->iso_frame_desc[fx].length = FRAME_SIZE_PER_DESC;
	}

	wintv->sbuf[1].urb->next = wintv->sbuf[0].urb;
	wintv->sbuf[0].urb->next = wintv->sbuf[1].urb;
	
	err = usb_submit_urb(wintv->sbuf[0].urb);
	if (err)
		printk(KERN_ERR "wintv_init_isoc: usb_submit_urb(0) ret %d\n",
			err);
	err = usb_submit_urb(wintv->sbuf[1].urb);
	if (err)
		printk(KERN_ERR "wintv_init_isoc: usb_submit_urb(1) ret %d\n",
			err);

	wintv->streaming = 1;

	return 0;
}

static void wintv_stop_isoc(struct usb_wintv *wintv)
{
	if (!wintv->streaming || !wintv->dev)
		return;

	/* Turn off continuous grab */
	if (usb_wintv_set_grab_mode(wintv->dev, 0) < 0) {
		printk(KERN_ERR "wintv_set_grab_mode error\n");
		return /* -EBUSY */;
	}

	/* Set packet size to 0 */
	if (usb_set_interface(wintv->dev, wintv->iface, 0) < 0) {
		printk(KERN_ERR "usb_set_interface error\n");
		return /* -EINVAL */;
	}

	wintv->streaming = 0;

	/* Unschedule all of the iso td's */
	if (wintv->sbuf[1].urb) {
		wintv->sbuf[1].urb->next = NULL;
		usb_unlink_urb(wintv->sbuf[1].urb);
		usb_free_urb(wintv->sbuf[1].urb);
		wintv->sbuf[1].urb = NULL;
	}
	if (wintv->sbuf[0].urb) {
		wintv->sbuf[0].urb->next = NULL;
		usb_unlink_urb(wintv->sbuf[0].urb);
		usb_free_urb(wintv->sbuf[0].urb);
		wintv->sbuf[0].urb = NULL;
	}
}

static int wintv_new_frame(struct usb_wintv *wintv, int framenum)
{
	struct wintv_frame *frame;
	int width, height;

	if (!wintv->dev)
		return -1;

	/* If we're not grabbing a frame right now and the other frame is */
	/*  ready to be grabbed into, then use it instead */
	if (wintv->curframe == -1) {
		if (wintv->frame[(framenum - 1 + WINTV_NUMFRAMES) % WINTV_NUMFRAMES].grabstate == FRAME_READY)
			framenum = (framenum - 1 + WINTV_NUMFRAMES) % WINTV_NUMFRAMES;
	} else
		return 0;

	frame = &wintv->frame[framenum];
	width = frame->width;
	height = frame->height;

	frame->grabstate = FRAME_GRABBING;
	frame->scanstate = STATE_SCANNING;
	frame->scanlength = 0;		/* accumulated in wintv_parse_data() */

	wintv->curframe = framenum;

	/* Make sure it's not too big */
	if (width > 352)
		width = 352;
	width = (width / 8) * 8;	/* Multiple of 8 */

	if (height > 288)
		height = 288;
	height = (height / 4) * 4;	/* Multiple of 4 */

	/* Set the ROI they want */
	if (usb_wintv_set_roi(wintv->dev, 0, width / 8, 0, height / 4) < 0)
		return -EBUSY;

	if (usb_wintv_set_compression(wintv->dev, wintv->compress ?
			COMP_AUTO : COMP_DISABLED, DONT_DECIMATE) < 0) {
		printk(KERN_ERR "wintv_set_compression error\n");
		return -EBUSY;
	}

	/* We want a fresh frame every 30 we get */
	wintv->compress = (wintv->compress + 1) % 30;

	/* Grab the frame */
	if (usb_wintv_upload_frame(wintv->dev, WAIT_FOR_NEXT_FRAME) < 0) {
		printk(KERN_ERR "wintv_upload_frame error\n");
		return -EBUSY;
	}

	return 0;
}

/* Video 4 Linux API */
static int wintv_open(struct video_device *dev, int flags)
{
	int err = -EBUSY;
	struct usb_wintv *wintv = (struct usb_wintv *)dev;

	down(&wintv->lock);
	if (wintv->user)
		goto out_unlock;

	wintv->frame[0].grabstate = FRAME_UNUSED;
	wintv->frame[1].grabstate = FRAME_UNUSED;

	err = -ENOMEM;

	/* Allocate memory for the frame buffers */
	wintv->fbuf = rvmalloc(2 * MAX_FRAME_SIZE);
	if (!wintv->fbuf)
		goto open_err_ret;

	wintv->frame[0].data = wintv->fbuf;
	wintv->frame[1].data = wintv->fbuf + MAX_FRAME_SIZE;

	wintv->sbuf[0].data = kmalloc (FRAMES_PER_DESC * FRAME_SIZE_PER_DESC, GFP_KERNEL);
	if (!wintv->sbuf[0].data)
		goto open_err_on0;

	wintv->sbuf[1].data = kmalloc (FRAMES_PER_DESC * FRAME_SIZE_PER_DESC, GFP_KERNEL);
	if (!wintv->sbuf[1].data)
		goto open_err_on1;

	/* Set default sizes in case IOCTL (VIDIOCMCAPTURE) is not used
	 * (using read() instead). */
	wintv->frame[0].width = 352;
	wintv->frame[0].height = 288;
	wintv->frame[0].bytes_read = 0;
	wintv->frame[1].width = 352;
	wintv->frame[1].height = 288;
	wintv->frame[1].bytes_read = 0;

	err = wintv_init_isoc(wintv);
	if (err)
		goto open_err_on2;

	wintv->user++;
	up(&wintv->lock);

	MOD_INC_USE_COUNT;

	return 0;

open_err_on2:
	kfree (wintv->sbuf[1].data);
open_err_on1:
	kfree (wintv->sbuf[0].data);
open_err_on0:
	rvfree(wintv->fbuf, 2 * MAX_FRAME_SIZE);
open_err_ret:
	return err;

out_unlock:
	up(&wintv->lock);
	return err;
}

static void wintv_close(struct video_device *dev)
{
	struct usb_wintv *wintv = (struct usb_wintv *)dev;

	down(&wintv->lock);	
	wintv->user--;

	MOD_DEC_USE_COUNT;

	wintv_stop_isoc(wintv);

	rvfree(wintv->fbuf, 2 * MAX_FRAME_SIZE);

	kfree(wintv->sbuf[1].data);
	kfree(wintv->sbuf[0].data);

	up(&wintv->lock);

	if (!wintv->dev) {
		video_unregister_device(&wintv->vdev);
		kfree(wintv);
	}
}

static int wintv_init_done(struct video_device *dev)
{
	return 0;
}

static long wintv_write(struct video_device *dev, const char *buf, unsigned long count, int noblock)
{
	return -EINVAL;
}

static int wintv_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
{
	struct usb_wintv *wintv = (struct usb_wintv *)dev;

	if (!wintv->dev)
		return -EIO;

	switch (cmd) {
		case VIDIOCGCAP:
		{
			struct video_capability b;

			strcpy(b.name, "WinTV USB Camera");
			b.type = VID_TYPE_CAPTURE | VID_TYPE_SUBCAPTURE;
			b.channels = 1;
			b.audios = 0;
			b.maxwidth = 352;	/* CIF */
			b.maxheight = 288;	/*  "  */
			b.minwidth = 8;
			b.minheight = 4;

			if (copy_to_user(arg, &b, sizeof(b)))
				return -EFAULT;

			return 0;
		}
		case VIDIOCGCHAN:
		{
			struct video_channel v;

			if (copy_from_user(&v, arg, sizeof(v)))
				return -EFAULT;
			if (v.channel != 0)
				return -EINVAL;

			v.flags = 0;
			v.tuners = 0;
			v.type = VIDEO_TYPE_CAMERA;
			strcpy(v.name, "Camera");

			if (copy_to_user(arg, &v, sizeof(v)))
				return -EFAULT;

			return 0;
		}
		case VIDIOCSCHAN:
		{
			int v;

			if (copy_from_user(&v, arg, sizeof(v)))
				return -EFAULT;

			if (v != 0)
				return -EINVAL;

			return 0;
		}
		case VIDIOCGPICT:
		{
			struct video_picture p;

			p.colour = 0x8000;	/* Damn British people :) */
			p.hue = 0x8000;
			p.brightness = 180 << 8;	/* XXX */
			p.contrast = 192 << 8;		/* XXX */
			p.whiteness = 105 << 8;		/* XXX */
			p.depth = 24;
			p.palette = VIDEO_PALETTE_RGB24;

			if (copy_to_user(arg, &p, sizeof(p)))
				return -EFAULT;

			return 0;
		}
		case VIDIOCSPICT:
		{
			struct video_picture p;

			if (copy_from_user(&p, arg, sizeof(p)))
				return -EFAULT;

			return 0;
		}
		case VIDIOCSWIN:
		{
			struct video_window vw;

			if (copy_from_user(&vw, arg, sizeof(vw)))
				return -EFAULT;
			if (vw.flags)
				return -EINVAL;
			if (vw.clipcount)
				return -EINVAL;
			if (vw.height != 288)
				return -EINVAL;
			if (vw.width != 352)
				return -EINVAL;

			wintv->compress = 0;

			return 0;
		}
		case VIDIOCGWIN:
		{
			struct video_window vw;

			vw.x = 0;
			vw.y = 0;
			vw.width = 352;
			vw.height = 288;
			vw.chromakey = 0;
			vw.flags = 30;		/* 30 fps */

			if (copy_to_user(arg, &vw, sizeof(vw)))
				return -EFAULT;

			return 0;
		}
		case VIDIOCGMBUF:
		{
			struct video_mbuf vm;

			memset(&vm, 0, sizeof(vm));
			vm.size = MAX_FRAME_SIZE * 2;
			vm.frames = 2;
			vm.offsets[0] = 0;
			vm.offsets[1] = MAX_FRAME_SIZE;

			if (copy_to_user((void *)arg, (void *)&vm, sizeof(vm)))
				return -EFAULT;

			return 0;
		}
		case VIDIOCMCAPTURE:
		{
			struct video_mmap vm;

			if (copy_from_user((void *)&vm, (void *)arg, sizeof(vm)))
				return -EFAULT;

			if (debug >= 1)
				printk(KERN_DEBUG "frame: %d, size: %dx%d, format: %d\n",
					vm.frame, vm.width, vm.height, vm.format);

			if (vm.format != VIDEO_PALETTE_RGB24)
				return -EINVAL;

			if ((vm.frame != 0) && (vm.frame != 1))
				return -EINVAL;

			if (wintv->frame[vm.frame].grabstate == FRAME_GRABBING)
				return -EBUSY;

			/* Don't compress if the size changed */
			if ((wintv->frame[vm.frame].width != vm.width) ||
			    (wintv->frame[vm.frame].height != vm.height))
				wintv->compress = 0;

			wintv->frame[vm.frame].width = vm.width;
			wintv->frame[vm.frame].height = vm.height;

			/* Mark it as ready */
			wintv->frame[vm.frame].grabstate = FRAME_READY;

			return wintv_new_frame(wintv, vm.frame);
		}
		case VIDIOCSYNC:
		{
			int frame;

			if (copy_from_user((void *)&frame, arg, sizeof(int)))
				return -EFAULT;

			if (debug >= 1)
				printk(KERN_DEBUG "wintv: syncing to frame %d\n", frame);

			switch (wintv->frame[frame].grabstate) {
			case FRAME_UNUSED:
				return -EINVAL;
			case FRAME_READY:
			case FRAME_GRABBING:
			case FRAME_ERROR:
redo:
				if (!wintv->dev)
					return -EIO;

				do {
					interruptible_sleep_on(&wintv->frame[frame].wq);
					if (signal_pending(current))
						return -EINTR;
				} while (wintv->frame[frame].grabstate == FRAME_GRABBING);

				if (wintv->frame[frame].grabstate == FRAME_ERROR) {
					int ret;

					if ((ret = wintv_new_frame(wintv, frame)) < 0)
						return ret;
					goto redo;
				}
			case FRAME_DONE:
				wintv->frame[frame].grabstate = FRAME_UNUSED;
				break;
			}

			wintv->frame[frame].grabstate = FRAME_UNUSED;

			return 0;
		}
		case VIDIOCGFBUF:
		{
			struct video_buffer vb;

			memset(&vb, 0, sizeof(vb));
			vb.base = NULL;	/* frame buffer not supported, not used */

			if (copy_to_user((void *)arg, (void *)&vb, sizeof(vb)))
				return -EFAULT;

 			return 0;
 		}
		case VIDIOCKEY:
			return 0;
		case VIDIOCCAPTURE:
			return -EINVAL;
		case VIDIOCSFBUF:
			return -EINVAL;
		case VIDIOCGTUNER:
		case VIDIOCSTUNER:
			return -EINVAL;
		case VIDIOCGFREQ:
		case VIDIOCSFREQ:
			return -EINVAL;
		case VIDIOCGAUDIO:
		case VIDIOCSAUDIO:
			return -EINVAL;
		default:
			return -ENOIOCTLCMD;
	}
	return 0;
}

static long wintv_read(struct video_device *dev, char *buf, unsigned long count, int noblock)
{
	struct usb_wintv *wintv = (struct usb_wintv *)dev;
	int frmx = -1;
	volatile struct wintv_frame *frame;

	if (debug >= 1)
		printk(KERN_DEBUG "wintv_read: %ld bytes, noblock=%d\n", count, noblock);

	if (!dev || !buf)
		return -EFAULT;

	if (!wintv->dev)
		return -EIO;

	/* See if a frame is completed, then use it. */
	if (wintv->frame[0].grabstate >= FRAME_DONE)	/* _DONE or _ERROR */
		frmx = 0;
	else if (wintv->frame[1].grabstate >= FRAME_DONE)/* _DONE or _ERROR */
		frmx = 1;

	if (noblock && (frmx == -1))
		return -EAGAIN;

	/* If no FRAME_DONE, look for a FRAME_GRABBING state. */
	/* See if a frame is in process (grabbing), then use it. */
	if (frmx == -1) {
		if (wintv->frame[0].grabstate == FRAME_GRABBING)
			frmx = 0;
		else if (wintv->frame[1].grabstate == FRAME_GRABBING)
			frmx = 1;
	}

	/* If no frame is active, start one. */
	if (frmx == -1)
		wintv_new_frame(wintv, frmx = 0);

	frame = &wintv->frame[frmx];

restart:
	if (!wintv->dev)
		return -EIO;

	while (frame->grabstate == FRAME_GRABBING) {
		interruptible_sleep_on(&frame->wq);
		if (signal_pending(current))
			return -EINTR;
	}

	if (frame->grabstate == FRAME_ERROR) {
		frame->bytes_read = 0;
printk("wintv_read: errored frame %d\n", wintv->curframe);
		if (wintv_new_frame(wintv, frmx))
			printk(KERN_ERR "wintv_read: wintv_new_frame error\n");
		goto restart;
	}

	if (debug >= 1)
		printk(KERN_DEBUG "wintv_read: frmx=%d, bytes_read=%ld, scanlength=%ld\n",
			frmx, frame->bytes_read, frame->scanlength);

	/* copy bytes to user space; we allow for partials reads */
	if ((count + frame->bytes_read) > frame->scanlength)
		count = frame->scanlength - frame->bytes_read;

	if (copy_to_user(buf, frame->data + frame->bytes_read, count))
		return -EFAULT;

	frame->bytes_read += count;
	if (debug >= 1)
		printk(KERN_DEBUG "wintv_read: {copy} count used=%ld, new bytes_read=%ld\n",
			count, frame->bytes_read);

	if (frame->bytes_read >= frame->scanlength) { /* All data has been read */
		frame->bytes_read = 0;

		/* Mark it as available to be used again. */
		wintv->frame[frmx].grabstate = FRAME_UNUSED;
		if (wintv_new_frame(wintv, frmx ? 0 : 1))
			printk(KERN_ERR "wintv_read: wintv_new_frame returned error\n");
	}

	return count;
}

static int wintv_mmap(struct video_device *dev, const char *adr, unsigned long size)
{
	struct usb_wintv *wintv = (struct usb_wintv *)dev;
	unsigned long start = (unsigned long)adr;
	unsigned long page, pos;

	if (!wintv->dev)
		return -EIO;

	if (size > (((2 * MAX_FRAME_SIZE) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1)))
		return -EINVAL;

	pos = (unsigned long)wintv->fbuf;
	while (size > 0) {
		page = kvirt_to_pa(pos);
		if (remap_page_range(start, page, PAGE_SIZE, PAGE_SHARED))
			return -EAGAIN;

		start += PAGE_SIZE;
		pos += PAGE_SIZE;
		if (size > PAGE_SIZE)
			size -= PAGE_SIZE;
		else
			size = 0;
	}

	return 0;
}

static struct video_device wintv_template = {
	"WinTV USB",
	VID_TYPE_CAPTURE,
	VID_HARDWARE_WINTV,
	wintv_open,
	wintv_close,
	wintv_read,
	wintv_write,
	NULL,
	wintv_ioctl,
	wintv_mmap,
	wintv_init_done,
	NULL,
	0,
	0
};

static int usb_wintv_configure(struct usb_wintv *wintv)
{
	struct usb_device *dev = wintv->dev;
	unsigned char version[4];

	/* Set altsetting 0 */
	if (usb_set_interface(dev, wintv->iface, 0) < 0) {
		printk(KERN_ERR "usb_set_interface error\n");
		return -EBUSY;
	}

/*	if (usb_wintv_get_version(dev, version) < 0) {
		printk(KERN_ERR "wintv_get_version error\n");
		return -EBUSY;
	}
*/
	if (debug >= 1)
		printk(KERN_DEBUG "wintv: Firmware v%d.%d, VC Hardware v%d.%d\n",
			version[0], version[1], version[2], version[3]);

	memcpy(&wintv->vdev, &wintv_template, sizeof(wintv_template));

	init_waitqueue_head(&wintv->frame[0].wq);
	init_waitqueue_head(&wintv->frame[1].wq);

	if (video_register_device(&wintv->vdev, VFL_TYPE_GRABBER) == -1) {
		printk(KERN_ERR "video_register_device failed\n");
		return -EBUSY;
	}

	if (usb_wintv_goto_hi_power(dev) < 0) {
		printk(KERN_ERR "wintv_goto_hi_power error\n");
		goto error;
	}

/*	if (usb_wintv_get_vp_version(dev, version) < 0) {
		printk(KERN_ERR "wintv_get_vp_version error\n");
		goto error;
	}
*/
	if (debug >= 1) {
		printk(KERN_DEBUG "wintv: VP v%d rev %d\n", version[0], version[1]);
		printk(KERN_DEBUG "wintv: Camera Head ID %04X\n", (version[3] << 8) + version[2]);
	}

	/* Turn on continuous grab */
/*	if (usb_wintv_set_grab_mode(dev, 1) < 0) {
		printk(KERN_ERR "wintv_set_grab_mode error\n");
		goto error;
	}
*/
	printk(KERN_DEBUG "wintv: VP Version %d\n", usb_wintv_get_vp_version(dev,1));

	/* Set up the sensor to be 30fps */
	if (usb_wintv_set_sensor_fps(dev, 1, 0) < 0) {
		printk(KERN_ERR "wintv_set_sensor_fps error\n");
		goto error;
	}

	/* Set video into CIF mode, and order into YUYV mode */
	if (usb_wintv_set_format(dev, FORMAT_CIF, FORMAT_422,
			FORMAT_YUYV) < 0) {
		printk(KERN_ERR "wintv_set_format error\n");
		goto error;
	}

	/* Turn off compression */
	if (usb_wintv_set_compression(dev, COMP_DISABLED, DONT_DECIMATE) < 0) {
		printk(KERN_ERR "wintv_set_compression error\n");
		goto error;
	}

	wintv->compress = 0;

	return 0;

error:
	video_unregister_device(&wintv->vdev);
	usb_driver_release_interface(&wintv_driver,
		&dev->actconfig->interface[0]);

	kfree(wintv);

	return -EBUSY;
}

static void * wintv_probe(struct usb_device *dev, unsigned int ifnum)
{
	struct usb_interface_descriptor *interface;
	struct usb_wintv *wintv;

        printk(KERN_INFO "USB WinTV_Probe was called\n"); 
	/* We don't handle multi-config cameras */
	if (dev->descriptor.bNumConfigurations != 1)
		return NULL;

	interface = &dev->actconfig->interface[ifnum].altsetting[0];

	/* Is it a WinTV? */
	if (dev->descriptor.idVendor != 0x0573)
		return NULL;
	if (dev->descriptor.idProduct != 0x4d11)
		return NULL;

	/* Checking vendor/product should be enough, but what the hell */
/*	if (interface->bInterfaceClass != 0xFF)
		return NULL;
	if (interface->bInterfaceSubClass != 0x00)
		return NULL;
*/
	/* We found a WinTV */
	printk(KERN_INFO "USB WinTV found\n");

	if ((wintv = kmalloc(sizeof(*wintv), GFP_KERNEL)) == NULL) {
		printk(KERN_ERR "couldn't kmalloc wintv struct\n");
		return NULL;
	}

	memset(wintv, 0, sizeof(*wintv));

	wintv->dev = dev;
	wintv->iface = interface->bInterfaceNumber;

	if (!usb_wintv_configure(wintv)) {
		wintv->user=0; 
		init_MUTEX(&wintv->lock);	/* to 1 == available */

		return wintv;
	} else
		return NULL;
}

static void wintv_disconnect(struct usb_device *dev, void *ptr)
{
	struct usb_wintv *wintv = (struct usb_wintv *) ptr;

	/* We don't want people trying to open up the device */
	if (!wintv->user)
		video_unregister_device(&wintv->vdev);

	usb_driver_release_interface(&wintv_driver,
		&wintv->dev->actconfig->interface[0]);

	wintv->dev = NULL;
	wintv->frame[0].grabstate = FRAME_ERROR;
	wintv->frame[1].grabstate = FRAME_ERROR;
	wintv->curframe = -1;

	/* This will cause the process to request another frame. */
	if (waitqueue_active(&wintv->frame[0].wq))
		wake_up_interruptible(&wintv->frame[0].wq);

	if (waitqueue_active(&wintv->frame[1].wq))
		wake_up_interruptible(&wintv->frame[1].wq);

	wintv->streaming = 0;

	/* Unschedule all of the iso td's */
	if (wintv->sbuf[1].urb) {
		wintv->sbuf[1].urb->next = NULL;
		usb_unlink_urb(wintv->sbuf[1].urb);
		usb_free_urb(wintv->sbuf[1].urb);
		wintv->sbuf[1].urb = NULL;
	}
	if (wintv->sbuf[0].urb) {
		wintv->sbuf[0].urb->next = NULL;
		usb_unlink_urb(wintv->sbuf[0].urb);
		usb_free_urb(wintv->sbuf[0].urb);
		wintv->sbuf[0].urb = NULL;
	}

	/* Free the memory */
	if (!wintv->user)
		kfree(wintv);
}

static struct usb_driver wintv_driver = {
	"wintv",
	wintv_probe,
	wintv_disconnect,
	{ NULL, NULL }
};

int usb_wintv_init(void)
{
	return usb_register(&wintv_driver);
}

void usb_wintv_cleanup(void)
{
	usb_deregister(&wintv_driver);
}

#ifdef MODULE
int init_module(void)
{
	return usb_wintv_init();
}

void cleanup_module(void)
{
	usb_wintv_cleanup();
}
#endif
