/*
 * PWCBSD - Philips USB webcam driver for FreeBSD 5.4 and higher
 *
 *  Copyright (C) 2006 Raaf 
 *
 * Based on the Linux pwc driver.
 *  
 *  Copyright (C) 1999-2003 Nemosoft Unv.
 *  Copyright (C) 2004-2006 Luc Saillard
 *  
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301  USA
 */

#include "pwc.h"
#include "pwc-dec1.h"
#include "pwc-dec23.h"

static void pwc_isoc_handler(usbd_xfer_handle xfer, usbd_private_handle addr,usbd_status status);
static void pwc_reset_buffers(struct pwc_softc *sc);
static void pwc_free_buffers(struct pwc_softc *sc, int detach);

#ifdef USB_DEBUG
int     pwcdebug = 0;
SYSCTL_NODE(_hw_usb, OID_AUTO, pwc, CTLFLAG_RW, 0, "USB pwc");
SYSCTL_INT(_hw_usb_pwc, OID_AUTO, debug, CTLFLAG_RW,&pwcdebug, 0, "pwc debug level");
#endif
#define MAX_ISOC_ERRORS	20

d_open_t  pwc_open;
d_close_t pwc_close;
d_read_t  pwc_read;
d_ioctl_t pwc_ioctl;
d_mmap_t  pwc_mmap;
d_poll_t  pwc_poll;

struct cdevsw pwc_cdevsw = {
	.d_version	= D_VERSION,
	.d_flags	= D_NEEDGIANT,
	.d_open		= pwc_open,
	.d_close	= pwc_close,
	.d_read		= pwc_read,
	.d_ioctl	= pwc_ioctl,
	.d_poll		= pwc_poll,
	.d_mmap		= pwc_mmap,
	.d_name		= "pwc",
};
					
struct pwc_info {
	struct usb_devno	devno;
	int			type;
	char			*name;
};
static const struct pwc_info pwc_devs[] = {
	{{ 0x0471, 0x0302 }, 645, "Philips PCA645VC" }, 
	{{ 0x0471, 0x0303 }, 646, "Philips PCA646VC" },
	{{ 0x0471, 0x0304 }, 646, "Askey VC010 type 2" },
	{{ 0x0471, 0x0307 }, 675, "Philips PCVC675K (Vesta)" },
	{{ 0x0471, 0x0308 }, 680, "Philips PCVC680K (Vesta Pro)" },
	{{ 0x0471, 0x030C }, 690, "Philips PCVC690K (Vesta Pro Scan)" },
	{{ 0x0471, 0x0310 }, 730, "Philips PCVC730K (ToUCam Fun)/PCVC830 (ToUCam II)" },
	{{ 0x0471, 0x0311 }, 740, "Philips PCVC740K (ToUCam Pro)/PCVC840 (ToUCam II)" },
	{{ 0x0471, 0x0312 }, 750, "Philips PCVC750K (ToUCam Pro Scan)" },
	{{ 0x0471, 0x0313 }, 720, "Philips PCVC720K/40 (ToUCam XS)" },
	{{ 0x0471, 0x0329 }, 740, "Philips SPC900NC" },
	{{ 0x069A, 0x0001 }, 645, "Askey VC010 type 1" }, 
	{{ 0x046D, 0x08B0 }, 740, "Logitech QuickCam Pro 3000" }, 
	{{ 0x046D, 0x08B1 }, 740, "Logitech QuickCam Notebook Pro" }, 
	{{ 0x046D, 0x08B2 }, 740, "Logitech QuickCam Pro 4000" }, 
	{{ 0x046D, 0x08B3 }, 740, "Logitech QuickCam Zoom" }, 
	{{ 0x046D, 0x08B4 }, 740, "Logitech QuickCam Zoom (new model)" }, 
	{{ 0x046D, 0x08B5 }, 740, "Logitech QuickCam Orbit/Sphere" }, 
	{{ 0x046D, 0x08B6 }, 730, "Logitech QuickCam (reserved ID)" }, 
	{{ 0x046D, 0x08B7 }, 730, "Logitech QuickCam (reserved ID)" }, 
	{{ 0x046D, 0x08B8 }, 730, "Logitech QuickCam (reserved ID)" }, 
	{{ 0x055D, 0x9000 }, 675, "Samsung MPC-C10" }, 
	{{ 0x055D, 0x9001 }, 675, "Samsung MPC-C30" },
	{{ 0x055D, 0x9002 }, 740, "Samsung SNC-35E" },
	{{ 0x041E, 0x400C }, 730, "Creative Labs Webcam 5" }, 
	{{ 0x041E, 0x4011 }, 740, "Creative Labs Webcam Pro Ex" }, 
	{{ 0x04CC, 0x8116 }, 730, "Sotec Afina Eye"  }, 
	{{ 0x06BE, 0x8116 }, 750, "AME Co. Afina Eye" }, 
	{{ 0x0d81, 0x1910 }, 740, "Visionite VCS-UC300" }, 
	{{ 0x0d81, 0x1900 }, 730, "Visionite VCS-UM100" },
};
#define pwc_lookup(v, p) ((const struct pwc_info *)usb_lookup(pwc_devs, v, p))
#define PWCUNIT(n) (minor(n))

static device_probe_t pwc_match;
static device_attach_t pwc_attach;
static device_detach_t pwc_detach;

static devclass_t pwc_devclass;

static device_method_t pwc_methods[] = {
	DEVMETHOD(device_probe, pwc_match),
	DEVMETHOD(device_attach, pwc_attach),
	DEVMETHOD(device_detach, pwc_detach),
	{0,0},
};

static driver_t pwc_driver = {
	"pwc",
	pwc_methods,
	sizeof(struct pwc_softc)
};

MODULE_DEPEND(pwc, usb, 1, 1, 1);

static int
pwc_match(device_t self)
{
        struct usb_attach_arg *uaa = device_get_ivars(self);
	usb_interface_descriptor_t *id;

	Trace(TRACE_PROBE,"pwc_match: vendor=0x%x, product=0x%x release=%04x\n",uaa->vendor, uaa->product,uaa->release);
	
	if(pwc_lookup(uaa->vendor, uaa->product) == NULL)
		return UMATCH_NONE;

	/* Driver loaded when device was already plugged in, we have to claim all interfaces or get none... */
	if(uaa->usegeneric)
		return  UMATCH_VENDOR_PRODUCT;
	
	/* We don't want all interfaces */
	if(uaa->iface == NULL) 
		return UMATCH_NONE;
	
	id = usbd_get_interface_descriptor(uaa->iface);
	if(id == NULL) {
		printf("pwc: failed to get interface descriptor\n");
		return UMATCH_NONE;
	}
	
        Trace(TRACE_PROBE,"pwc_match: iface=%d\n",id->bInterfaceNumber);
	
	/* Interface 0 is the video interface
	 * Interface 1 is supposed to be audiocontrol
	 * Interface 2 is supposed to be audio
	 */
	if(id->bInterfaceNumber != 0)
		return UMATCH_NONE;
	
	return  UMATCH_VENDOR_PRODUCT;
			
}

static int
pwc_attach(device_t self)
{
	struct pwc_softc *sc = device_get_softc(self);
	struct usb_attach_arg *uaa = device_get_ivars(self);
	char devinfo[1024];
	const char *tmpstr;
	const struct pwc_info *info;
	char *sizenames[PSZ_MAX] = { "sqcif", "qsif", "qcif", "sif", "cif", "vga" };
	int i, err;

	info = pwc_lookup(uaa->vendor, uaa->product);
	if(info == NULL) {
		printf("%s: attach error vendor/product mismatch (vendor=0x%x product=0x%x)\n",
			device_get_nameunit(sc->sc_dev),uaa->vendor,uaa->product);
		return ENXIO;
	}

	usbd_devinfo(uaa->device, 0, devinfo);
	sc->sc_dev = self;
	device_set_desc_copy(self, devinfo);
	device_printf(self, "%s\n", devinfo);

	err = usbd_device2interface_handle(uaa->device,0,&sc->sc_iface);
	if(err) {
		printf("%s: failed to get interface handle\n",device_get_nameunit(sc->sc_dev));
		return ENXIO;
	}

	sc->udev = uaa->device;
	sc->type = info->type;
	sc->name = info->name;
	sc->release = uaa->release;
	sc->power_save = 0;
	
	strcpy(sc->serial,"000-000-000-000");
	
	if(uaa->vendor == 0x046D) {
		if(uaa->product == 0x08B4) {
			sc->power_save = 1;
		}
		else if(uaa->product == 0x08B5) {
		
			/* Logitech QuickCam Orbit */
			sc->features |= FEATURE_MOTOR_PANTILT;
			sc->angle_range.pan_min  = -7000;
			sc->angle_range.pan_max  =  7000;
			sc->angle_range.tilt_min = -3000;
			sc->angle_range.tilt_max =  2500;
		}
	}

	mtx_init(&sc->ptrlock,device_get_nameunit(sc->sc_dev),NULL,MTX_DEF);

	tmpstr = "video";
	resource_string_value("pwc",device_get_unit(self),"devname",&tmpstr);
	sc->sc_dev_t = make_dev(&pwc_cdevsw, device_get_unit(self),UID_ROOT, GID_OPERATOR,
				0666, "%s%d",tmpstr,device_get_unit(self));
	
	resource_int_value("pwc",device_get_unit(self),"power_save",&sc->power_save);

	sc->stats = 0;
	resource_int_value("pwc",device_get_unit(self),"stats",&sc->stats);

	sc->led_on = 100;
	resource_int_value("pwc",device_get_unit(self),"led_on",&sc->led_on);
	if(sc->led_on < 0)
		sc->led_on = 0;
	
	sc->led_off = 0;
	resource_int_value("pwc",device_get_unit(self),"led_off",&sc->led_off);
	if(sc->led_off < 0)
		sc->led_off = 0;
	
	sc->vframes = 5;
	resource_int_value("pwc",device_get_unit(self),"fps",&sc->vframes);
	if(sc->vframes < 4 || sc->vframes > 30)
		sc->vframes = 5;

#ifdef USE_MMAP
	sc->pwc_mbufs = 2;	/* Default number of mmap() buffers */
#else
	sc->pwc_mbufs = 1;
#endif
	resource_int_value("pwc",device_get_unit(self),"mbufs",&sc->pwc_mbufs);
	if(sc->pwc_mbufs < 1 || sc->pwc_mbufs > MAX_IMAGES)
		sc->pwc_mbufs = 1;
	
	sc->pwc_fbufs = 3;	/* Default number of frame buffers */
	resource_int_value("pwc",device_get_unit(self),"fbufs",&sc->pwc_fbufs);
	if(sc->pwc_fbufs < 2 || sc->pwc_fbufs > MAX_FRAMES)
		sc->pwc_fbufs = 2;
	
	sc->vcompression = 2;	/* 0..3 = uncompressed..high */
	resource_int_value("pwc",device_get_unit(self),"compression",&sc->vcompression);
	if(sc->vcompression > 3)
		sc->vcompression = 3;
			
	sc->vsize = PSZ_SIF;
	tmpstr = NULL;
	resource_string_value("pwc",device_get_unit(self),"size",&tmpstr);
	if(tmpstr != NULL) {
		for (i = 0; i < PSZ_MAX; i++) {
			if (!strcmp(sizenames[i],tmpstr)) { /* Found! */
				sc->vsize = i;
				break;
			}
		}
	}
	if(sc->vsize == PSZ_VGA && sc->vframes > 15)
		sc->vframes = 15;
		
	sc->pwc_pad = 1;
	resource_int_value("pwc",device_get_unit(self),"pad",&sc->pwc_pad);

	pwc_construct(sc);
	printf("%s: %s USB webcam\n",device_get_nameunit(sc->sc_dev),sc->name);

	if(pwc_get_cmos_sensor(sc, &i) >= 0) {
	
		switch(i) {
		case 0x00:  tmpstr = "Hyundai CMOS sensor"; break;
		case 0x20:  tmpstr = "Sony CCD sensor + TDA8787"; break;
		case 0x2E:  tmpstr = "Sony CCD sensor + Exas 98L59"; break;
		case 0x2F:  tmpstr = "Sony CCD sensor + ADI 9804"; break;
		case 0x30:  tmpstr = "Sharp CCD sensor + TDA8787"; break;
		case 0x3E:  tmpstr = "Sharp CCD sensor + Exas 98L59"; break;
		case 0x3F:  tmpstr = "Sharp CCD sensor + ADI 9804"; break;
		case 0x40:  tmpstr = "UPA 1021 sensor"; break;
		case 0x100: tmpstr = "VGA sensor"; break;
		case 0x101: tmpstr = "PAL MR sensor"; break;
		default:    tmpstr = "unknown type of sensor"; break;
		}
		
		printf("%s: This camera is equipped with a %s (%d)\n",device_get_nameunit(sc->sc_dev),tmpstr, i);
	}
	
	pwc_set_leds(sc, 0, 0);

	if(sc->power_save)
		pwc_camera_power(sc, 0);

	usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->udev,sc->sc_dev);
	return 0;
}

static int
pwc_detach(device_t self)
{
	struct pwc_softc *sc = device_get_softc(self);

	Trace(TRACE_PROBE,"pwc_detach: sc=%p\n",sc);
	
	if(sc->sc_videopipe != NULL) {
		usbd_abort_pipe(sc->sc_videopipe);
		usbd_close_pipe(sc->sc_videopipe);
		sc->sc_videopipe = NULL;
	}

	sc->error_status = EPIPE;

	if(sc->vopen) {
		if(sc->state & PWC_ASLEEP) {
			wakeup(sc);
		}
		if(sc->state & PWC_POLL) {
			sc->state &= ~PWC_POLL;
			selwakeuppri(&sc->rsel,PZERO);
		}
		printf("%s: Disconnected while webcam is in use!\n",device_get_nameunit(sc->sc_dev));
	}
	
	if(sc->sc_dev_t != NULL)
		destroy_dev(sc->sc_dev_t);
	
	mtx_destroy(&sc->ptrlock);
	pwc_free_buffers(sc,1);
	usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->udev,sc->sc_dev);
	return 0;
}

int
pwc_open(struct cdev *dev, int flag, int mode, struct thread *p)
{
	struct pwc_softc *sc;
	int i,err;
	int unit = PWCUNIT(dev);
	
	sc = devclass_get_softc(pwc_devclass,unit);
	if(sc == NULL)
		return ENXIO;
	
	Trace(TRACE_OPEN,"pwc_open: flag=%d, mode=%d, unit=%d\n",flag, mode, unit);

	if(sc->error_status == EPIPE)
		return sc->error_status;

	mtx_lock(&sc->ptrlock);
	if (sc->vopen) {
		mtx_unlock(&sc->ptrlock);
		return EBUSY;
	}
	sc->vopen = 1;
	mtx_unlock(&sc->ptrlock);

	/* Turn on camera */
	if (sc->power_save) {
		err = pwc_camera_power(sc, 1);
		if (err < 0)
			printf("%s: Failed to restore power to the camera! (%d)\n", device_get_nameunit(sc->sc_dev),-err);
	}

	pwc_set_leds(sc, sc->led_on, sc->led_off);
	err = ENOMEM;

	/* Allocate frame buffer structure */
	sc->fbuf = malloc(sc->pwc_fbufs * sizeof(struct pwc_frame_buf), M_USBDEV, M_WAITOK);
	if (sc->fbuf == NULL) {
		printf("%s: Failed to allocate frame buffer structure.\n",device_get_nameunit(sc->sc_dev));
		goto bad;
	}
	memset(sc->fbuf, 0, sc->pwc_fbufs * sizeof(struct pwc_frame_buf));
	
	/* create frame buffers, and make circular ring */
	for (i = 0; i < sc->pwc_fbufs; i++) {
			
		sc->fbuf[i].data = malloc(PWC_FRAME_SIZE,M_USBDEV,M_WAITOK);
		if (sc->fbuf[i].data == NULL) {
			printf("%s: Failed to allocate frame buffer\n", device_get_nameunit(sc->sc_dev));
			goto bad;
		}
		memset(sc->fbuf[i].data, 128, PWC_FRAME_SIZE);
	}
	/* Allocate decompressor table space */
	if(sc->type == 645 || sc->type == 646) 
		sc->decompress_data = malloc(sizeof(struct pwc_dec23_private), M_USBDEV, M_WAITOK);/* TODO & FIXME */
	else
		sc->decompress_data = malloc(sizeof(struct pwc_dec23_private), M_USBDEV, M_WAITOK);/* Timon & Kiara */

	if(sc->decompress_data == NULL) {
		printf("%s: Failed to allocate decompress table.\n",device_get_nameunit(sc->sc_dev));
		goto bad;
	}
	
	/* Allocate image buffer */
	if(sc->image_data == NULL) {
		sc->image_data = malloc(sc->pwc_mbufs * round_page(sc->len_per_image), M_USBDEV, M_WAITOK);
		if(sc->image_data == NULL) {
			printf("%s: Failed to allocate image buffers\n",device_get_nameunit(sc->sc_dev));
			goto bad;
		}
	}
	for(i = 0; i < sc->pwc_mbufs; i++)	{
		sc->images[i].bufmem = ((char*)sc->image_data) + i * round_page(sc->len_per_image);
		sc->images[i].vma_use_count = 0;
	}
	for (; i < MAX_IMAGES; i++)
		sc->images[i].bufmem = NULL;
			
	for (i = 0; i < sc->pwc_mbufs; i++)
		sc->image_used[i] = 0;

	/* Allocate iso transfers */
	for (i = 0; i < MAX_ISO_BUFS; i++) {
		sc->sbuf[i].sc = sc;
		sc->sbuf[i].xfer = usbd_alloc_xfer(sc->udev); 
		
		if(sc->sbuf[i].xfer == NULL) {
			printf("%s: Failed to allocate transfer\n",device_get_nameunit(sc->sc_dev));
			goto bad;
		}
		
		sc->sbuf[i].data = usbd_alloc_buffer(sc->sbuf[i].xfer, ISO_BUFFER_SIZE);
		if(sc->sbuf[i].data == NULL) {
			printf("%s: Failed to allocate transferbuffer\n",device_get_nameunit(sc->sc_dev));
			goto bad;
		}
	}
		
	sc->state = 0;
	sc->vframe_count = 0;
	sc->vframes_dumped = 0;
	sc->vframes_error = 0;
	sc->visoc_errors = 0;
	sc->error_status = 0;
	sc->vsnapshot = 0;
	
	/* set fallback videomode */
	i = sc->vframes;
	sc->vframes = 10;
	
	if(sc->type > 730) {
		sc->view.x = 160;
		sc->view.y = 120;
	}
	else {
		sc->view.x = 176;
		sc->view.y = 144;
	}
		
	/* Try the last used video */
	sc->state |= PWC_INIT;	
	err = -pwc_try_video_mode(sc,pwc_image_sizes[sc->vsize].x,pwc_image_sizes[sc->vsize].y,i,sc->vcompression,0);
	sc->state &= ~PWC_INIT;
	
	if(err == 0)
		return 0;

bad:
	pwc_free_buffers(sc,0);
	sc->vopen = 0;
	return err;
}

/* Important: close doesn't get called after a detach */

int
pwc_close(struct cdev *dev, int flag, int mode, struct thread *p)
{
	struct pwc_softc *sc;
	int unit = PWCUNIT(dev);
	
	sc = devclass_get_softc(pwc_devclass, unit);
	Trace(TRACE_OPEN,"pwc_close: flag=%d, mode=%d, unit=%d\n", flag, mode, unit);

	/* Dump statistics, but only if a reasonable amount of frames were
	   processed (to prevent endless log-entries in case of snap-shot
	   programs)
	 */
	if(sc->vframe_count > 20 && sc->stats) {
		printf("%s: %d frames received, dumped %d frames, %d frames with errors.\n"
			,device_get_nameunit(sc->sc_dev),sc->vframe_count, sc->vframes_dumped, sc->vframes_error);
	}

	if(sc->type == 645 || sc->type == 646)
		pwc_dec1_exit();
	else
		pwc_dec23_exit();	/* Timon & Kiara */
	
	if(sc->sc_videopipe != NULL) {
		usbd_abort_pipe(sc->sc_videopipe);
		usbd_close_pipe(sc->sc_videopipe);
		sc->sc_videopipe = NULL;
	}

	pwc_free_buffers(sc,0);
	
	/* Turn off LEDS and power down camera, but only when not unplugged */
	if(sc->error_status != EPIPE) {
		
		usbd_set_interface(sc->sc_iface, 0);
		pwc_set_leds(sc,0,0);
		
		if(sc->power_save) {
			if(pwc_camera_power(sc, 0) < 0)
				printf("%s: Failed to power down the camera\n", device_get_nameunit(sc->sc_dev));
		}
	}
	
        sc->vopen = 0;
        return 0;
}

int
pwc_read(struct cdev *dev, struct uio *uio, int flag)
{
	struct pwc_softc *sc;
	int unit = PWCUNIT(dev);
        int bytes_to_read;
	int count = uio->uio_resid;
	int err;

	Trace(TRACE_READ,"pwc_read: flag=%d, unit=%d\n", flag, unit);

	sc = devclass_get_softc(pwc_devclass, unit);
	if (sc->error_status)
		return sc->error_status;

	/* In case we're doing partial reads, we don't have to wait for a frame */
	if(sc->image_read_pos == 0) {
		
		while ((err = pwc_handle_frame(sc)) == -EBUSY) {
		
			if(flag & O_NONBLOCK)
				return EWOULDBLOCK;
			
			sc->state |= PWC_ASLEEP;	
			err = tsleep(sc, PZERO | PCATCH, "pwcrd", 0);
			sc->state &= ~PWC_ASLEEP;
			
			if(sc->error_status)
				return sc->error_status;
			else if(err)
				return err;
		}
		if(err < 0)
			return -err;
	}

	if(sc->vpalette == VIDEO_PALETTE_RAW)
		bytes_to_read = sc->frame_size;
	else
 		bytes_to_read = sc->view.size;

	/* copy bytes to user space; we allow for partial reads */
	if(count + sc->image_read_pos > bytes_to_read)
		count = bytes_to_read - sc->image_read_pos;
	
	Trace(TRACE_READ_VERBOSE, "pwc_read: wants: %d bytes, reading: %d bytes\n",uio->uio_resid,count);

	err = uiomove(sc->images[sc->fill_image].bufmem + sc->image_read_pos,count,uio);
	if(err)
		return err;
	
	sc->image_read_pos += count;
	if(sc->image_read_pos >= bytes_to_read) { /* All data has been read */
		sc->image_read_pos = 0;
		pwc_next_image(sc);
	}
	
	return 0;
}

int
pwc_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *p)
{
	struct pwc_softc *sc;
	int unit = PWCUNIT(dev);

	sc = devclass_get_softc(pwc_devclass, unit);
	if (sc->error_status)
		return sc->error_status;
	
	return -pwc_video_do_ioctl(sc,cmd,addr,unit);
}

int
pwc_poll(struct cdev *dev, int events, struct thread *p)
{
	struct pwc_softc *sc;
	int unit = PWCUNIT(dev);
	int revents = 0;
	
	sc = devclass_get_softc(pwc_devclass, unit);
	if(sc->error_status)
		return sc->error_status;

	if (events & (POLLIN | POLLRDNORM)) {
		mtx_lock(&sc->ptrlock);
		if(sc->full_frames == NULL) {
			sc->state |= PWC_POLL;
			selrecord(p,&sc->rsel);
			Trace(TRACE_READ,"pwc_poll: will block\n");
		}
		else {
			revents |= events & (POLLIN | POLLRDNORM);
			Trace(TRACE_READ,"pwc_poll: not blocking\n");
		}
		mtx_unlock(&sc->ptrlock);
	}
	if (events & (POLLOUT | POLLWRNORM)) {
		/* write is not allowed, so no point blocking on it */
		revents |= events & (POLLOUT | POLLWRNORM);
	}
	return revents;
}

int
pwc_mmap(struct cdev *dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot)
{
#ifdef USE_MMAP
	struct pwc_softc *sc;
	int unit = PWCUNIT(dev);

	sc = devclass_get_softc(pwc_devclass, unit);
	if(sc == NULL)
		return ENXIO;
	
	if (sc->error_status)
		return sc->error_status;

	if(nprot & PROT_EXEC || sc->image_data == NULL || offset < 0 || offset >= sc->pwc_mbufs * round_page(sc->len_per_image))
		return -1;

	*paddr = vtophys(((char*)sc->image_data) + offset);
	Trace(TRACE_MEMORY,"pwc_mmap: phys: 0x%08x offset: 0x%08x\n",*paddr,offset);
	return 0;
#else
	return ENXIO;
#endif
}

int
pwc_try_video_mode(struct pwc_softc *sc, int width, int height, int new_fps, int new_compression, int new_snapshot)
{
	usb_endpoint_descriptor_t *edesc = NULL;
	u_int8_t nendpt;
	int i, j, err, ret;

	if(sc->sc_videopipe != NULL) {
		usbd_abort_pipe(sc->sc_videopipe);
		usbd_close_pipe(sc->sc_videopipe);
		sc->sc_videopipe = NULL;
	}
	
	pwc_reset_buffers(sc);
	
	/* Try to set video mode... if that fails fallback to previous mode  */
	ret = pwc_set_video_mode(sc, width, height, new_fps, new_compression, new_snapshot);
	if(ret < 0) {
		Trace(TRACE_FLOW, "pwc_set_video_mode attempt 1 failed.\n");
		err = pwc_set_video_mode(sc,sc->view.x,sc->view.y,sc->vframes,sc->vcompression,sc->vsnapshot);
		if(err < 0) {
		        Trace(TRACE_FLOW, "pwc_set_video_mode attempt 2 failed.\n");
			return err;
		}
	}
	
	sc->drop_frames++; /* try to avoid garbage during switch */
	sc->vsync = 0;

	err = usbd_set_interface(sc->sc_iface,sc->valternate);
	if(err != USBD_NORMAL_COMPLETION) {
		printf("%s: Failed to set alternate interface to: %d (%d)\n",device_get_nameunit(sc->sc_dev),sc->valternate,err);
		return -err;
	}

	err = usbd_endpoint_count(sc->sc_iface, &nendpt);
        if(err != USBD_NORMAL_COMPLETION) {
		printf("%s: Failed to get endpoint count (%d)\n",device_get_nameunit(sc->sc_dev),err);
		return -err;
	}
	for (i = 0; i < nendpt; i++) {
		edesc = usbd_interface2endpoint_descriptor(sc->sc_iface, i);
		if(edesc != NULL && UE_GET_ADDR(edesc->bEndpointAddress) == sc->vendpoint)
			break;
	}
	if(i == nendpt) {
		printf("%s: Failed to find videoendpoint\n",device_get_nameunit(sc->sc_dev));
		return -EINVAL;
	}
	
	sc->vmax_packet_size = UGETW(edesc->wMaxPacketSize);
	if(sc->vmax_packet_size < 0 || sc->vmax_packet_size > ISO_MAX_FRAME_SIZE) {
		printf("%s: Invalid packetsize (%d) for endpoint %d\n",device_get_nameunit(sc->sc_dev),
			sc->vmax_packet_size,edesc->bEndpointAddress);
		return -EINVAL;
	}
		 
	err = usbd_open_pipe(sc->sc_iface,edesc->bEndpointAddress, 0, &sc->sc_videopipe);
	if(err != USBD_NORMAL_COMPLETION) {
		printf("%s: Failed to open videopipe (%d)\n",device_get_nameunit(sc->sc_dev),err);
		return -err;
	}

	for (i = 0; i < MAX_ISO_BUFS; i++) {
		
		for(j = 0; j < ISO_FRAMES_PER_DESC; ++j)
			sc->sbuf[i].sizes[j] = sc->vmax_packet_size;

		usbd_setup_isoc_xfer(sc->sbuf[i].xfer,sc->sc_videopipe,&sc->sbuf[i],sc->sbuf[i].sizes,
					ISO_FRAMES_PER_DESC,USBD_NO_COPY,pwc_isoc_handler);
		
		usbd_transfer(sc->sbuf[i].xfer);
	}
	
	if(sc->state & PWC_INIT)
		return 0;
	
	return ret;
}

static int
pwc_next_fill_frame(struct pwc_softc *sc)
{
	int ret = 0;
	
	mtx_lock(&sc->ptrlock);

	if(sc->fill_frame != NULL) {
		/* append to 'full' list */
		if (sc->full_frames == NULL) {
			sc->full_frames = sc->fill_frame;
			sc->full_frames_tail = sc->full_frames;
		}
		else {
			sc->full_frames_tail->next = sc->fill_frame;
			sc->full_frames_tail = sc->fill_frame;
		}
	}
	if (sc->empty_frames != NULL) {
		/* We have empty frames available. That's easy */
		sc->fill_frame = sc->empty_frames;
		sc->empty_frames = sc->empty_frames->next;
	}
	else {
		/* Hmm. Take it from the full list */
		sc->fill_frame = sc->full_frames;
		sc->full_frames = sc->full_frames->next;
		ret = 1;
	}
	sc->fill_frame->next = NULL;
	mtx_unlock(&sc->ptrlock);
	return ret;
}

static void
pwc_isoc_handler(usbd_xfer_handle xfer, usbd_private_handle addr,usbd_status status)
{
   	struct pwc_iso_buf *req = addr;
	struct pwc_softc *sc = req->sc;
	struct pwc_frame_buf *fbuf;
	unsigned char *fillptr = NULL;
	u_int32_t count;
	int awake = 0;
	int i;
	
	usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL);
	Trace(TRACE_ISOC_VERBOSE, "pwc_isoc_handler: status=%d count=%u\n",status,count);

	if (status == USBD_CANCELLED) {
		Trace(TRACE_ISOC, "pwc_isoc_handler: status = cancelled\n");
		return;
	}

	if(status != USBD_NORMAL_COMPLETION) {

		Trace(TRACE_ISOC, "pwc_isoc_handler called with status: %d\n",status);

		if (++sc->visoc_errors > MAX_ISOC_ERRORS) {
			
			if(sc->error_status != EIO)
				printf("%s: Too many ISOC errors, bailing out.\n",device_get_nameunit(sc->sc_dev));

			sc->error_status = EIO;
			awake = 1;
		}
		goto handler_end; // ugly, but practical
	}

	/* Reset ISOC error counter. We did get here, after all. */
	sc->visoc_errors = 0;

	fbuf = sc->fill_frame;
	fillptr = fbuf->data + fbuf->filled;

	/* vsync stuff */
#define NOCOPY 0 
#define SYNCHUNT 1
#define SYNCHED 2

	/* Compact data */
	for (i = 0; i < ISO_FRAMES_PER_DESC; i++) {

		/* XXX there is no individual framestatus in FreeBSD usbstack
		 * so just assume all frames are good
		 */
		u_int16_t flen = req->sizes[i];
		unsigned char *iso_buf = req->data + sc->vmax_packet_size * i;

		if (flen > 0) { /* if valid data... */
			if(sc->vsync > NOCOPY) { /* ...and we are not sync-hunting... */
				sc->vsync = SYNCHED;
				/* ...copy data to frame buffer, if possible */
				if (flen + fbuf->filled > sc->frame_total_size) {
					Trace(TRACE_ISOC, "Frame buffer overflow (flen = %d,frame_total_size = %d).\n",flen, sc->frame_total_size);
							
					sc->vsync = NOCOPY; /* Hmm, let's wait for an EOF (end-of-frame) */
					sc->vframes_error++;
				}
				else {
					memcpy(fillptr, iso_buf, flen);
					fillptr += flen;
				}
			}
			fbuf->filled += flen;
		}

		if(flen >= sc->vlast_packet_size)
			goto eof_done;
		
		/* Shorter packet... We probably have the end of an image-frame; 
		   wake up read() process and let select()/poll() do something.
		   Decompression is done in user time over there.
		 */
		
		if (sc->vsync != SYNCHED)
			goto start_new_frame;
		
		/* The ToUCam Fun CMOS sensor causes the firmware to send 2 or 3 bogus 
		   frames on the USB wire after an exposure change. This conditition is 
		   however detected  in the cam and a bit is set in the header.
		 */
		if (sc->type == 730) {
			unsigned char *ptr = (unsigned char *)fbuf->data;
				
			if (ptr[1] == 1 && ptr[0] & 0x10) {
				sc->drop_frames += 2;
				sc->vframes_error++;
			}
			if ((ptr[0] ^ sc->vmirror) & 0x01) {
				if (ptr[0] & 0x01) {
					sc->snapshot_button_status = 1;
					Debug("pwc: Snapshot button pressed.\n");
				}
				else {
					Debug("pwc: Snapshot button released.\n");
				}
			}
			if ((ptr[0] ^ sc->vmirror) & 0x02) {
				if (ptr[0] & 0x02) {
					Debug("pwc: Image is mirrored.\n");
				}
				else {
					Debug("pwc: Image is normal.\n");
				}
			}
			sc->vmirror = ptr[0] & 0x03;
			/* Sometimes the trailer of the 730 is still sent as a 4 byte packet 
			   after a short frame; this condition is filtered out specifically. A 4 byte
			   frame doesn't make sense anyway.
			   So we get either this sequence: 
			   	drop_bit set -> 4 byte frame -> short frame -> good frame
			   Or this one:
			   	drop_bit set -> short frame -> good frame
			   So we drop either 3 or 2 frames in all!
			 */
			if (fbuf->filled == 4)
				sc->drop_frames++;
		}
		else if (sc->type == 740 || sc->type == 720) {
			unsigned char *ptr = (unsigned char *)fbuf->data;
			if ((ptr[0] ^ sc->vmirror) & 0x01) {
				if (ptr[0] & 0x01) {
					sc->snapshot_button_status = 1;
					Debug("pwc: Snapshot button pressed.\n");
				}
				else {
					Debug("pwc: Snapshot button released.\n");
				}
			}
			sc->vmirror = ptr[0] & 0x03;
		}

		/* In case we were instructed to drop the frame, do so silently.
		   The buffer pointers are not updated either (but the counters are reset below).
		 */
		if(sc->drop_frames > 0) {
			sc->drop_frames--;
		}
		/* Check for underflow first */
		else if(fbuf->filled < sc->frame_total_size) {
			Trace(TRACE_ISOC,"Frame buffer underflow (%d bytes); discarded.\n", fbuf->filled);
			sc->vframes_error++;
		}
		else {
			/* Send only once per EOF */
			awake = 1; /* delay wake_ups */
			/* Find our next frame to fill. This will always succeed, since we
			 * nick a frame from either empty or full list, but if we had to
			 * take it from the full list, it means a frame got dropped.
			 */
			if (pwc_next_fill_frame(sc)) {
				sc->vframes_dumped++;
				if ((sc->vframe_count > FRAME_LOWMARK)) {
					if (sc->vframes_dumped < 20) {
						Trace(TRACE_ISOC,"Dumping frame %d.\n",sc->vframe_count);
					}
					if (sc->vframes_dumped == 20) {
						Trace(TRACE_ISOC,"Dumping frame %d (last message).\n", sc->vframe_count);
					}
				}
			}
			fbuf = sc->fill_frame;
		}
		sc->vframe_count++;
start_new_frame:
		fbuf->filled = 0;
		fillptr = fbuf->data;
		sc->vsync = SYNCHUNT;
eof_done:
		sc->vlast_packet_size = flen;

	}
handler_end:
	if(awake) {
		if(sc->state & PWC_ASLEEP) {
			wakeup(sc);
		}
		if(sc->state & PWC_POLL) {
			sc->state &= ~PWC_POLL;
			selwakeuppri(&sc->rsel, PZERO);
		}
	}
	/* setup size for next transfer */
	for (i = 0; i < ISO_FRAMES_PER_DESC; i++)
		req->sizes[i] = sc->vmax_packet_size;
	
	usbd_setup_isoc_xfer(xfer, sc->sc_videopipe, req, req->sizes,
				ISO_FRAMES_PER_DESC,USBD_NO_COPY, pwc_isoc_handler);

	usbd_transfer(xfer);
}

int
pwc_handle_frame(struct pwc_softc *sc)
{
	int ret = 0;

	mtx_lock(&sc->ptrlock);
	
	/* First grab our read_frame; this is removed from all lists, so
	   we can release the lock after this without problems */

	if (sc->full_frames == NULL) {
		mtx_unlock(&sc->ptrlock);
		return -EBUSY;
	}
	
	sc->read_frame = sc->full_frames;
	sc->full_frames = sc->full_frames->next;
	sc->read_frame->next = NULL;
	
	/* Decompression is a lenghty process, so it's outside of the lock.
	 * This gives the isoc_handler the opportunity to fill more frames
	 * in the mean time.
	*/

	mtx_unlock(&sc->ptrlock);
	mtx_unlock(&Giant);
	ret = pwc_decompress(sc);
	mtx_lock(&Giant);
	mtx_lock(&sc->ptrlock);

	/* We're done with read_buffer, tack it to the end of the empty buffer list */
	if(sc->empty_frames == NULL) {
		sc->empty_frames = sc->read_frame;
		sc->empty_frames_tail = sc->empty_frames;
	}
	else {
		sc->empty_frames_tail->next = sc->read_frame;
		sc->empty_frames_tail = sc->read_frame;
	}
	
	sc->read_frame = NULL;
	mtx_unlock(&sc->ptrlock);
	return ret;
}

void
pwc_next_image(struct pwc_softc *sc)
{
	sc->image_used[sc->fill_image] = 0;
	sc->fill_image = (sc->fill_image + 1) % sc->pwc_mbufs;
}

static void 
pwc_reset_buffers(struct pwc_softc *sc)
{
	int i;

	mtx_lock(&sc->ptrlock);
	
	sc->full_frames = NULL;
	sc->full_frames_tail = NULL;
	for (i = 0; i < sc->pwc_fbufs; i++) {
		sc->fbuf[i].filled = 0;
		if (i > 0)
			sc->fbuf[i].next = &sc->fbuf[i - 1];
		else
			sc->fbuf->next = NULL;
	}
	sc->empty_frames = &sc->fbuf[sc->pwc_fbufs - 1];
	sc->empty_frames_tail = sc->fbuf;
	sc->read_frame = NULL;
	sc->fill_frame = sc->empty_frames;
	sc->empty_frames = sc->empty_frames->next;

	sc->image_read_pos = 0;
	sc->fill_image = 0;

	mtx_unlock(&sc->ptrlock);
}

static void
pwc_free_buffers(struct pwc_softc *sc, int detach)
{
	int i;
	Trace(TRACE_MEMORY, "Entering free_buffers(%p).\n", sc);
	if (sc->fbuf != NULL) {
		for (i = 0; i < sc->pwc_fbufs; i++) {
			if (sc->fbuf[i].data != NULL) {
				free(sc->fbuf[i].data,M_USBDEV);
				sc->fbuf[i].data = NULL;
			}
		}
		free(sc->fbuf,M_USBDEV);
		sc->fbuf = NULL;
	}

	if (sc->decompress_data != NULL) {
		free(sc->decompress_data,M_USBDEV);
		sc->decompress_data = NULL;
	}
#ifdef USE_MMAP
	if(sc->image_data != NULL && detach == 1)
#else
	if(sc->image_data != NULL)
#endif
	{
		free(sc->image_data,M_USBDEV);
		sc->image_data = NULL;
	}

	for (i = 0; i < MAX_ISO_BUFS; i++) {
		if (sc->sbuf[i].xfer != NULL) {
			usbd_free_xfer(sc->sbuf[i].xfer); /* implicit buffer free */
			sc->sbuf[i].xfer = NULL;
		}
	}
}

DRIVER_MODULE(pwc, uhub, pwc_driver, pwc_devclass, usbd_driver_load, 0);
