patch-2.4.18 linux/drivers/usb/vicam.c

Next file: linux/drivers/usb/vicam.h
Previous file: linux/drivers/usb/usbnet.c
Back to the patch index
Back to the overall index

diff -Naur -X /home/marcelo/lib/dontdiff linux.orig/drivers/usb/vicam.c linux/drivers/usb/vicam.c
@@ -0,0 +1,986 @@
+/* -*- linux-c -*-
+ * USB ViCAM driver
+ *
+ * Copyright (c) 2001 Christopher L Cheney (ccheney@cheney.cx)
+ * Copyright (c) 2001 Pavel Machek (pavel@suse.cz) sponsored by SuSE
+ *
+ *      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 driver is for the Vista Imaging ViCAM and 3Com HomeConnect USB
+ *
+ * Thanks to Greg Kroah-Hartman for the USB Skeleton driver
+ *
+ * TODO:
+ *	- find out the ids for the Vista Imaging ViCAM
+ *
+ * History:
+ *
+ * 2001_07_07 - 0.1 - christopher: first version
+ * 2001_08_28 - 0.2 - pavel: messed it up, but for some fun, try 
+ 			while true; do dd if=/dev/video of=/dev/fb0 bs=$[0x1e480] count=1 2> /dev/null; done
+		      yep, moving pictures.
+ * 2001_08_29 - 0.3 - pavel: played a little bit more. Experimental mmap support. For some fun,
+ 			get gqcam-0.9, compile it and run. Better than dd ;-).
+ * 2001_08_29 - 0.4 - pavel: added shutter speed control (not much functional)
+ 			kill update_params if it does not seem to work for you.
+ * 2001_08_30 - 0.5 - pavel: fixed stupid bug with update_params & vicam_bulk
+
+ *
+ * FIXME: It crashes on rmmod with camera plugged.
+ */
+#define DEBUG 1
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/signal.h>
+#include <linux/errno.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/fcntl.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/list.h>
+#include <linux/smp_lock.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/usb.h>
+
+#include <asm/io.h>
+#include <linux/wrapper.h>
+#include <linux/vmalloc.h>
+
+#include <linux/videodev.h>
+
+#include "vicam.h"
+#include "vicamurbs.h"
+
+/* Version Information */
+#define DRIVER_VERSION "v0"
+#define DRIVER_AUTHOR "Christopher L Cheney <ccheney@cheney.cx>, Pavel Machek <pavel@suse.cz>"
+#define DRIVER_DESC "USB ViCAM Driver"
+
+/* Define these values to match your device */
+#define USB_VICAM_VENDOR_ID	0x04C1
+#define USB_VICAM_PRODUCT_ID	0x009D
+
+/* table of devices that work with this driver */
+static struct usb_device_id vicam_table [] = {
+	{ USB_DEVICE(USB_VICAM_VENDOR_ID, USB_VICAM_PRODUCT_ID) },
+	{ }					/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, vicam_table);
+
+static int video_nr = -1; 		/* next avail video device */
+static struct usb_driver vicam_driver;
+
+static char *buf, *buf2;
+static int change_pending = 0; 
+
+static int vicam_parameters(struct usb_vicam *vicam);
+
+/******************************************************************************
+ *
+ *  Memory management functions
+ *
+ *  Taken from bttv-drivers.c 2.4.7-pre3
+ *
+ ******************************************************************************/
+
+/* [DaveM] I've recoded most of this so that:
+ * 1) It's easier to tell what is happening
+ * 2) It's more portable, especially for translating things
+ *    out of vmalloc mapped areas in the kernel.
+ * 3) Less unnecessary translations happen.
+ *
+ * The code used to assume that the kernel vmalloc mappings
+ * existed in the page tables of every process, this is simply
+ * not guarenteed.  We now use pgd_offset_k which is the
+ * defined way to get at the kernel page tables.
+ */
+
+/* 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  = (unsigned long) page_address(pte_page(pte));
+				ret |= (adr & (PAGE_SIZE - 1));
+
+			}
+		}
+	}
+	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);
+	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);
+	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);
+	return ret;
+}
+
+static void * rvmalloc(signed long size)
+{
+	void * mem;
+	unsigned long adr, page;
+
+	mem=vmalloc_32(size);
+	if (mem)
+	{
+		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(virt_to_page(__va(page)));
+			adr+=PAGE_SIZE;
+			size-=PAGE_SIZE;
+		}
+	}
+	return mem;
+}
+
+static void rvfree(void * mem, signed long size)
+{
+	unsigned long adr, page;
+
+	if (mem)
+	{
+		adr=(unsigned long) mem;
+		while (size > 0)
+		{
+			page = kvirt_to_pa(adr);
+			mem_map_unreserve(virt_to_page(__va(page)));
+			adr+=PAGE_SIZE;
+			size-=PAGE_SIZE;
+		}
+		vfree(mem);
+	}
+}
+
+/******************************************************************************
+ *
+ *  Foo Bar
+ *
+ ******************************************************************************/
+
+/**
+ *	usb_vicam_debug_data
+ */
+static inline void usb_vicam_debug_data (const char *function, int size, const unsigned char *data)
+{
+	int i;
+
+	if (!debug)
+		return;
+
+	printk (KERN_DEBUG __FILE__": %s - length = %d, data = ",
+		function, size);
+	for (i = 0; i < size; ++i) {
+		printk ("%.2x ", data[i]);
+	}
+	printk ("\n");
+}
+
+/*****************************************************************************
+ *
+ *  Send command to vicam
+ *
+ *****************************************************************************/
+
+static int vicam_sndctrl(int set, struct usb_vicam *vicam, unsigned short req,
+	unsigned short value, unsigned char *cp, int size)
+{
+	int ret;
+	unsigned char *transfer_buffer = kmalloc (size, GFP_KERNEL);
+
+	/* Needs to return data I think, works for sending though */
+	memcpy(transfer_buffer, cp, size);
+	
+	ret = usb_control_msg ( vicam->udev, set ? usb_sndctrlpipe(vicam->udev, 0) : usb_rcvctrlpipe(vicam->udev, 0), req, (set ? USB_DIR_OUT : USB_DIR_IN) | USB_TYPE_VENDOR | USB_RECIP_DEVICE, value, 0, transfer_buffer, size, HZ);
+
+	kfree(transfer_buffer);
+	if (ret)
+		printk("vicam: error: %d\n", ret);
+	mdelay(100);
+	return ret;
+}
+
+
+/*****************************************************************************
+ *
+ *  Video4Linux Helpers
+ * 
+ *****************************************************************************/
+
+static int vicam_get_capability(struct usb_vicam *vicam, struct video_capability *b)
+{
+	dbg("vicam_get_capability");
+
+	strcpy(b->name, vicam->camera_name);
+	b->type = VID_TYPE_CAPTURE | VID_TYPE_MONOCHROME;
+	b->channels = 1;
+	b->audios = 0;
+
+	b->maxwidth = vicam->width[vicam->sizes-1];
+	b->maxheight = vicam->height[vicam->sizes-1];
+	b->minwidth = vicam->width[0];
+	b->minheight = vicam->height[0];
+
+	return 0;
+}
+		
+static int vicam_get_channel(struct usb_vicam *vicam, struct video_channel *v)
+{
+	dbg("vicam_get_channel");
+
+	if (v->channel != 0)
+		return -EINVAL;
+ 
+	v->flags = 0;
+	v->tuners = 0;
+	v->type = VIDEO_TYPE_CAMERA;
+	strcpy(v->name, "Camera");
+
+	return 0;
+} 
+		
+static int vicam_set_channel(struct usb_vicam *vicam, struct video_channel *v)
+{
+	dbg("vicam_set_channel");
+
+	if (v->channel != 0)
+		return -EINVAL;
+	
+	return 0;
+}
+		
+static int vicam_get_mmapbuffer(struct usb_vicam *vicam, struct video_mbuf *vm)
+{
+	int i;
+
+	dbg("vicam_get_mmapbuffer");
+
+	memset(vm, 0, sizeof(vm));
+	vm->size = VICAM_NUMFRAMES * vicam->maxframesize;
+	vm->frames = VICAM_NUMFRAMES;
+
+	for (i=0; i<VICAM_NUMFRAMES; i++)
+		vm->offsets[i] = vicam->maxframesize * i;
+
+	return 0;
+}
+
+static int vicam_get_picture(struct usb_vicam *vicam, struct video_picture *p)
+{
+	dbg("vicam_get_picture");
+
+	/* This is probably where that weird 0x56 call goes */
+	p->brightness = vicam->win.brightness;
+	p->hue = vicam->win.hue;
+	p->colour = vicam->win.colour;
+	p->contrast = vicam->win.contrast;
+	p->whiteness = vicam->win.whiteness;
+	p->depth = vicam->win.depth;
+	p->palette = vicam->win.palette;
+
+	return 0;
+}
+
+static void synchronize(struct usb_vicam *vicam)
+{
+	change_pending = 1;
+	interruptible_sleep_on(&vicam->wait);
+	vicam_sndctrl(1, vicam, VICAM_REQ_CAMERA_POWER, 0x00, NULL, 0);
+	mdelay(10);
+	vicam_sndctrl(1, vicam, VICAM_REQ_LED_CONTROL, 0x00, NULL, 0);
+	mdelay(10);
+}
+
+static void params_changed(struct usb_vicam *vicam)
+{
+#if 1
+	synchronize(vicam);
+	mdelay(10);
+	vicam_parameters(vicam);
+	printk("Submiting urb: %d\n", usb_submit_urb(&vicam->readurb));
+#endif
+}
+
+static int vicam_set_picture(struct usb_vicam *vicam, struct video_picture *p)
+{
+	int changed = 0;
+	info("vicam_set_picture (%d)", p->brightness);
+
+
+#define SET(x) \
+	if (vicam->win.x != p->x) \
+		vicam->win.x = p->x, changed = 1;
+	SET(brightness);
+	SET(hue);
+	SET(colour);
+	SET(contrast);
+	SET(whiteness);
+	SET(depth);
+	SET(palette);
+	if (changed)
+		params_changed(vicam);
+
+	return 0;
+	/* Investigate what should be done maybe 0x56 type call */
+	if (p->depth != 8) return 1;
+	if (p->palette != VIDEO_PALETTE_GREY) return 1;
+
+	return 0;
+}
+
+/* FIXME - vicam_sync_frame - important */
+static int vicam_sync_frame(struct usb_vicam *vicam, int frame)
+{
+	dbg("vicam_sync_frame");
+
+	if(frame <0 || frame >= VICAM_NUMFRAMES)
+		return -EINVAL;
+
+	/* Probably need to handle various cases */
+/*	ret=vicam_newframe(vicam, frame);
+	vicam->frame[frame].grabstate=FRAME_UNUSED;
+*/
+	return 0;
+}
+	
+static int vicam_get_window(struct usb_vicam *vicam, struct video_window *vw)
+{
+	dbg("vicam_get_window");
+
+	vw->x = 0;
+	vw->y = 0;
+	vw->chromakey = 0;
+	vw->flags = 0;
+	vw->clipcount = 0;
+	vw->width = vicam->win.width;
+	vw->height = vicam->win.height;
+
+	return 0;
+}
+
+static int vicam_set_window(struct usb_vicam *vicam, struct video_window *vw)
+{
+	info("vicam_set_window");
+		
+	if (vw->flags)
+		return -EINVAL;
+	if (vw->clipcount)
+		return -EINVAL;
+
+	if (vicam->win.width == vw->width && vicam->win.height == vw->height)
+		return 0;
+
+	/* Pick largest mode that is smaller than specified res */
+	/* If specified res is too small reject                 */
+
+	/* Add urb send to device... */
+
+	vicam->win.width = vw->width;
+	vicam->win.height = vw->height;
+	params_changed(vicam);
+
+	return 0;
+}
+
+/* FIXME - vicam_mmap_capture - important */
+static int vicam_mmap_capture(struct usb_vicam *vicam, struct video_mmap *vm)
+{
+	dbg("vicam_mmap_capture");
+
+	/* usbvideo.c looks good for using here */
+
+	/* 
+	if (vm->frame >= VICAM_NUMFRAMES)
+		return -EINVAL;
+	if (vicam->frame[vm->frame].grabstate != FRAME_UNUSED)
+		return -EBUSY;
+	vicam->frame[vm->frame].grabstate=FRAME_READY;
+	*/
+
+	/* No need to vicam_set_window here according to Alan */
+
+	/*
+	if (!vicam->streaming)
+		vicam_start_stream(vicam);
+	*/
+
+	/* set frame as ready */
+
+	return 0;
+}
+
+/*****************************************************************************
+ *
+ *  Video4Linux
+ * 
+ *****************************************************************************/
+
+static int vicam_v4l_open(struct video_device *vdev, int flags)
+{
+	struct usb_vicam *vicam = (struct usb_vicam *)vdev;
+	int err = 0;
+	
+	dbg("vicam_v4l_open");
+
+	MOD_INC_USE_COUNT; 
+	down(&vicam->sem);
+
+	if (vicam->open_count)		/* Maybe not needed? */
+		err = -EBUSY;
+	else {
+		vicam->fbuf = rvmalloc(vicam->maxframesize * VICAM_NUMFRAMES);
+		if (!vicam->fbuf)
+			err=-ENOMEM;
+		else {
+			vicam->open_count = 1;
+		}
+#ifdef BLINKING
+		vicam_sndctrl(1, vicam, VICAM_REQ_CAMERA_POWER, 0x01, NULL, 0);
+		info ("led on");
+		vicam_sndctrl(1, vicam, VICAM_REQ_LED_CONTROL, 0x01, NULL, 0);
+#endif
+	}
+
+	up(&vicam->sem);
+	if (err)
+		MOD_DEC_USE_COUNT;
+	return err;
+}
+
+static void vicam_v4l_close(struct video_device *vdev)
+{
+	struct usb_vicam *vicam = (struct usb_vicam *)vdev;
+
+	dbg("vicam_v4l_close");
+	
+	down(&vicam->sem);
+
+#ifdef BLINKING
+	info ("led off");
+	vicam_sndctrl(1, vicam, VICAM_REQ_LED_CONTROL, 0x00, NULL, 0);
+//	vicam_sndctrl(1, vicam, VICAM_REQ_CAMERA_POWER, 0x00, NULL, 0); Leave it on
+#endif
+
+	rvfree(vicam->fbuf, vicam->maxframesize * VICAM_NUMFRAMES);
+	vicam->fbuf = 0;
+	vicam->open_count=0;
+
+	up(&vicam->sem);
+	/* Why does se401.c have a usbdevice check here? */
+	/* If device is unplugged while open, I guess we only may unregister now */
+	MOD_DEC_USE_COUNT;
+}
+
+static long vicam_v4l_read(struct video_device *vdev, char *user_buf, unsigned long buflen, int noblock)
+{
+	//struct usb_vicam *vicam = (struct usb_vicam *)vdev;
+
+	dbg("vicam_v4l_read(%ld)", buflen);
+
+	if (!vdev || !buf)
+		return -EFAULT;
+
+	if (copy_to_user(user_buf, buf2, buflen))
+		return -EFAULT;
+	return buflen;
+}
+
+static long vicam_v4l_write(struct video_device *dev, const char *buf, unsigned long count, int noblock)
+{
+	info("vicam_v4l_write");
+	return -EINVAL;
+}
+
+static int vicam_v4l_ioctl(struct video_device *vdev, unsigned int cmd, void *arg)
+{
+	struct usb_vicam *vicam = (struct usb_vicam *)vdev;
+	int ret = -EL3RST;
+
+	if (!vicam->udev)
+		return -EIO;
+
+	down(&vicam->sem);
+
+	switch (cmd) {
+	case VIDIOCGCAP:
+	{
+		struct video_capability b;
+		ret = vicam_get_capability(vicam,&b);
+		dbg("name %s",b.name);
+		if (copy_to_user(arg, &b, sizeof(b)))
+			ret = -EFAULT;
+	}
+	case VIDIOCGFBUF:
+	{
+		struct video_buffer vb;
+		info("vicam_v4l_ioctl - VIDIOCGBUF - query frame buffer param");
+		/* frame buffer not supported, not used */
+		memset(&vb, 0, sizeof(vb));
+		vb.base = NULL;
+		
+		/* FIXME - VIDIOCGFBUF - why the void */
+		if (copy_to_user((void *)arg, (void *)&vb, sizeof(vb)))
+			ret = -EFAULT;
+		ret = 0;
+	}
+	case VIDIOCGWIN:
+	{
+		struct video_window vw;
+		ret = vicam_get_window(vicam, &vw);
+		if (copy_to_user(arg, &vw, sizeof(vw)))
+			ret = -EFAULT;
+	}
+	case VIDIOCSWIN:
+	{
+		struct video_window vw;
+		if (copy_from_user(&vw, arg, sizeof(vw)))
+			ret = -EFAULT;
+		else
+			ret = vicam_set_window(vicam, &vw);
+		return ret;
+	}
+	case VIDIOCGCHAN:
+	{
+		struct video_channel v;
+
+		if (copy_from_user(&v, arg, sizeof(v)))
+			ret = -EFAULT;
+		else {
+			ret = vicam_get_channel(vicam,&v);
+			if (copy_to_user(arg, &v, sizeof(v)))
+				ret = -EFAULT;
+		}
+	}
+	case VIDIOCSCHAN:
+	{
+		struct video_channel v;
+		if (copy_from_user(&v, arg, sizeof(v)))
+			ret = -EFAULT;
+		else
+			ret = vicam_set_channel(vicam,&v);
+ 	}
+	case VIDIOCGPICT:
+	{
+		struct video_picture p;
+		ret = vicam_get_picture(vicam, &p);
+		if (copy_to_user(arg, &p, sizeof(p)))
+			ret = -EFAULT;
+	}
+	case VIDIOCSPICT:
+	{
+		struct video_picture p;
+		if (copy_from_user(&p, arg, sizeof(p)))
+			ret = -EFAULT;
+		else
+			ret = vicam_set_picture(vicam, &p);
+	}
+	case VIDIOCGMBUF:
+	{
+		struct video_mbuf vm;
+		ret = vicam_get_mmapbuffer(vicam,&vm);
+		/* FIXME - VIDIOCGMBUF - why the void */
+		if (copy_to_user((void *)arg, (void *)&vm, sizeof(vm)))
+			ret = -EFAULT;
+	}
+	case VIDIOCMCAPTURE:
+	{
+		struct video_mmap vm;
+		ret = vicam_mmap_capture(vicam, &vm);
+		/* FIXME: This is probably not right */
+	}
+	case VIDIOCSYNC:
+	{
+		int frame;
+		/* FIXME - VIDIOCSYNC - why the void */
+		if (copy_from_user((void *)&frame, arg, sizeof(int)))
+			ret = -EFAULT;
+		else
+			ret = vicam_sync_frame(vicam,frame);
+	}
+
+	case VIDIOCKEY:
+		ret = 0;
+ 
+	case VIDIOCCAPTURE:
+	case VIDIOCSFBUF:
+	case VIDIOCGTUNER:
+	case VIDIOCSTUNER:
+	case VIDIOCGFREQ:
+	case VIDIOCSFREQ:
+	case VIDIOCGAUDIO:
+	case VIDIOCSAUDIO:
+	case VIDIOCGUNIT:
+		ret = -EINVAL;
+
+	default:
+	{
+		info("vicam_v4l_ioctl - %ui",cmd);
+		ret = -ENOIOCTLCMD;
+	}
+	} /* end switch */
+
+	up(&vicam->sem);
+        return ret;
+}
+
+static int vicam_v4l_mmap(struct video_device *dev, const char *adr, unsigned long size)
+{
+	struct usb_vicam *vicam = (struct usb_vicam *)dev;
+	unsigned long start = (unsigned long)adr;
+	unsigned long page, pos;
+
+	down(&vicam->sem);
+	
+	if (vicam->udev == NULL) {
+		up(&vicam->sem);
+		return -EIO;
+	}
+#if 0
+	if (size > (((VICAM_NUMFRAMES * vicam->maxframesize) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))) {
+		up(&vicam->sem);
+		return -EINVAL;
+	}
+#endif
+	pos = (unsigned long)vicam->fbuf;
+	while (size > 0) {
+		page = kvirt_to_pa(pos);
+		if (remap_page_range(start, page, PAGE_SIZE, PAGE_SHARED)) {
+			up(&vicam->sem);
+			return -EAGAIN;
+		}
+		start += PAGE_SIZE;
+		pos += PAGE_SIZE;
+		if (size > PAGE_SIZE)
+			size -= PAGE_SIZE;
+		else
+			size = 0;
+	}
+	up(&vicam->sem);
+
+        return 0;
+}
+
+/* FIXME - vicam_v4l_init */
+static int vicam_v4l_init(struct video_device *dev)
+{
+	/* stick proc fs stuff in here if wanted */
+	dbg("vicam_v4l_init");
+	return 0;
+}
+
+/* FIXME - vicam_template - important */
+static struct video_device vicam_template = {
+	name:		"vicam USB camera",
+	type:		VID_TYPE_CAPTURE,
+	hardware:	VID_HARDWARE_SE401, /* need to ask for own id */
+	open:		vicam_v4l_open,
+	close:		vicam_v4l_close,
+	read:		vicam_v4l_read,
+	write:		vicam_v4l_write,
+	ioctl:		vicam_v4l_ioctl,
+	mmap:		vicam_v4l_mmap,
+	initialize:	vicam_v4l_init,
+};
+
+/******************************************************************************
+ *
+ *  Some Routines
+ *
+ ******************************************************************************/
+
+/*
+Flash the led
+vicam_sndctrl(1, vicam, VICAM_REQ_CAMERA_POWER, 0x01, NULL, 0);
+info ("led on");
+vicam_sndctrl(1, vicam, VICAM_REQ_LED_CONTROL, 0x01, NULL, 0);
+info ("led off");
+vicam_sndctrl(1, vicam, VICAM_REQ_LED_CONTROL, 0x00, NULL, 0);
+vicam_sndctrl(1, vicam, VICAM_REQ_CAMERA_POWER, 0x00, NULL, 0);
+*/
+
+static void vicam_bulk(struct urb *urb)
+{
+	struct usb_vicam *vicam = urb->context;
+
+	/*	if (!vicam || !vicam->dev || !vicam->used)
+		return;
+	*/
+
+	if (urb->status)
+		printk("vicam%d: nonzero read/write bulk status received: %d",
+			0, urb->status);
+
+	urb->actual_length = 0;
+	urb->dev = vicam->udev;
+
+	memcpy(buf2, buf+64, 0x1e480);
+	if (vicam->fbuf)
+		memcpy(vicam->fbuf, buf+64, 0x1e480);
+
+	if (!change_pending) {
+		if (usb_submit_urb(urb))
+			dbg("failed resubmitting read urb");
+	} else {
+		change_pending = 0;
+		wake_up_interruptible(&vicam->wait);
+	}
+}
+
+static int vicam_parameters(struct usb_vicam *vicam)
+{
+	unsigned char req[0x10];
+	unsigned int shutter;
+	shutter = 10;
+
+	switch (vicam->win.width) {
+	case 512:
+	default:
+		memcpy(req, s512x242bw, 0x10);
+		break;
+	case 256:
+		memcpy(req, s256x242bw, 0x10);
+		break;
+	case 128:
+		memcpy(req, s128x122bw, 0x10);
+		break;
+	}
+
+
+	mdelay(10);
+	vicam_sndctrl(1, vicam, VICAM_REQ_CAMERA_POWER, 0x01, NULL, 0);
+	info ("led on");
+	vicam_sndctrl(1, vicam, VICAM_REQ_LED_CONTROL, 0x01, NULL, 0);
+
+	mdelay(10);
+
+	shutter = vicam->win.contrast / 256;
+	if (shutter == 0)
+		shutter = 1;
+	printk("vicam_parameters: brightness %d, shutter %d\n", vicam->win.brightness, shutter );
+	req[0] = vicam->win.brightness /256;
+	shutter = 15600/shutter - 1;
+	req[6] = shutter & 0xff;
+	req[7] = (shutter >> 8) & 0xff;
+	vicam_sndctrl(1, vicam, VICAM_REQ_CAPTURE, 0x80, req, 0x10);
+	mdelay(10);
+	vicam_sndctrl(0, vicam, VICAM_REQ_GET_SOMETHIN, 0, buf, 0x10);
+	mdelay(10);
+
+	return 0;
+}
+
+static int vicam_init(struct usb_vicam *vicam)
+{
+	int width[] = {128, 256, 512};
+	int height[] = {122, 242, 242};
+
+	dbg("vicam_init");
+	buf = kmalloc(0x1e480, GFP_KERNEL);
+	buf2 = kmalloc(0x1e480, GFP_KERNEL);
+	if ((!buf) || (!buf2)) {
+		printk("Not enough memory for vicam!\n");
+		goto error;
+	}
+
+	/* do we do aspect correction in kernel or not? */
+	vicam->sizes = 3;
+	vicam->width = kmalloc(vicam->sizes*sizeof(int), GFP_KERNEL);
+	vicam->height = kmalloc(vicam->sizes*sizeof(int), GFP_KERNEL);
+	memcpy(vicam->width, &width, sizeof(width));
+	memcpy(vicam->height, &height, sizeof(height));
+	vicam->maxframesize = vicam->width[vicam->sizes-1] * vicam->height[vicam->sizes-1];
+
+	/* Download firmware to camera */
+	vicam_sndctrl(1, vicam, VICAM_REQ_VENDOR, 0, firmware1, sizeof(firmware1));
+	vicam_sndctrl(1, vicam, VICAM_REQ_VENDOR, 0, findex1, sizeof(findex1));
+	vicam_sndctrl(1, vicam, VICAM_REQ_VENDOR, 0, fsetup, sizeof(fsetup));
+	vicam_sndctrl(1, vicam, VICAM_REQ_VENDOR, 0, firmware2, sizeof(firmware2));
+	vicam_sndctrl(1, vicam, VICAM_REQ_VENDOR, 0, findex2, sizeof(findex2));
+	vicam_sndctrl(1, vicam, VICAM_REQ_VENDOR, 0, fsetup, sizeof(fsetup));
+
+	vicam_parameters(vicam);
+
+	FILL_BULK_URB(&vicam->readurb, vicam->udev, usb_rcvbulkpipe(vicam->udev, 0x81),
+		      buf, 0x1e480, vicam_bulk, vicam);
+	printk("Submiting urb: %d\n", usb_submit_urb(&vicam->readurb));
+
+	return 0;
+error:
+	if (buf)
+		kfree(buf);
+	if (buf2)
+		kfree(buf2);
+	return 1;
+}
+
+static void * __devinit vicam_probe(struct usb_device *udev, unsigned int ifnum,
+	const struct usb_device_id *id)
+{
+	struct usb_vicam *vicam;
+	char *camera_name=NULL;
+
+	dbg("vicam_probe");
+
+	/* See if the device offered us matches what we can accept */
+	if ((udev->descriptor.idVendor != USB_VICAM_VENDOR_ID) ||
+	    (udev->descriptor.idProduct != USB_VICAM_PRODUCT_ID)) {
+		return NULL;
+	}
+	
+	camera_name="3Com HomeConnect USB";
+	info("ViCAM camera found: %s", camera_name);
+	
+	vicam = kmalloc (sizeof(struct usb_vicam), GFP_KERNEL);
+	if (vicam == NULL) {
+		err ("couldn't kmalloc vicam struct");
+		return NULL;
+	}
+	memset(vicam, 0, sizeof(*vicam));
+	
+	vicam->udev = udev;
+	vicam->camera_name = camera_name;
+	vicam->win.brightness = 128;
+	vicam->win.contrast = 10;
+
+	/* FIXME */
+	if (vicam_init(vicam))
+		return NULL;
+	memcpy(&vicam->vdev, &vicam_template, sizeof(vicam_template));
+	memcpy(vicam->vdev.name, vicam->camera_name, strlen(vicam->camera_name));
+	
+	if (video_register_device(&vicam->vdev, VFL_TYPE_GRABBER, video_nr) == -1) {
+		err("video_register_device");
+		return NULL;
+	}
+
+	info("registered new video device: video%d", vicam->vdev.minor);
+	
+	init_MUTEX (&vicam->sem);
+	init_waitqueue_head(&vicam->wait);
+	
+	return vicam;
+}
+
+
+/* FIXME - vicam_disconnect - important */
+static void vicam_disconnect(struct usb_device *udev, void *ptr)
+{
+	struct usb_vicam *vicam;
+
+	vicam = (struct usb_vicam *) ptr;
+
+	if (!vicam->open_count)
+		video_unregister_device(&vicam->vdev);
+	vicam->udev = NULL;
+/*
+	vicam->frame[0].grabstate = FRAME_ERROR;
+	vicam->frame[1].grabstate = FRAME_ERROR;
+*/
+
+	/* Free buffers and shit */
+
+	info("%s disconnected", vicam->camera_name);
+	synchronize(vicam);
+
+	if (!vicam->open_count) {
+		/* Other random junk */
+		kfree(vicam);
+		vicam = NULL;
+	}
+}
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver vicam_driver = {
+	name:		"vicam",
+	probe:		vicam_probe,
+	disconnect:	vicam_disconnect,
+	id_table:	vicam_table,
+};
+
+/******************************************************************************
+ *
+ *  Module Routines
+ *
+ ******************************************************************************/
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+/* Module paramaters */
+MODULE_PARM(debug, "i");
+MODULE_PARM_DESC(debug, "Debug enabled or not");
+
+static int __init usb_vicam_init(void)
+{
+	int result;
+
+	printk("VICAM: initializing\n");
+	/* register this driver with the USB subsystem */
+	result = usb_register(&vicam_driver);
+	if (result < 0) {
+		err("usb_register failed for the "__FILE__" driver. Error number %d",
+		    result);
+		return -1;
+	}
+
+	info(DRIVER_VERSION " " DRIVER_AUTHOR);
+	info(DRIVER_DESC);
+	return 0;
+}
+
+static void __exit usb_vicam_exit(void)
+{
+	/* deregister this driver with the USB subsystem */
+	usb_deregister(&vicam_driver);
+}
+
+module_init(usb_vicam_init);
+module_exit(usb_vicam_exit);

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)