[PATCH] USB: add zr364xx V4L2 driver

Antoine Jacquet <[email protected]>
Newsgroups gmane.comp.video.video4linux,gmane.linux.usb.devel
Message-ID <[email protected]>
This patch adds a V4L2 driver giving support for USB webcams based on the 
zr364xx chipsets.

Signed-off-by: Antoine Jacquet <[email protected]>

---

This driver has been available for some time on my website as a separate 
package and people seem to be happy with it.
I think users would appreciate to have it in the kernel mainline, 
this is why I am submitting this patch, which is my first contribution.


diff -uprN -X linux-2.6.20-vanilla/Documentation/dontdiff linux-2.6.20-vanilla/Documentation/video4linux/zr364xx.txt linux-2.6.20/Documentation/video4linux/zr364xx.txt
--- linux-2.6.20-vanilla/Documentation/video4linux/zr364xx.txt	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.20/Documentation/video4linux/zr364xx.txt	2007-02-09 12:31:25.000000000 +0100
@@ -0,0 +1,65 @@
+Zoran 364xx based USB webcam module version 0.71
+site: http://royale.zerezo.com/zr364xx/
+mail: [email protected]
+
+introduction:
+This brings support under Linux for the Aiptek PocketDV 3300 in webcam mode.
+If you just want to get on your PC the pictures and movies on the camera, you should use the usb-storage module instead.
+The driver works with several other cameras in webcam mode (see the list below).
+Maybe this code can work for other JPEG/USB cams based on the Coach chips from Zoran?
+Possible chipsets are : ZR36430 (ZR36430BGC) and maybe ZR36431, ZR36440, ZR36442...
+You can try the experience changing the vendor/product ID values (look at the source code).
+You can get these values by looking at /var/log/messages when you plug your camera, or by typing : cat /proc/bus/usb/devices.
+If you manage to use your cam with this code, you can send me a mail ([email protected]) with the name of your cam and a patch if needed.
+This is a beta release of the driver.
+Since version 0.70, this driver is only compatible with V4L2 API and 2.6.x kernels.
+If you need V4L1 or 2.4x kernels support, please use an older version, but the code is not maintained anymore.
+Good luck!
+
+install:
+In order to use this driver, you must compile it with your kernel.
+Location: Device Drivers -> Multimedia devices -> Video For Linux -> Video Capture Adapters -> V4L USB devices
+
+usage:
+modprobe zr364xx debug=X mode=Y
+ - debug      : set to 1 to enable verbose debug messages
+ - mode       : 0 = 320x240, 1 = 160x120, 2 = 640x480
+You can then use the camera with V4L2 compatible applications, for example Ekiga.
+To capture a single image, try this: dd if=/dev/video0 of=test.jpg bs=1 count=1
+
+links :
+http://mxhaard.free.fr/ (support for many others cams including some Aiptek PocketDV)
+http://www.harmwal.nl/pccam880/ (this project also supports cameras based on this chipset)
+
+supported devices:
+------  -------  -----------     -----
+Vendor  Product  Distributor     Model
+------  -------  -----------     -----
+0x08ca  0x0109   Aiptek          PocketDV 3300
+0x08ca  0x0109   Maxell          Maxcam PRO DV3
+0x041e  0x4024   Creative        PC-CAM 880
+0x0d64  0x0108   Aiptek          Fidelity 3200
+0x0d64  0x0108   Praktica        DCZ 1.3 S
+0x0d64  0x0108   Genius          Digital Camera (?)
+0x0d64  0x0108   DXG Technology  Fashion Cam
+0x0546  0x3187   Polaroid        iON 230
+0x0d64  0x3108   Praktica        Exakta DC 2200
+0x0d64  0x3108   Genius          G-Shot D211
+0x0595  0x4343   Concord         Eye-Q Duo 1300
+0x0595  0x4343   Concord         Eye-Q Duo 2000
+0x0595  0x4343   Fujifilm        EX-10
+0x0595  0x4343   Ricoh           RDC-6000
+0x0595  0x4343   Digitrex        DSC 1300
+0x0595  0x4343   Firstline       FDC 2000
+0x0bb0  0x500d   Concord         EyeQ Go Wireless
+0x0feb  0x2004   CRS Electronic  3.3 Digital Camera
+0x0feb  0x2004   Packard Bell    DSC-300
+0x055f  0xb500   Mustek          MDC 3000
+0x08ca  0x2062   Aiptek          PocketDV 5700
+0x052b  0x1a18   Chiphead        Megapix V12
+0x04c8  0x0729   Konica          Revio 2
+0x04f2  0xa208   Creative        PC-CAM 850
+0x0784  0x0040   Traveler        Slimline X5
+0x06d6  0x0034   Trust           Powerc@m 750
+0x0a17  0x0062   Pentax          Optio 50L
+
diff -uprN -X linux-2.6.20-vanilla/Documentation/dontdiff linux-2.6.20-vanilla/drivers/media/video/Kconfig linux-2.6.20/drivers/media/video/Kconfig
--- linux-2.6.20-vanilla/drivers/media/video/Kconfig	2007-02-04 19:44:54.000000000 +0100
+++ linux-2.6.20/drivers/media/video/Kconfig	2007-02-09 11:19:03.000000000 +0100
@@ -761,6 +761,18 @@ source "drivers/media/video/zc0301/Kconf
 
 source "drivers/media/video/pwc/Kconfig"
 
+config USB_ZR364XX
+        tristate "USB ZR364XX Camera support"
+	depends on USB && VIDEO_V4L2
+        ---help---
+	  Say Y here if you want to connect this type of camera to your
+	  computer's USB port.
+	  See <file:Documentation/video4linux/zr364xx.txt> for more info
+	  and list of supported cameras.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called zr364xx.
+
 endmenu # V4L USB devices
 
 endmenu
diff -uprN -X linux-2.6.20-vanilla/Documentation/dontdiff linux-2.6.20-vanilla/drivers/media/video/Makefile linux-2.6.20/drivers/media/video/Makefile
--- linux-2.6.20-vanilla/drivers/media/video/Makefile	2007-02-04 19:44:54.000000000 +0100
+++ linux-2.6.20/drivers/media/video/Makefile	2007-02-11 02:35:33.000000000 +0100
@@ -99,6 +99,7 @@ obj-$(CONFIG_USB_OV511)         += ov511
 obj-$(CONFIG_USB_SE401)         += se401.o
 obj-$(CONFIG_USB_STV680)        += stv680.o
 obj-$(CONFIG_USB_W9968CF)       += w9968cf.o
+obj-$(CONFIG_USB_ZR364XX)       += zr364xx.o
 
 obj-$(CONFIG_USB_SN9C102)       += sn9c102/
 obj-$(CONFIG_USB_ET61X251)      += et61x251/
diff -uprN -X linux-2.6.20-vanilla/Documentation/dontdiff linux-2.6.20-vanilla/drivers/media/video/zr364xx.c linux-2.6.20/drivers/media/video/zr364xx.c
--- linux-2.6.20-vanilla/drivers/media/video/zr364xx.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.20/drivers/media/video/zr364xx.c	2007-02-11 02:19:48.000000000 +0100
@@ -0,0 +1,984 @@
+/*
+ * Zoran 364xx based USB webcam module version 0.71
+ *
+ * Allows you to use your USB webcam with V4L2 applications
+ * This is still in heavy developpement !
+ *
+ * Copyright (C) 2004  Antoine Jacquet <[email protected]>
+ * http://royale.zerezo.com/zr364xx/
+ *
+ * Heavily inspired by usb-skeleton.c, vicam.c, cpia.c and spca50x.c drivers
+ * V4L2 version inspired by meye.c driver
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/videodev.h>
+#include <linux/usb.h>
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
+#include <linux/proc_fs.h>
+#include <media/v4l2-common.h>
+
+
+/* Version Information */
+#define DRIVER_VERSION "v0.71"
+#define DRIVER_AUTHOR "Antoine Jacquet, http://royale.zerezo.com/"
+#define DRIVER_DESC "Zoran 364xx"
+
+
+/* Camera */
+#define ZORAN_FRAMES 2
+#define BUFFER_SIZE 0x1000
+#define CTRL_TIMEOUT 500
+#define ZORAN_MAX_FRAME_SIZE 100000
+
+
+/* debug macro */
+#define DBG(x...) if (debug) info(x)
+
+
+/* Module parameters */
+static int debug = 0;
+static int mode = 0;
+
+
+/* Module parameters interface */
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Debug level");
+module_param(mode, int, 0644);
+MODULE_PARM_DESC(mode, "0 = 320x240, 1 = 160x120, 2 = 640x480");
+
+
+/* devices supported by this driver */
+static struct usb_device_id device_table[] = {
+	{USB_DEVICE(    -1,     -1)},	/* add your custom entry here ... */
+	{USB_DEVICE(0x08ca, 0x0109)},
+	{USB_DEVICE(0x041e, 0x4024)},
+	{USB_DEVICE(0x0d64, 0x0108)},
+	{USB_DEVICE(0x0546, 0x3187)},
+	{USB_DEVICE(0x0d64, 0x3108)},
+	{USB_DEVICE(0x0595, 0x4343)},
+	{USB_DEVICE(0x0bb0, 0x500d)},
+	{USB_DEVICE(0x0feb, 0x2004)},
+	{USB_DEVICE(0x055f, 0xb500)},
+	{USB_DEVICE(0x08ca, 0x2062)},
+	{USB_DEVICE(0x052b, 0x1a18)},
+	{USB_DEVICE(0x04c8, 0x0729)},
+	{USB_DEVICE(0x04f2, 0xa208)},
+	{USB_DEVICE(0x0784, 0x0040)},
+	{USB_DEVICE(0x06d6, 0x0034)},
+	{USB_DEVICE(0x0a17, 0x0062)},
+	{}			/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+
+/* devices supported by this driver */
+static struct {
+	int vendor;
+	int product;
+	int method;		/* which init method this camera uses */
+	const char *name;
+} devices[] = {
+	{     -1,     -1, 0, "Custom camera" }, /* ... and here ! */
+	{ 0x08ca, 0x0109, 0, "Aiptek PocketDV 3300 or Maxell Maxcam PRO DV3" },
+	{ 0x041e, 0x4024, 0, "Creative PC-CAM 880" },
+	{ 0x0d64, 0x0108, 0, "Aiptek Fidelity 3200 or Praktica DCZ 1.3 S or "
+					"Genius Digital Camera or "
+					"DXG Technology Fashion Cam" },
+	{ 0x0546, 0x3187, 0, "Polaroid iON 230" },
+	{ 0x0d64, 0x3108, 0, "Praktica Exakta DC 2200 or Genius G-Shot D211" },
+	{ 0x0595, 0x4343, 0, "Concord Eye-Q Duo 1300 or "
+					"Concord Eye-Q Duo 2000 or "
+					"Ricoh RDC-6000 or Fujifilm EX-10 or "
+					"Digitrex DSC 1300 or "
+					"Firstline FDC 2000" },
+	{ 0x0bb0, 0x500d, 0, "Concord EyeQ Go Wireless" },
+	{ 0x0feb, 0x2004, 0, "CRS Electronic 3.3 Digital Camera or "
+					"Packard Bell DSC-300" },
+	{ 0x055f, 0xb500, 0, "Mustek MDC 3000" },
+	{ 0x08ca, 0x2062, 2, "Aiptek PocketDV 5700" },
+	{ 0x052b, 0x1a18, 1, "Chiphead Megapix V12" },
+	{ 0x04c8, 0x0729, 0, "Konica Revio 2" },
+	{ 0x04f2, 0xa208, 0, "Creative PC-CAM 850" },
+	{ 0x0784, 0x0040, 1, "Traveler Slimline X5" },
+	{ 0x06d6, 0x0034, 0, "Trust Powerc@m 750" }, 
+	{ 0x0a17, 0x0062, 2, "Pentax Optio 50L" }, 
+	{      0,      0, 0, "" } /* end entry */
+};
+
+
+/* Camera stuff */
+struct zoran {
+	struct usb_device *udev;	/* save off the usb device pointer */
+	struct usb_interface *interface;/* the interface for this device */
+	struct video_device *vdev;	/* v4l video device */
+	u8 *framebuf;
+	int nb;
+	unsigned char *buffer;
+	int skip;
+	int brightness;
+	int width;
+	int height;
+	int method;
+	struct mutex lock;
+};
+
+
+/* function used to send initialisation commands to the camera */
+static int send_control_msg(struct usb_device *udev, u8 request, u16 value,
+			    u16 index, unsigned char *cp, u16 size)
+{
+	int status;
+
+	unsigned char *transfer_buffer = kmalloc(size, GFP_KERNEL);
+	if (!transfer_buffer) {
+		info("kmalloc(%d) failed", size);
+		return -ENOMEM;
+	}
+
+	memcpy(transfer_buffer, cp, size);
+
+	status = usb_control_msg(udev,
+				 usb_sndctrlpipe(udev, 0),
+				 request,
+				 USB_DIR_OUT | USB_TYPE_VENDOR |
+				 USB_RECIP_DEVICE, value, index,
+				 transfer_buffer, size, CTRL_TIMEOUT);
+
+	kfree(transfer_buffer);
+
+	if (status < 0)
+		info("Failed sending control message, error %d.", status);
+
+	return status;
+}
+
+
+/* Control messages sent to the camera to initialize it 
+ * and launch the capture */
+typedef struct {
+	unsigned int value;
+	unsigned int size;
+	unsigned char *bytes;
+} message;
+
+/* method 1 */
+static unsigned char m1d1[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+static unsigned char m1d2[] = { 0, 0, 0, 0, 0, 0 };
+static unsigned char m1d3[] = { 0, 0 };
+static message m1[] = {
+	{0x1f30, 0, NULL},
+	{0xd000, 0, NULL},
+	{0x3370, sizeof(m1d1), m1d1},
+	{0x2000, 0, NULL},
+	{0x2f0f, 0, NULL},
+	{0x2610, sizeof(m1d2), m1d2},
+	{0xe107, 0, NULL},
+	{0x2502, 0, NULL},
+	{0x1f70, 0, NULL},
+	{0xd000, 0, NULL},
+	{0x9a01, sizeof(m1d3), m1d3},
+	{-1, -1, NULL}
+};
+
+/* method 2 */
+static unsigned char m2d1[] = { 0xff, 0xff };
+static unsigned char m2d2[] = { 0x00, 0x00 };
+static message m2[] = {
+	{0x1f30, 0, NULL},
+	{0xd000, 0, NULL},
+	{0xf000, 0, NULL},
+	{0x2000, 0, NULL},
+	{0x2f0f, 0, NULL},
+	{0x2650, 0, NULL},
+	{0xe107, 0, NULL},
+	{0x2502, sizeof(m2d1), m2d1},
+	{0x1f70, 0, NULL},
+	{0xd000, 0, NULL},
+	{0xd000, 0, NULL},
+	{0xd000, 0, NULL},
+	{0x9a01, sizeof(m2d2), m2d2},
+	{-1, -1, NULL}
+};
+
+/* method 3 */
+static unsigned char m3d1[] = { 0xff, 0xff };
+static message m3[] = {
+	{0x1f30, 0, NULL},
+	{0xf000, 0, NULL},
+	{0x2000, 0, NULL},
+	{0x2f0f, 0, NULL},
+	{0x2650, 0, NULL},
+	{0xe107, 0, NULL},
+	{0x2502, sizeof(m3d1), m3d1},
+	{0x1f70, 0, NULL},
+	{-1, -1, NULL}
+};
+
+/* init table */
+static message *init[3] = { m1, m2, m3 };
+
+
+/* JPEG static data in header (Huffman table, etc) */
+static unsigned char header1[] = {
+	0xFF, 0xD8,
+	/*
+	0xFF, 0xE0, 0x00, 0x10, 'J', 'F', 'I', 'F', 
+	0x00, 0x01, 0x01, 0x00, 0x33, 0x8A, 0x00, 0x00, 0x33, 0x88,
+	*/
+	0xFF, 0xDB, 0x00, 0x84
+};
+static unsigned char header2[] = {
+	0xFF, 0xC4, 0x00, 0x1F, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 
+	0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+	0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 
+	0xFF, 0xC4, 0x00, 0xB5, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 
+	0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, 0x01, 
+	0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 
+	0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 
+	0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 0x24, 0x33, 
+	0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 
+	0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 
+	0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 
+	0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 
+	0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 
+	0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 
+	0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 
+	0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 
+	0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 
+	0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2, 
+	0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 
+	0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFF, 0xC4, 0x00, 0x1F, 
+	0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 
+	0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 
+	0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0xFF, 0xC4, 0x00, 0xB5, 
+	0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 
+	0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 
+	0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 
+	0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 
+	0x09, 0x23, 0x33, 0x52, 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 
+	0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, 
+	0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 
+	0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 
+	0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 
+	0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 
+	0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 
+	0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 
+	0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 
+	0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 
+	0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 
+	0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 
+	0xF8, 0xF9, 0xFA, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0xF0, 0x01, 
+	0x40, 0x03, 0x01, 0x21, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 
+	0xFF, 0xDA, 0x00, 0x0C, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 
+	0x00, 0x3F, 0x00
+};
+static unsigned char header3;
+
+
+
+/********************/
+/* V4L2 integration */
+/********************/
+
+/* this function reads a full JPEG picture synchronously
+ * TODO: do it asynchronously... */
+static int read_frame(struct zoran *cam, int framenum)
+{
+	int i, n, temp, head, size, actual_length;
+	unsigned char *ptr = NULL, *jpeg, swap;
+
+      redo:
+	/* hardware brightness */
+	n = send_control_msg(cam->udev, 1, 0x2001, 0, NULL, 0);
+	temp = (0x60 << 8) + 127 - cam->brightness;
+	n = send_control_msg(cam->udev, 1, temp, 0, NULL, 0);
+
+	/* during the first loop we are going to insert JPEG header */
+	head = 0;
+	/* this is the place in memory where we are going to build 
+	 * the JPEG image */
+	jpeg = cam->framebuf + framenum * ZORAN_MAX_FRAME_SIZE;
+	/* read data... */
+	do {
+		n = usb_bulk_msg(cam->udev,
+				 usb_rcvbulkpipe(cam->udev, 0x81),
+				 cam->buffer, BUFFER_SIZE, &actual_length,
+				 CTRL_TIMEOUT);
+		DBG("buffer : %d %d", cam->buffer[0], cam->buffer[1]);
+		DBG("bulk : n=%d size=%d", n, actual_length);
+		if (n < 0) {
+			info("error reading bulk msg");
+			return 0;
+		}
+		if (actual_length < 0 || actual_length > BUFFER_SIZE) {
+			info("wrong number of bytes");
+			return 0;
+		}
+
+		/* swap to good indian if camera needs it */
+		if (cam->method == 0)
+			for (i = 0; i < BUFFER_SIZE; i += 2) {
+				swap = cam->buffer[i];
+				cam->buffer[i] = cam->buffer[i + 1];
+				cam->buffer[i + 1] = swap;
+			}
+
+		/* write the JPEG header */
+		if (!head) {
+			DBG("jpeg header");
+			ptr = jpeg;
+			memcpy(ptr, header1, sizeof(header1));
+			ptr += sizeof(header1);
+			header3 = 0;
+			memcpy(ptr, &header3, 1);
+			ptr++;
+			memcpy(ptr, cam->buffer, 64);
+			ptr += 64;
+			header3 = 1;
+			memcpy(ptr, &header3, 1);
+			ptr++;
+			memcpy(ptr, cam->buffer + 64, 64);
+			ptr += 64;
+			memcpy(ptr, header2, sizeof(header2));
+			ptr += sizeof(header2);
+			memcpy(ptr, cam->buffer + 128,
+			       actual_length - 128);
+			ptr += actual_length - 128;
+			head = 1;
+			DBG("header : %d %d %d %d %d %d %d %d %d",
+			    cam->buffer[0], cam->buffer[1], cam->buffer[2],
+			    cam->buffer[3], cam->buffer[4], cam->buffer[5],
+			    cam->buffer[6], cam->buffer[7], cam->buffer[8]);
+		} else {
+			memcpy(ptr, cam->buffer, actual_length);
+			ptr += actual_length;
+		}
+	}
+	/* ... until there is no more */
+	while (actual_length == BUFFER_SIZE);
+
+	/* we skip the 2 first frames which are usually buggy */
+	if (cam->skip) {
+		cam->skip--;
+		goto redo;
+	}
+
+	/* go back to find the JPEG EOI marker */
+	size = ptr - jpeg;
+	ptr -= 2;
+	while (ptr > jpeg) {
+		if (*ptr == 0xFF && *(ptr + 1) == 0xD9
+		    && *(ptr + 2) == 0xFF)
+			break;
+		ptr--;
+	}
+	if (ptr == jpeg)
+		DBG("No EOI marker");
+
+	/* Sometimes there is junk data in the middle of the picture,
+	 * we want to skip this bogus frames */
+	while (ptr > jpeg) {
+		if (*ptr == 0xFF && *(ptr + 1) == 0xFF
+		    && *(ptr + 2) == 0xFF)
+			break;
+		ptr--;
+	}
+	if (ptr != jpeg) {
+		DBG("Bogus frame ? %d", cam->nb);
+		goto redo;
+	}
+
+	DBG("jpeg : %d %d %d %d %d %d %d %d",
+				jpeg[0], jpeg[1], jpeg[2], jpeg[3],
+				jpeg[4], jpeg[5], jpeg[6], jpeg[7]);
+
+	return size;
+}
+
+
+static ssize_t zoran_read(struct file *file, char *buf, size_t cnt,
+			  loff_t * ppos)
+{
+	unsigned long count = cnt;
+	struct video_device *vdev = video_devdata(file);
+	struct zoran *cam;
+
+	DBG("zoran_read: read %d bytes.", (int) count);
+
+	/* check if the camera is still there */
+	if (vdev == NULL)
+		return -ENODEV;
+	cam = video_get_drvdata(vdev);
+
+	if (!buf)
+		return -EINVAL;
+
+	if (!count)
+		return -EINVAL;
+
+	/* NoMan Sux ! */
+	count = read_frame(cam, 0);
+
+	if (copy_to_user(buf, cam->framebuf, count))
+		return -EFAULT;
+
+	return count;
+}
+
+
+static int zoran_ioctl(struct inode *inode, struct file *file,
+		       unsigned int ioctlnr, unsigned long ul_arg)
+{
+	void *arg = (void *) ul_arg;
+	struct video_device *vdev = video_devdata(file);
+	struct zoran *cam;
+
+	/* check if the camera is still there */
+	if (vdev == NULL)
+		return -ENODEV;
+	cam = video_get_drvdata(vdev);
+
+	switch (ioctlnr) {
+
+	case VIDIOC_QUERYCAP:{
+			struct v4l2_capability *cap = arg;
+			DBG("VIDIOC_QUERYCAP");
+			memset(cap, 0, sizeof(*cap));
+			strcpy(cap->driver, DRIVER_DESC);
+			cap->capabilities =
+			    V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE;
+			break;
+		}
+
+	case VIDIOC_ENUMINPUT:{
+			struct v4l2_input *i = arg;
+			DBG("VIDIOC_ENUMINPUT");
+			if (i->index != 0)
+				return -EINVAL;
+			memset(i, 0, sizeof(*i));
+			i->index = 0;
+			strcpy(i->name, DRIVER_DESC " Camera");
+			i->type = V4L2_INPUT_TYPE_CAMERA;
+			break;
+		}
+
+	case VIDIOC_G_INPUT:{
+			int *i = arg;
+			DBG("VIDIOC_G_INPUT");
+			*i = 0;
+			break;
+		}
+
+	case VIDIOC_S_INPUT:{
+			int *i = arg;
+			DBG("VIDIOC_S_INPUT: %d", *i);
+			if (*i != 0)
+				return -EINVAL;
+			break;
+		}
+
+	case VIDIOC_QUERYCTRL:{
+			struct v4l2_queryctrl *c = arg;
+			DBG("VIDIOC_QUERYCTRL: %d", c->id);
+			switch (c->id) {
+			case V4L2_CID_BRIGHTNESS:
+				c->type = V4L2_CTRL_TYPE_INTEGER;
+				strcpy(c->name, "Brightness");
+				c->minimum = 0;
+				c->maximum = 127;
+				c->step = 1;
+				c->default_value = cam->brightness;
+				c->flags = 0;
+				break;
+			default:
+				return -EINVAL;
+			}
+			break;
+		}
+
+	case VIDIOC_S_CTRL:{
+			struct v4l2_control *c = arg;
+			DBG("VIDIOC_S_CTRL: %d <- %d", c->id, c->value);
+			switch (c->id) {
+			case V4L2_CID_BRIGHTNESS:
+				cam->brightness = c->value;
+				break;
+			default:
+				return -EINVAL;
+			}
+			break;
+		}
+
+	case VIDIOC_G_CTRL:{
+			struct v4l2_control *c = arg;
+			DBG("VIDIOC_G_CTRL: %d -> %d", c->id, c->value);
+			switch (c->id) {
+			case V4L2_CID_BRIGHTNESS:
+				c->value = cam->brightness;
+				break;
+			default:
+				return -EINVAL;
+			}
+			break;
+		}
+
+	case VIDIOC_ENUM_FMT:{
+			struct v4l2_fmtdesc *f = arg;
+			DBG("VIDIOC_ENUM_FMT: index = %d type = %d",
+			    f->index, f->type);
+			if (f->index > 0)
+				return -EINVAL;
+			if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+				return -EINVAL;
+			memset(f, 0, sizeof(*f));
+			f->index = 0;
+			f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+			f->flags = V4L2_FMT_FLAG_COMPRESSED;
+			strcpy(f->description, "JPEG");
+			f->pixelformat = V4L2_PIX_FMT_JPEG;
+			break;
+		}
+
+	case VIDIOC_TRY_FMT:{
+			struct v4l2_format *f = arg;
+			DBG("VIDIOC_TRY_FMT: type = %d", f->type);
+			if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+				return -EINVAL;
+			if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_JPEG)
+				return -EINVAL;
+			if (f->fmt.pix.field != V4L2_FIELD_ANY &&
+			    f->fmt.pix.field != V4L2_FIELD_NONE)
+				return -EINVAL;
+			f->fmt.pix.field = V4L2_FIELD_NONE;
+			f->fmt.pix.width = cam->width;
+			f->fmt.pix.height = cam->height;
+			f->fmt.pix.bytesperline = f->fmt.pix.width * 2;
+			f->fmt.pix.sizeimage = f->fmt.pix.height *
+			    f->fmt.pix.bytesperline;
+			f->fmt.pix.colorspace = 0;
+			f->fmt.pix.priv = 0;
+			break;
+		}
+
+	case VIDIOC_G_FMT:{
+			struct v4l2_format *f = arg;
+			DBG("VIDIOC_G_FMT: type = %d (should be %d)",
+			    f->type, V4L2_BUF_TYPE_VIDEO_CAPTURE);
+			if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+				return -EINVAL;
+			memset(&f->fmt.pix, 0,
+			       sizeof(struct v4l2_pix_format));
+			f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+			f->fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG;
+			f->fmt.pix.field = V4L2_FIELD_NONE;
+			f->fmt.pix.width = cam->width;
+			f->fmt.pix.height = cam->height;
+			f->fmt.pix.bytesperline = f->fmt.pix.width * 2;
+			f->fmt.pix.sizeimage = f->fmt.pix.height *
+			    f->fmt.pix.bytesperline;
+			f->fmt.pix.colorspace = 0;
+			f->fmt.pix.priv = 0;
+			break;
+		}
+
+	case VIDIOC_S_FMT:{
+			struct v4l2_format *f = arg;
+			DBG("VIDIOC_S_FMT: type = %d (should be %d), "
+					"pixelformat = %d (should be %d), "
+					"field = %d (should be %d or %d) ",
+					f->type, V4L2_BUF_TYPE_VIDEO_CAPTURE,
+					f->fmt.pix.pixelformat,
+					V4L2_PIX_FMT_JPEG, f->fmt.pix.field,
+					V4L2_FIELD_ANY, V4L2_FIELD_NONE);
+			if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+				return -EINVAL;
+			if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_JPEG)
+				return -EINVAL;
+			if (f->fmt.pix.field != V4L2_FIELD_ANY &&
+			    f->fmt.pix.field != V4L2_FIELD_NONE)
+				return -EINVAL;
+			f->fmt.pix.field = V4L2_FIELD_NONE;
+			f->fmt.pix.width = cam->width;
+			f->fmt.pix.height = cam->height;
+			f->fmt.pix.bytesperline = f->fmt.pix.width * 2;
+			f->fmt.pix.sizeimage = f->fmt.pix.height *
+			    f->fmt.pix.bytesperline;
+			f->fmt.pix.colorspace = 0;
+			f->fmt.pix.priv = 0;
+			DBG("ok!");
+			break;
+		}
+
+	case VIDIOC_STREAMON:
+		DBG("VIDIOC_STREAMON");
+		break;
+
+	case VIDIOC_STREAMOFF:
+		DBG("VIDIOC_STREAMOFF");
+		break;
+
+	case VIDIOC_REQBUFS:
+		DBG("VIDIOC_REQBUFS not supported!");
+		return -EINVAL;
+	case VIDIOC_QUERYBUF:
+		DBG("VIDIOC_QUERYBUF not supported!");
+		return -EINVAL;
+	case VIDIOC_QBUF:
+		DBG("VIDIOC_QBUF not supported!");
+		return -EINVAL;
+	case VIDIOC_DQBUF:
+		DBG("VIDIOC_DQBUF not supported!");
+		return -EINVAL;
+
+	case VIDIOC_ENUMOUTPUT:
+	case VIDIOC_G_OUTPUT:
+	case VIDIOC_S_OUTPUT:
+	case VIDIOC_G_MODULATOR:
+	case VIDIOC_S_MODULATOR:
+
+	case VIDIOC_ENUMAUDIO:
+	case VIDIOC_G_AUDIO:
+	case VIDIOC_S_AUDIO:
+
+	case VIDIOC_ENUMAUDOUT:
+	case VIDIOC_G_AUDOUT:
+	case VIDIOC_S_AUDOUT:
+
+	case VIDIOC_ENUMSTD:
+	case VIDIOC_QUERYSTD:
+	case VIDIOC_G_STD:
+	case VIDIOC_S_STD:
+
+	case VIDIOC_G_TUNER:
+	case VIDIOC_S_TUNER:
+	case VIDIOC_G_FREQUENCY:
+	case VIDIOC_S_FREQUENCY:
+
+	case VIDIOC_OVERLAY:
+	case VIDIOC_G_FBUF:
+	case VIDIOC_S_FBUF:
+
+	case VIDIOC_G_PARM:
+	case VIDIOC_S_PARM:
+		DBG("skip: %d", ioctlnr);
+		return -EINVAL;
+
+	default:
+		DBG("V4L2 command not supported!: %d", ioctlnr);
+		return -ENOIOCTLCMD;
+
+	}
+
+	return 0;
+}
+
+
+/* open the camera */
+static int zoran_open(struct inode *inode, struct file *file)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct zoran *cam = video_get_drvdata(vdev);
+	struct usb_device *udev = cam->udev;
+	int i, err;
+
+	DBG("zoran_open");
+
+	cam->skip = 2;
+
+	err = video_exclusive_open(inode, file);
+	if (err < 0)
+		return err;
+
+	if (!cam->framebuf) {
+		cam->framebuf =
+		    vmalloc_32(ZORAN_MAX_FRAME_SIZE * ZORAN_FRAMES);
+		if (!cam->framebuf) {
+			info("vmalloc_32 failed!");
+			return -ENOMEM;
+		}
+	}
+
+	mutex_lock(&cam->lock);
+	for (i = 0; init[cam->method][i].size != -1; i++) {
+		err =
+		    send_control_msg(udev, 1, init[cam->method][i].value,
+				     0, init[cam->method][i].bytes,
+				     init[cam->method][i].size);
+		if (err < 0) {
+			info("error during open sequence: %d", i);
+			mutex_unlock(&cam->lock);
+			return err;
+		}
+	}
+
+	file->private_data = vdev;
+
+	/* Added some delay here, since opening/closing the camera quickly,
+	 * like Ekiga does during its startup, can crash the webcam
+	 */
+	mdelay(100);
+
+	mutex_unlock(&cam->lock);
+	return 0;
+}
+
+
+/* release the camera */
+static int zoran_release(struct inode *inode, struct file *file)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct zoran *cam;
+	struct usb_device *udev;
+	int i, err;
+
+	DBG("zoran_release");
+
+	/* check if the camera is still there */
+	if (vdev == NULL)
+		return -ENODEV;
+	cam = video_get_drvdata(vdev);
+
+	udev = cam->udev;
+
+	mutex_lock(&cam->lock);
+	for (i = 0; i < 2; i++) {
+		err =
+		    send_control_msg(udev, 1, init[cam->method][i].value,
+				     0, init[i][cam->method].bytes,
+				     init[cam->method][i].size);
+		if (err < 0) {
+			info("error during release sequence");
+			mutex_unlock(&cam->lock);
+			return err;
+		}
+	}
+
+	file->private_data = NULL;
+	video_exclusive_release(inode, file);
+
+	/* Added some delay here, since opening/closing the camera quickly,
+	 * like Ekiga does during its startup, can crash the webcam
+	 */
+	mdelay(100);
+
+	mutex_unlock(&cam->lock);
+	return 0;
+}
+
+
+static int zoran_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	void *pos;
+	unsigned long start = vma->vm_start;
+	unsigned long size = vma->vm_end - vma->vm_start;
+	struct video_device *vdev = video_devdata(file);
+	struct zoran *cam;
+
+	DBG("zr364xx_mmap: %ld\n", size);
+
+	/* check if the camera is still there */
+	if (vdev == NULL)
+		return -ENODEV;
+	cam = video_get_drvdata(vdev);
+	
+	pos = cam->framebuf;
+	while (size > 0) {
+		if (vm_insert_page(vma, start, vmalloc_to_page(pos)))
+			return -EAGAIN;
+		start += PAGE_SIZE;
+		pos += PAGE_SIZE;
+		if (size > PAGE_SIZE)
+			size -= PAGE_SIZE;
+		else
+			size = 0;
+	}
+
+	return 0;
+}
+
+
+static struct file_operations zoran_fops = {
+	.owner = THIS_MODULE,
+	.open = zoran_open,
+	.release = zoran_release,
+	.read = zoran_read,
+	.mmap = zoran_mmap,
+	.ioctl = zoran_ioctl,
+	.llseek = no_llseek,
+};
+
+static struct video_device zoran_template = {
+	.owner = THIS_MODULE,
+	.name = DRIVER_DESC,
+	.type = VID_TYPE_CAPTURE,
+	.hardware = VID_HARDWARE_ZR364XX,
+	.fops = &zoran_fops,
+	.release = video_device_release,
+	.minor = -1
+};
+
+
+
+/*******************/
+/* USB integration */
+/*******************/
+
+static int zoran_probe(struct usb_interface *intf,
+		       const struct usb_device_id *id)
+{
+	struct usb_device *udev = interface_to_usbdev(intf);
+	struct zoran *cam = NULL;
+	int i;
+
+	DBG("probing...");
+
+	for (i = 0; devices[i].vendor; i++)
+		if ((udev->descriptor.idVendor == devices[i].vendor)
+		    && (udev->descriptor.idProduct == devices[i].product))
+			break;
+
+	if (!devices[i].vendor)
+		return -ENODEV;
+
+	info(DRIVER_DESC " webcam plugged");
+	info("model %s detected", devices[i].name);
+
+	if ((cam = kmalloc(sizeof(struct zoran), GFP_KERNEL)) == NULL) {
+		info("cam: out of memory !");
+		return -ENODEV;
+	}
+	memset(cam, 0x00, sizeof(struct zoran));
+	/* save the init method used by this camera */
+	cam->method = devices[i].method;
+
+	cam->vdev = video_device_alloc();
+	if (cam->vdev == NULL) {
+		info("cam->vdev: out of memory !");
+		return -ENODEV;
+	}
+	memcpy(cam->vdev, &zoran_template, sizeof(zoran_template));
+	video_set_drvdata(cam->vdev, cam);
+
+	cam->udev = udev;
+
+	if ((cam->buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL)) == NULL) {
+		info("cam->buffer: out of memory !");
+		return -ENODEV;
+	}
+
+	switch (mode) {
+	case 1:
+		info("160x120 mode selected");
+		cam->width = 160;
+		cam->height = 120;
+		break;
+	case 2:
+		info("640x480 mode selected");
+		cam->width = 640;
+		cam->height = 480;
+		break;
+	default:
+		info("320x240 mode selected");
+		cam->width = 320;
+		cam->height = 240;
+		break;
+	}
+
+	m1d1[0] = mode;
+	m2[2].value = 0xf000 + mode;
+	m3[1].value = 0xf000 + mode;
+	header2[437] = cam->height / 256;
+	header2[438] = cam->height % 256;
+	header2[439] = cam->width / 256;
+	header2[440] = cam->width % 256;
+
+	cam->nb = 0;
+	cam->brightness = 64;
+
+	if (video_register_device(cam->vdev, VFL_TYPE_GRABBER, -1) == -1) {
+		info("video_register_device failed");
+		return -ENODEV;
+	}
+
+	usb_set_intfdata(intf, cam);
+	mutex_init(&cam->lock);
+
+	info(DRIVER_DESC " controlling video device %d", cam->vdev->minor);
+	return 0;
+}
+
+
+static void zoran_disconnect(struct usb_interface *intf)
+{
+	struct zoran *cam = usb_get_intfdata(intf);
+	usb_set_intfdata(intf, NULL);
+	dev_set_drvdata(&intf->dev, NULL);
+	info(DRIVER_DESC " webcam unplugged");
+	if (cam->vdev)
+		video_unregister_device(cam->vdev);
+	cam->vdev = NULL;
+	kfree(cam->buffer);
+	if (cam->framebuf)
+		vfree(cam->framebuf);
+	kfree(cam);
+}
+
+
+
+/**********************/
+/* Module integration */
+/**********************/
+
+static struct usb_driver zoran_driver = {
+	.name = "zoran",
+	.probe = zoran_probe,
+	.disconnect = zoran_disconnect,
+	.id_table = device_table
+};
+
+
+static int __init zoran_init(void)
+{
+	int retval;
+	retval = usb_register(&zoran_driver) < 0;
+	if (retval)
+		info("usb_register failed!");
+	else
+		info(DRIVER_DESC " module loaded");
+	return retval;
+}
+
+
+static void __exit zoran_exit(void)
+{
+	info(DRIVER_DESC " module unloaded");
+	usb_deregister(&zoran_driver);
+}
+
+
+module_init(zoran_init);
+module_exit(zoran_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff -uprN -X linux-2.6.20-vanilla/Documentation/dontdiff linux-2.6.20-vanilla/include/linux/videodev.h linux-2.6.20/include/linux/videodev.h
--- linux-2.6.20-vanilla/include/linux/videodev.h	2007-02-04 19:44:54.000000000 +0100
+++ linux-2.6.20/include/linux/videodev.h	2007-02-09 10:54:12.000000000 +0100
@@ -335,6 +335,7 @@ struct video_code
 #define VID_HARDWARE_SAA7114H   37
 #define VID_HARDWARE_SN9C102	38
 #define VID_HARDWARE_ARV	39
+#define VID_HARDWARE_ZR364XX	40
 
 #endif /* CONFIG_VIDEO_V4L1_COMPAT */
 
diff -uprN -X linux-2.6.20-vanilla/Documentation/dontdiff linux-2.6.20-vanilla/MAINTAINERS linux-2.6.20/MAINTAINERS
--- linux-2.6.20-vanilla/MAINTAINERS	2007-02-04 19:44:54.000000000 +0100
+++ linux-2.6.20/MAINTAINERS	2007-02-09 11:31:13.000000000 +0100
@@ -3538,6 +3538,14 @@ L:	[email protected]
 W:	http://linux-lc100020.sourceforge.net
 S:	Maintained
 
+USB ZR364XX DRIVER
+P:	Antoine Jacquet
+M:	[email protected]
+L:	[email protected]
+L:	[email protected]
+W:	http://royale.zerezo.com/zr364xx/
+S:	Maintained
+
 USER-MODE LINUX
 P:	Jeff Dike
 M:	[email protected]

--
video4linux-list mailing list
Unsubscribe mailto:[email protected]?subject=unsubscribe
https://www.redhat.com/mailman/listinfo/video4linux-list
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.