Add support for OmniVision OV534 based USB cameras.
From: | majortrips@gmail.com | |
To: | video4linux-list@redhat.com | |
Subject: | [PATCH] Add support for OmniVision OV534 based USB cameras. | |
Date: | Sat, 16 Aug 2008 00:00:23 -0500 | |
Message-ID: | <20080816050023.GB30725@thumper> | |
Archive‑link: | Article |
Adds suport for OmniVision OV534 based cameras: - Hercules Blog Webcam - Hercules Dualpix HD Webcam - Sony HD PS3 Eye (SLEH 00201) Currently only supports 640x480 YUYV non-interlaced output. Signed-off-by: Mark Ferrell <majortrips@gmail.com> --- diff --git a/linux/Documentation/video4linux/ov534.txt b/linux/Documentation/video4linux/ov534.txt new file mode 100644 --- /dev/null +++ b/linux/Documentation/video4linux/ov534.txt @@ -0,0 +1,30 @@ +OmniVision OV534 USB2.0 Camera driver. +Written by Mark Ferrell <majortrips--a.t--gmail.com> + +The OV534 is the chipset used by the Hercules Blog Webcam and Hercules Dualpix +HD Webcam. A variation of this chip, the ov538, is used Sony's HD PS3 Eye +(SLEH 00201). There is currently no known public information about what is +different between the ov534 and the ov538. + +The original code for initializating the camera and retrieving an image from it +was written by Jim Paris on ps2dev.org and submitted under the GPL. It is my +understanding that Jim sniffed the USB traffic to see what the Playstation3 was +sending to the camera to initialize it and to start/stop captures. + +The V4L interface of the driver was based on the vivi driver with the only +functionality changes being the addition of USB support, the addition of Jim's +control code, and a rewrite of the buffer fill code to pull directly from the +camera. + +The ov534 outputs frames in YUYV format, non-interlaced, at 640x480. This +format does not yet have wide support among user-land applications. Though at +the time of this writing xawtv was known to work correctly. + +Online information about the ov534 claims that it is capable of handling +320x240 resolutions as well as supporting JPEG compression, but the commands to +toggle the size and compression are currently unknown. + +If you use the camera with Xawtv you need to force the output resolution to +640x480 with the geometry command. i.e. xawtv -geometry 640x480 + +Good Luck diff --git a/linux/drivers/media/video/Kconfig b/linux/drivers/media/video/Kconfig --- a/linux/drivers/media/video/Kconfig +++ b/linux/drivers/media/video/Kconfig @@ -970,6 +970,17 @@ config USB_S2255 Say Y here if you want support for the Sensoray 2255 USB device. This driver can be compiled as a module, called s2255drv. +config USB_OV534 + tristate "USB OV534 Camera support" + depends on VIDEO_V4L2 + select VIDEOBUF_VMALLOC + default n + help + Say Y here if you want support for OV534 based USB cameras. + Hercules Blog Webcam, Hercules Dualpix HD Webcam, and the + Sony HD Eye for PS3 (SLEH 00201). + This driver can be compiled as a module, called ov534. + endif # V4L_USB_DRIVERS endif # VIDEO_CAPTURE_DRIVERS diff --git a/linux/drivers/media/video/Makefile b/linux/drivers/media/video/Makefile --- a/linux/drivers/media/video/Makefile +++ b/linux/drivers/media/video/Makefile @@ -110,6 +110,7 @@ obj-$(CONFIG_VIDEO_TCM825X) += tcm825x.o obj-$(CONFIG_USB_DABUSB) += dabusb.o obj-$(CONFIG_USB_OV511) += ov511.o +obj-$(CONFIG_USB_OV534) += ov534.o obj-$(CONFIG_USB_SE401) += se401.o obj-$(CONFIG_USB_STV680) += stv680.o obj-$(CONFIG_USB_W9968CF) += w9968cf.o diff --git a/linux/drivers/media/video/ov534.c b/linux/drivers/media/video/ov534.c new file mode 100644 --- /dev/null +++ b/linux/drivers/media/video/ov534.c @@ -0,0 +1,1411 @@ +/* + * OmniVision OV534 USB Camera driver + * + * Cameras: + * Hercules Blog Webcam + * Hercules Dualpix HD Webcam + * Sony HD Eye for PS3 (SLEH 00201) + * + * Copyright (c) 2008 Mark Ferrell <majortrips--a.t--gmail.com> + * + * Camera control based on eye.c by Jim Paris <jim--a.t--jtan.com> + * + * V4L2 code based on vivi.c, see original file for credits. + * + * 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/usb.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/highmem.h> +#include <linux/videodev2.h> +#ifdef CONFIG_VIDEO_V4L1_COMPAT +/* Include V4L1 specific functions. Should be removed soon */ +#include <linux/videodev.h> +#endif +#include <media/videobuf-vmalloc.h> +#include <media/v4l2-common.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-ioctl.h> +#include <linux/kthread.h> +#include <linux/freezer.h> + +#define OV534_MODULE_NAME "ov534" +#define OV534_MODULE_DESC "OmniVision OV534" +#define OV534_AUTHOR "Mark Ferrell" + +#define OV534_MAJOR_VERSION 0 +#define OV534_MINOR_VERSION 0 +#define OV534_RELEASE 5 +#define OV534_VERSION KERNEL_VERSION(OV534_MAJOR_VERSION, OV534_MINOR_VERSION, OV534_RELEASE) + +#define OV534_REG_ADDRESS 0xf1 /* ? */ +#define OV534_REG_SUBADDR 0xf2 +#define OV534_REG_WRITE 0xf3 +#define OV534_REG_READ 0xf4 +#define OV534_REG_OPERATION 0xf5 +#define OV534_REG_STATUS 0xf6 + +#define OV534_OP_WRITE_3 0x37 +#define OV534_OP_WRITE_2 0x33 +#define OV534_OP_READ_2 0xf9 + +/* Wake up at about 30 fps */ +#define CTRL_TIMEOUT 500 +#define WAKE_NUMERATOR 30 +#define WAKE_DENOMINATOR 1001 + + +/* globals */ +static unsigned int vid_limit = 16; /* Video memory limit, in Mb */ +static struct video_device ov534; /* Video device */ +static int video_nr = -1; /* /dev/videoN, -1 for autodetect */ + +/* module params */ +module_param_named(debug, ov534.debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level [0-4]"); + +/* supported controls */ +static struct v4l2_queryctrl ov534_qctrl[] = { + { + .id = V4L2_CID_AUDIO_VOLUME, + .name = "Volume", + .minimum = 0, + .maximum = 65535, + .step = 65535/100, + .default_value = 65535, + .flags = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, { + .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Brightness", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 127, + .flags = 0, + }, { + .id = V4L2_CID_CONTRAST, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Contrast", + .minimum = 0, + .maximum = 255, + .step = 0x1, + .default_value = 0x10, + .flags = 0, + }, { + .id = V4L2_CID_SATURATION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Saturation", + .minimum = 0, + .maximum = 255, + .step = 0x1, + .default_value = 127, + .flags = 0, + }, { + .id = V4L2_CID_HUE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Hue", + .minimum = -128, + .maximum = 127, + .step = 0x1, + .default_value = 0, + .flags = 0, + } +}; + +static int qctl_regs[ARRAY_SIZE(ov534_qctrl)]; +#define PDEBUG(level, fmt, args...) \ + if (ov534.debug >= level) info("[%s:%d] " fmt, __PRETTY_FUNCTION__, __LINE__ , ## args) + +static struct usb_device_id device_table[] = { + {USB_DEVICE(0x06f8, 0x3002)}, /* Hercules Blog Webcam */ + {USB_DEVICE(0x06f8, 0x3003)}, /* Hercules Dualpix HD Weblog */ + {USB_DEVICE(0x1415, 0x2000)}, /* Sony HD Eye for PS3 (SLEH 00201) */ + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, device_table); + +struct ov534_fmt { + char *name; + u32 fourcc; + int depth; + enum v4l2_field field; +}; + +static struct ov534_fmt format[] = { + { .name = "4:2:2, packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .field = V4L2_FIELD_NONE, + }, +}; + +struct ov534_buffer { + struct videobuf_buffer vb; + struct ov534_fmt *fmt; +}; + +struct ov534_dmaqueue { + struct list_head active; + + /* thread for generating video stream*/ + struct task_struct *kthread; + wait_queue_head_t wq; + /* Counters to control fps rate */ + int frame; + int ini_jiffies; +}; + +static LIST_HEAD(ov534_devlist); + +struct ov534_dev { + struct list_head ov534_devlist; + + spinlock_t slock; + struct mutex mutex; + + int users; + + /* various device info */ + struct video_device *vfd; + struct usb_device *udev; + struct usb_interface *interface; + struct ov534_dmaqueue vidq; + + /* Several counters */ + int h, m, s, ms; + unsigned long jiffies; + char timestr[13]; + +}; + +struct ov534_fh { + struct ov534_dev *dev; + + /* video capture */ + struct ov534_fmt *fmt; + unsigned int width, height; + struct videobuf_queue vb_vidq; + + enum v4l2_buf_type type; +}; + + +/* ------------------------------------------------------------------ + Camera operations + ------------------------------------------------------------------*/ + +static void ov534_reg_write(struct usb_device *udev, u16 reg, u16 val) +{ + u16 data = val; + int ret; + + PDEBUG(2, "reg=0x%04x, val=0%04x", reg, val); + ret = usb_control_msg(udev, + usb_sndctrlpipe(udev, 0), + 0x1, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0, reg, &data, 1, CTRL_TIMEOUT); + if (ret < 0) + PDEBUG(1, "write failed"); +} + +static u16 ov534_reg_read(struct usb_device *udev, u16 reg) +{ + u16 data; + int ret; + + ret = usb_control_msg(udev, + usb_rcvctrlpipe(udev, 0), + 0x1, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0, reg, &data, 1, CTRL_TIMEOUT); + PDEBUG(2, "reg=0x%04x, data=0x%04x", reg, data); + if (ret < 0) + PDEBUG(1, "read failed"); + return data; +} + +static void ov534_reg_verify_write(struct usb_device *udev, u16 reg, u16 val) +{ + u16 data; + + ov534_reg_write(udev, reg, val); + data = ov534_reg_read(udev, reg); + if (data != val) { + PDEBUG(1, "unexpected result from read: 0x%04x != 0x%04x", + val, data); + } +} + +/* Two bits control LED: 0x21 bit 7 and 0x23 bit 7. + * (direction and output)? */ +static void ov534_set_led(struct usb_device *udev, int status) +{ + u16 data; + + PDEBUG(2, "led status: %d", status); + + data = ov534_reg_read(udev, 0x21); + data |= 0x80; + ov534_reg_write(udev, 0x21, data); + + data = ov534_reg_read(udev, 0x23); + if (status) { + data |= 0x80; + } else { + data &= ~(0x80); + } + ov534_reg_write(udev, 0x23, data); +} + +static int sccb_check_status(struct usb_device *udev) +{ + u16 data; + int i; + + for (i = 0; i < 5; i++) { + data = ov534_reg_read(udev, OV534_REG_STATUS); + switch (data & 0xFF) { + case 0x00: return 1; + case 0x04: return 0; + case 0x03: break; + default: + PDEBUG(1, "sccb status 0x%02x, attempt %d/5\n", + data, i+1); + } + } + return 0; +} + +static void sccb_reg_write(struct usb_device *udev, u16 reg, u16 val) +{ + PDEBUG(2, "reg: 0x%04x, val: 0x%04x", reg, val); + ov534_reg_write(udev, OV534_REG_SUBADDR, reg); + ov534_reg_write(udev, OV534_REG_WRITE, val); + ov534_reg_write(udev, OV534_REG_OPERATION, OV534_OP_WRITE_3); + + if (!sccb_check_status(udev)) { + PDEBUG(1, "sccb_reg_write failed"); + } +} + +static void ov534_setup(struct usb_device *udev) +{ + ov534_reg_verify_write(udev, 0xe7, 0x3a); + + ov534_reg_write(udev, OV534_REG_ADDRESS, 0x60); + ov534_reg_write(udev, OV534_REG_ADDRESS, 0x60); + ov534_reg_write(udev, OV534_REG_ADDRESS, 0x60); + ov534_reg_write(udev, OV534_REG_ADDRESS, 0x42); + + ov534_reg_verify_write(udev, 0xc2,0x0c); + ov534_reg_verify_write(udev, 0x88,0xf8); + ov534_reg_verify_write(udev, 0xc3,0x69); + ov534_reg_verify_write(udev, 0x89,0xff); + ov534_reg_verify_write(udev, 0x76,0x03); + ov534_reg_verify_write(udev, 0x92,0x01); + ov534_reg_verify_write(udev, 0x93,0x18); + ov534_reg_verify_write(udev, 0x94,0x10); + ov534_reg_verify_write(udev, 0x95,0x10); + ov534_reg_verify_write(udev, 0xe2,0x00); + ov534_reg_verify_write(udev, 0xe7,0x3e); + + ov534_reg_write(udev, 0x1c,0x0a); + ov534_reg_write(udev, 0x1d,0x22); + ov534_reg_write(udev, 0x1d,0x06); + + ov534_reg_verify_write(udev, 0x96,0x00); + + ov534_reg_write(udev, 0x97,0x20); + ov534_reg_write(udev, 0x97,0x20); + ov534_reg_write(udev, 0x97,0x20); + ov534_reg_write(udev, 0x97,0x0a); + ov534_reg_write(udev, 0x97,0x3f); + ov534_reg_write(udev, 0x97,0x4a); + ov534_reg_write(udev, 0x97,0x20); + ov534_reg_write(udev, 0x97,0x15); + ov534_reg_write(udev, 0x97,0x0b); + + ov534_reg_verify_write(udev, 0x8e,0x40); + ov534_reg_verify_write(udev, 0x1f,0x81); + ov534_reg_verify_write(udev, 0x34,0x05); + ov534_reg_verify_write(udev, 0xe3,0x04); + ov534_reg_verify_write(udev, 0x88,0x00); + ov534_reg_verify_write(udev, 0x89,0x00); + ov534_reg_verify_write(udev, 0x76,0x00); + ov534_reg_verify_write(udev, 0xe7,0x2e); + ov534_reg_verify_write(udev, 0x31,0xf9); + ov534_reg_verify_write(udev, 0x25,0x42); + ov534_reg_verify_write(udev, 0x21,0xf0); + + ov534_reg_write(udev, 0x1c,0x00); + ov534_reg_write(udev, 0x1d,0x40); + ov534_reg_write(udev, 0x1d,0x02); + ov534_reg_write(udev, 0x1d,0x00); + ov534_reg_write(udev, 0x1d,0x02); + ov534_reg_write(udev, 0x1d,0x57); + ov534_reg_write(udev, 0x1d,0xff); + + ov534_reg_verify_write(udev, 0x8d,0x1c); + ov534_reg_verify_write(udev, 0x8e,0x80); + ov534_reg_verify_write(udev, 0xe5,0x04); + + ov534_set_led(udev, 1); + + sccb_reg_write(udev, 0x12,0x80); + sccb_reg_write(udev, 0x11,0x01); + sccb_reg_write(udev, 0x11,0x01); + sccb_reg_write(udev, 0x11,0x01); + sccb_reg_write(udev, 0x11,0x01); + sccb_reg_write(udev, 0x11,0x01); + sccb_reg_write(udev, 0x11,0x01); + sccb_reg_write(udev, 0x11,0x01); + sccb_reg_write(udev, 0x11,0x01); + sccb_reg_write(udev, 0x11,0x01); + sccb_reg_write(udev, 0x11,0x01); + sccb_reg_write(udev, 0x11,0x01); + + ov534_set_led(udev, 0); + + sccb_reg_write(udev, 0x3d,0x03); + sccb_reg_write(udev, 0x17,0x26); + sccb_reg_write(udev, 0x18,0xa0); + sccb_reg_write(udev, 0x19,0x07); + sccb_reg_write(udev, 0x1a,0xf0); + sccb_reg_write(udev, 0x32,0x00); + sccb_reg_write(udev, 0x29,0xa0); + sccb_reg_write(udev, 0x2c,0xf0); + sccb_reg_write(udev, 0x65,0x20); + sccb_reg_write(udev, 0x11,0x01); + sccb_reg_write(udev, 0x42,0x7f); + sccb_reg_write(udev, 0x63,0xe0); + sccb_reg_write(udev, 0x64,0xff); + sccb_reg_write(udev, 0x66,0x00); + sccb_reg_write(udev, 0x13,0xf0); + sccb_reg_write(udev, 0x0d,0x41); + sccb_reg_write(udev, 0x0f,0xc5); + sccb_reg_write(udev, 0x14,0x11); + + ov534_set_led(udev, 1); + + sccb_reg_write(udev, 0x22,0x7f); + sccb_reg_write(udev, 0x23,0x03); + sccb_reg_write(udev, 0x24,0x40); + sccb_reg_write(udev, 0x25,0x30); + sccb_reg_write(udev, 0x26,0xa1); + sccb_reg_write(udev, 0x2a,0x00); + sccb_reg_write(udev, 0x2b,0x00); + sccb_reg_write(udev, 0x6b,0xaa); + sccb_reg_write(udev, 0x13,0xff); + + ov534_set_led(udev, 0); + + sccb_reg_write(udev, 0x90,0x05); + sccb_reg_write(udev, 0x91,0x01); + sccb_reg_write(udev, 0x92,0x03); + sccb_reg_write(udev, 0x93,0x00); + sccb_reg_write(udev, 0x94,0x60); + sccb_reg_write(udev, 0x95,0x3c); + sccb_reg_write(udev, 0x96,0x24); + sccb_reg_write(udev, 0x97,0x1e); + sccb_reg_write(udev, 0x98,0x62); + sccb_reg_write(udev, 0x99,0x80); + sccb_reg_write(udev, 0x9a,0x1e); + sccb_reg_write(udev, 0x9b,0x08); + sccb_reg_write(udev, 0x9c,0x20); + sccb_reg_write(udev, 0x9e,0x81); + + ov534_set_led(udev, 1); + + sccb_reg_write(udev, 0xa6,0x04); + sccb_reg_write(udev, 0x7e,0x0c); + sccb_reg_write(udev, 0x7f,0x16); + sccb_reg_write(udev, 0x80,0x2a); + sccb_reg_write(udev, 0x81,0x4e); + sccb_reg_write(udev, 0x82,0x61); + sccb_reg_write(udev, 0x83,0x6f); + sccb_reg_write(udev, 0x84,0x7b); + sccb_reg_write(udev, 0x85,0x86); + sccb_reg_write(udev, 0x86,0x8e); + sccb_reg_write(udev, 0x87,0x97); + sccb_reg_write(udev, 0x88,0xa4); + sccb_reg_write(udev, 0x89,0xaf); + sccb_reg_write(udev, 0x8a,0xc5); + sccb_reg_write(udev, 0x8b,0xd7); + sccb_reg_write(udev, 0x8c,0xe8); + sccb_reg_write(udev, 0x8d,0x20); + + sccb_reg_write(udev, 0x0c,0x90); + + + ov534_reg_verify_write(udev, 0xc0,0x50); + ov534_reg_verify_write(udev, 0xc1,0x3c); + ov534_reg_verify_write(udev, 0xc2,0x0c); + + ov534_set_led(udev, 1); + + sccb_reg_write(udev, 0x2b,0x00); + sccb_reg_write(udev, 0x22,0x7f); + sccb_reg_write(udev, 0x23,0x03); + sccb_reg_write(udev, 0x11,0x01); + sccb_reg_write(udev, 0x0c,0xd0); + sccb_reg_write(udev, 0x64,0xff); + sccb_reg_write(udev, 0x0d,0x41); + + sccb_reg_write(udev, 0x14,0x41); + sccb_reg_write(udev, 0x0e,0xcd); + sccb_reg_write(udev, 0xac,0xbf); + sccb_reg_write(udev, 0x8e,0x00); + sccb_reg_write(udev, 0x0c,0xd0); + + ov534_reg_write(udev, 0xe0,0x09); + ov534_set_led(udev, 0); +} + + +/* ------------------------------------------------------------------ + DMA and thread functions + ------------------------------------------------------------------*/ + +static void ov534_fillbuff(struct ov534_dev *dev, struct ov534_buffer *buf) +{ + int size = ((buf->vb.height * buf->vb.width * 16) >> 3) - 4; + struct timeval ts; + unsigned char *tmpbuf; + void *vbuf = videobuf_to_vmalloc(&buf->vb); + int ret, len; + + if (!vbuf) + return; + + tmpbuf = kmalloc(size + 4, GFP_ATOMIC); + if (!tmpbuf) + return; + + PDEBUG(2, "reading frame, size=%d", size); + ov534_reg_write(dev->udev, 0xe0, 0x00); + ret = usb_bulk_msg(dev->udev, usb_rcvbulkpipe(dev->udev, 0x1), + tmpbuf, size, &len, CTRL_TIMEOUT); + ov534_reg_write(dev->udev, 0xe0, 0x09); + if (ret < 0) { + PDEBUG(1, "error reading bulk msg (%d)", ret); + } else { + PDEBUG(2, "read=%d, expected=%d", len, size); + } + + memcpy(vbuf, tmpbuf, size); + + kfree(tmpbuf); + + /* Updates stream time */ + + dev->ms += jiffies_to_msecs(jiffies-dev->jiffies); + dev->jiffies = jiffies; + if (dev->ms >= 1000) { + dev->ms -= 1000; + dev->s++; + if (dev->s >= 60) { + dev->s -= 60; + dev->m++; + if (dev->m > 60) { + dev->m -= 60; + dev->h++; + if (dev->h > 24) + dev->h -= 24; + } + } + } + sprintf(dev->timestr, "%02d:%02d:%02d:%03d", + dev->h, dev->m, dev->s, dev->ms); + + PDEBUG(2, "ov534 fill at %s: Buffer 0x%08lx size=%d", + dev->timestr, (unsigned long)tmpbuf, size); + + /* Advice that buffer was filled */ + buf->vb.field_count++; + do_gettimeofday(&ts); + buf->vb.ts = ts; + buf->vb.state = VIDEOBUF_DONE; +} + + +static void ov534_thread_tick(struct ov534_fh *fh) +{ + struct ov534_buffer *buf; + struct ov534_dev *dev = fh->dev; + struct ov534_dmaqueue *dma_q = &dev->vidq; + + unsigned long flags = 0; + + PDEBUG(3, "Thread tick"); + + spin_lock_irqsave(&dev->slock, flags); + if (list_empty(&dma_q->active)) { + PDEBUG(1, "No active queue to serve"); + goto unlock; + } + + buf = list_entry(dma_q->active.next, + struct ov534_buffer, vb.queue); + + /* Nobody is waiting on this buffer, return */ + if (!waitqueue_active(&buf->vb.done)) + goto unlock; + + list_del(&buf->vb.queue); + + do_gettimeofday(&buf->vb.ts); + + /* Fill buffer */ + ov534_fillbuff(dev, buf); + PDEBUG(2, "filled buffer %p", buf); + + wake_up(&buf->vb.done); + PDEBUG(2, "[%p/%d] wakeup", buf, buf->vb. i); +unlock: + spin_unlock_irqrestore(&dev->slock, flags); + return; +} + +#define frames_to_ms(frames) \ + ((frames * WAKE_NUMERATOR * 1000) / WAKE_DENOMINATOR) + +static void ov534_sleep(struct ov534_fh *fh) +{ + struct ov534_dev *dev = fh->dev; + struct ov534_dmaqueue *dma_q = &dev->vidq; + int timeout; + DECLARE_WAITQUEUE(wait, current); + + PDEBUG(2, "dma_q=0x%08lx\n", (unsigned long)dma_q); + + add_wait_queue(&dma_q->wq, &wait); + if (kthread_should_stop()) + goto stop_task; + + /* Calculate time to wake up */ + timeout = msecs_to_jiffies(frames_to_ms(1)); + + ov534_thread_tick(fh); + + schedule_timeout_interruptible(timeout); + +stop_task: + remove_wait_queue(&dma_q->wq, &wait); + try_to_freeze(); +} + +static int ov534_thread(void *data) +{ + struct ov534_fh *fh = data; + + PDEBUG(3, "thread started\n"); + + set_freezable(); + + for (;;) { + ov534_sleep(fh); + + if (kthread_should_stop()) + break; + } + PDEBUG(3, "thread: exit\n"); + return 0; +} + +static int ov534_start_thread(struct ov534_fh *fh) +{ + struct ov534_dev *dev = fh->dev; + struct ov534_dmaqueue *dma_q = &dev->vidq; + + dma_q->frame = 0; + dma_q->ini_jiffies = jiffies; + + PDEBUG(3, "begin"); + + dma_q->kthread = kthread_run(ov534_thread, fh, "ov534"); + + if (IS_ERR(dma_q->kthread)) { + printk(KERN_ERR "ov534: kernel_thread() failed\n"); + return PTR_ERR(dma_q->kthread); + } + /* Wakes thread */ + wake_up_interruptible(&dma_q->wq); + + PDEBUG(3, "end"); + return 0; +} + +static void ov534_stop_thread(struct ov534_dmaqueue *dma_q) +{ + PDEBUG(3, "begin"); + /* shutdown control thread */ + if (dma_q->kthread) { + kthread_stop(dma_q->kthread); + dma_q->kthread = NULL; + } + PDEBUG(3, "end"); +} + +/* ------------------------------------------------------------------ + Videobuf operations + ------------------------------------------------------------------*/ + +static int buffer_setup(struct videobuf_queue *vq, unsigned int *count, + unsigned int *size) +{ + struct ov534_fh *fh = vq->priv_data; + + *size = (fh->height * fh->width * fh->fmt->depth) >> 3; + + if (*count == 0) + *count = 32; + + while (*size * *count > vid_limit * 1024 * 1024) + (*count)--; + + PDEBUG(2, "count=%d, size=%d", *count, *size); + + return 0; +} + +static void free_buffer(struct videobuf_queue *vq, struct ov534_buffer *buf) +{ + PDEBUG(3, "begin"); + PDEBUG(2, "state=%i", buf->vb.state); + + if (in_interrupt()) + BUG(); + + videobuf_vmalloc_free(&buf->vb); + PDEBUG(2, "freed"); + buf->vb.state = VIDEOBUF_NEEDS_INIT; + + PDEBUG(3, "end"); +} + +static int buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct ov534_fh *fh = vq->priv_data; + struct ov534_buffer *buf = container_of(vb,struct ov534_buffer,vb); + int rc, init_buffer = 0; + + PDEBUG(2, "field=%d", field); + BUG_ON(NULL == fh->fmt); + + if (fh->width != 640 || fh->height != 480) + return -EINVAL; + buf->vb.size = (fh->height * fh->width * fh->fmt->depth) >> 3; + + if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) + return -EINVAL; + + if (buf->fmt != fh->fmt || + buf->vb.width != fh->width || + buf->vb.height != fh->height || + buf->vb.field != field) { + buf->fmt = fh->fmt; + buf->vb.width = fh->width; + buf->vb.height = fh->height; + buf->vb.field = field; + init_buffer = 1; + } + + if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { + rc = videobuf_iolock(vq, &buf->vb, NULL); + if (rc < 0) + goto fail; + } + + buf->vb.state = VIDEOBUF_PREPARED; + + return 0; + +fail: + free_buffer(vq, buf); + return rc; +} + +static void +buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ + struct ov534_buffer *buf = container_of(vb,struct ov534_buffer,vb); + struct ov534_fh *fh = vq->priv_data; + struct ov534_dev *dev = fh->dev; + struct ov534_dmaqueue *vidq = &dev->vidq; + + PDEBUG(3, "begin"); + + buf->vb.state = VIDEOBUF_QUEUED; + list_add_tail(&buf->vb.queue, &vidq->active); + + PDEBUG(3, "end"); +} + +static void buffer_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct ov534_buffer *buf = container_of(vb,struct ov534_buffer,vb); + + PDEBUG(3, "begin"); + free_buffer(vq, buf); + PDEBUG(3, "end"); +} + +static struct videobuf_queue_ops ov534_video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + + +/* ------------------------------------------------------------------ + IOCTL vidioc handling + ------------------------------------------------------------------*/ + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strcpy(cap->driver, OV534_MODULE_NAME); + cap->version = OV534_VERSION; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE; + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index >= (sizeof(format) / sizeof(struct ov534_fmt))) + return -EINVAL; + + strlcpy(f->description,format[f->index].name,sizeof(f->description)); + f->pixelformat = format[f->index].fourcc; + return 0; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct ov534_fh *fh = priv; + + f->fmt.pix.width = fh->width; + f->fmt.pix.height = fh->height; + f->fmt.pix.field = fh->vb_vidq.field; + f->fmt.pix.pixelformat = fh->fmt->fourcc; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fh->fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct ov534_fmt *fmt = NULL; + int len = sizeof(format) / sizeof(struct ov534_fmt); + int i = 0; + + for (i = 0; i < len; i++) { + if (format[i].fourcc == f->fmt.pix.pixelformat) + fmt = &format[i]; + } + + if (fmt == NULL) { + PDEBUG(2, "Fourcc format (0x%08x) invalid", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + if (f->fmt.pix.field != fmt->field && + f->fmt.pix.field != V4L2_FIELD_ANY) { + PDEBUG(1,"Field type invalid."); + return -EINVAL; + } + + f->fmt.pix.pixelformat = fmt->fourcc; + f->fmt.pix.field = fmt->field; + f->fmt.pix.width = 640; + f->fmt.pix.height = 480; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct ov534_fh *fh = priv; + struct videobuf_queue *q = &fh->vb_vidq; + int len = sizeof(format) / sizeof(struct ov534_fmt); + int i = 0; + + int ret = vidioc_try_fmt_vid_cap(file, fh, f); + if (ret < 0) + return (ret); + + mutex_lock(&q->vb_lock); + + if (videobuf_queue_is_busy(&fh->vb_vidq)) { + PDEBUG(1, "queue busy"); + ret = -EBUSY; + goto out; + } + + for (i = 0; i < len; i++) { + if (format[i].fourcc == f->fmt.pix.pixelformat) + fh->fmt = &format[i]; + } + + fh->width = f->fmt.pix.width; + fh->height = f->fmt.pix.height; + fh->vb_vidq.field = f->fmt.pix.field; + fh->type = f->type; + + ret = 0; +out: + mutex_unlock(&q->vb_lock); + + return (ret); +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + struct ov534_fh *fh = priv; + + return (videobuf_reqbufs(&fh->vb_vidq, p)); +} + +static int vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct ov534_fh *fh = priv; + + return (videobuf_querybuf(&fh->vb_vidq, p)); +} + +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct ov534_fh *fh = priv; + + return (videobuf_qbuf(&fh->vb_vidq, p)); +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct ov534_fh *fh = priv; + + return (videobuf_dqbuf(&fh->vb_vidq, p, + file->f_flags & O_NONBLOCK)); +} + +#ifdef CONFIG_VIDEO_V4L1_COMPAT +static int vidiocgmbuf(struct file *file, void *priv, struct video_mbuf *mbuf) +{ + struct ov534_fh *fh = priv; + + return videobuf_cgmbuf(&fh->vb_vidq, mbuf, 8); +} +#endif + +static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct video_device *vdf = video_devdata(file); + struct ov534_dev *dev; + struct ov534_fh *fh = priv; + + if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (i != fh->type) + return -EINVAL; + + if (vdf == NULL) + return -ENODEV; + dev = video_get_drvdata(vdf); + + ov534_set_led(dev->udev, 1); + + return videobuf_streamon(&fh->vb_vidq); +} + +static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct video_device *vdf = video_devdata(file); + struct ov534_dev *dev; + struct ov534_fh *fh = priv; + + if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (i != fh->type) + return -EINVAL; + + if (vdf == NULL) + return -ENODEV; + dev = video_get_drvdata(vdf); + + ov534_set_led(dev->udev, 0); + + return videobuf_streamoff(&fh->vb_vidq); +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id *i) +{ + return 0; +} + +/* only one input */ +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + if (inp->index != 0) + return -EINVAL; + + inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->std = V4L2_STD_525_60; + strcpy(inp->name, "Camera"); + + return (0); +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + + return (0); +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + if (i > 0) + return -EINVAL; + + return (0); +} + +static int vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ov534_qctrl); i++) + if (qc->id && qc->id == ov534_qctrl[i].id) { + memcpy(qc, &(ov534_qctrl[i]), + sizeof(*qc)); + return (0); + } + + return -EINVAL; +} + +static int vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ov534_qctrl); i++) + if (ctrl->id == ov534_qctrl[i].id) { + ctrl->value = qctl_regs[i]; + return (0); + } + + return -EINVAL; +} + +static int vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ov534_qctrl); i++) + if (ctrl->id == ov534_qctrl[i].id) { + if (ctrl->value < + ov534_qctrl[i].minimum + || ctrl->value > + ov534_qctrl[i].maximum) { + return (-ERANGE); + } + qctl_regs[i] = ctrl->value; + return (0); + } + return -EINVAL; +} + +static const struct v4l2_ioctl_ops ov534_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_s_std = vidioc_s_std, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_queryctrl = vidioc_queryctrl, + .vidioc_g_ctrl = vidioc_g_ctrl, + .vidioc_s_ctrl = vidioc_s_ctrl, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, +#ifdef CONFIG_VIDEO_V4L1_COMPAT + .vidiocgmbuf = vidiocgmbuf, +#endif +}; + + +/* ------------------------------------------------------------------ + File operations for the device + ------------------------------------------------------------------*/ + +static int ov534_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct ov534_dev *dev; + struct ov534_fh *fh = NULL; + int i; + int retval = 0; + + printk(KERN_DEBUG "ov534: open called (minor=%d)\n", minor); + + lock_kernel(); + list_for_each_entry(dev, &ov534_devlist, ov534_devlist) + if (dev->vfd->minor == minor) + goto found; + unlock_kernel(); + return -ENODEV; + +found: + mutex_lock(&dev->mutex); + dev->users++; + + if (dev->users > 1) { + dev->users--; + retval = -EBUSY; + goto unlock; + } + + PDEBUG(2, "open minor=%d type=%s users=%d\n", minor, + v4l2_type_names[V4L2_BUF_TYPE_VIDEO_CAPTURE], dev->users); + + /* allocate + initialize per filehandle data */ + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (NULL == fh) { + dev->users--; + retval = -ENOMEM; + goto unlock; + } +unlock: + mutex_unlock(&dev->mutex); + if (retval) { + unlock_kernel(); + return retval; + } + + file->private_data = fh; + fh->dev = dev; + + fh->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fh->fmt = &format[0]; + fh->width = 640; + fh->height = 480; + + /* Put all controls at a sane state */ + for (i = 0; i < ARRAY_SIZE(ov534_qctrl); i++) + qctl_regs[i] = ov534_qctrl[i].default_value; + + /* Resets frame counters */ + dev->h = 0; + dev->m = 0; + dev->s = 0; + dev->ms = 0; + dev->jiffies = jiffies; + + sprintf(dev->timestr,"%02d:%02d:%02d:%03d", + dev->h, dev->m, dev->s, dev->ms); + + videobuf_queue_vmalloc_init(&fh->vb_vidq, &ov534_video_qops, + NULL, &dev->slock, fh->type, fh->fmt->field, + sizeof(struct ov534_buffer), fh); + + ov534_start_thread(fh); + unlock_kernel(); + + return 0; +} + +static ssize_t +ov534_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ + struct ov534_fh *fh = file->private_data; + + if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + return videobuf_read_stream(&fh->vb_vidq, data, count, ppos, 0, + file->f_flags & O_NONBLOCK); + } + return 0; +} + +static unsigned int +ov534_poll(struct file *file, struct poll_table_struct *wait) +{ + struct ov534_fh *fh = file->private_data; + struct videobuf_queue *q = &fh->vb_vidq; + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != fh->type) + return POLLERR; + + return videobuf_poll_stream(file, q, wait); +} + +static int ov534_close(struct inode *inode, struct file *file) +{ + struct ov534_fh *fh = file->private_data; + struct ov534_dev *dev = fh->dev; + struct ov534_dmaqueue *vidq = &dev->vidq; + + int minor = iminor(inode); + + ov534_stop_thread(vidq); + videobuf_stop(&fh->vb_vidq); + videobuf_mmap_free(&fh->vb_vidq); + + kfree(fh); + + mutex_lock(&dev->mutex); + dev->users--; + mutex_unlock(&dev->mutex); + + PDEBUG(2, "ov534: close called (minor=%d, users=%d)", minor,dev->users); + + return 0; +} + +static int ov534_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct ov534_fh *fh = file->private_data; + int ret; + + PDEBUG (2, "mmap called, vma=0x%08lx\n", (unsigned long)vma); + + ret = videobuf_mmap_mapper(&fh->vb_vidq, vma); + + PDEBUG (2, "vma start=0x%08lx, size=%ld, ret=%d\n", + (unsigned long)vma->vm_start, + (unsigned long)vma->vm_end-(unsigned long)vma->vm_start, + ret); + + return ret; +} + +static struct file_operations ov534_fops = { + .owner = THIS_MODULE, + .open = ov534_open, + .release = ov534_close, + .read = ov534_read, + .poll = ov534_poll, + .mmap = ov534_mmap, + .ioctl = video_ioctl2, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + + +static struct video_device ov534_template = { + .name = OV534_MODULE_NAME, + .fops = &ov534_fops, + .ioctl_ops = &ov534_ioctl_ops, + .minor = -1, + .release = video_device_release, + + .tvnorms = V4L2_STD_525_60, + .current_norm = V4L2_STD_NTSC_M, +}; + + +/*******************/ +/* USB integration */ +/*******************/ + +static int ov534_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct ov534_dev *dev; + char *desc; + int err; + + info(OV534_MODULE_DESC " compatible webcam detected"); + + switch (udev->descriptor.idVendor) { + case 0x06f8: + switch (udev->descriptor.idProduct) { + case 0x3002: + desc = "Hercules Blog Webcam"; + break; + case 0x3003: + desc = "Hercules Dualpix HD Webcam"; + break; + default: + return -ENODEV; + } + break; + case 0x1415: + switch (udev->descriptor.idProduct) { + case 0x2000: + desc = "Sony HD Eye for PS3 (SLEH 00201)"; + break; + default: + return -ENODEV; + } + break; + default: + return -ENODEV; + } + info("%04x:%04x %s found", udev->descriptor.idVendor, + udev->descriptor.idProduct, desc); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) + return -ENOMEM; + + list_add_tail(&dev->ov534_devlist,&ov534_devlist); + + /* init video dma queues */ + INIT_LIST_HEAD(&dev->vidq.active); + init_waitqueue_head(&dev->vidq.wq); + + /* init locks */ + spin_lock_init(&dev->slock); + mutex_init(&dev->mutex); + + dev->vfd = video_device_alloc(); + if (dev->vfd == NULL) { + kfree(dev); + return -ENOMEM; + } + memcpy(dev->vfd, &ov534_template, sizeof(ov534_template)); + + video_set_drvdata(dev->vfd, dev); + if (ov534.debug > 3) + dev->vfd->debug = V4L2_DEBUG_IOCTL | V4L2_DEBUG_IOCTL_ARG; + + dev->udev = udev; + + err = video_register_device(dev->vfd, VFL_TYPE_GRABBER, video_nr); + + if (err) { + info("video_register_device failed"); + video_device_release(dev->vfd); + kfree(dev->vfd); + kfree(dev); + return err; + } + + usb_set_intfdata(intf, dev); + + ov534_setup(udev); + + info(OV534_MODULE_NAME " controlling video device %d", dev->vfd->minor); + return 0; +} + +static void ov534_disconnect(struct usb_interface *intf) +{ + struct ov534_dev *dev = usb_get_intfdata(intf); + + list_del(&dev->ov534_devlist); + + usb_set_intfdata(intf, NULL); + dev_set_drvdata(&intf->dev, NULL); + + info(OV534_MODULE_NAME " webcam unplugged"); + if (dev->vfd) { + if (-1 != dev->vfd->minor) { + video_unregister_device(dev->vfd); + printk(KERN_INFO "%s: /dev/video%d unregistered.\n", + OV534_MODULE_NAME, dev->vfd->minor); + } else { + video_device_release(dev->vfd); + printk(KERN_INFO "%s: /dev/video%d released.\n", + OV534_MODULE_NAME, dev->vfd->minor); + } + } + + dev->vfd = NULL; + + kfree(dev); +} + + +/**********************/ +/* Module integration */ +/**********************/ + +static struct usb_driver ov534_driver = { + .name = "ov534", + .probe = ov534_probe, + .disconnect = ov534_disconnect, + .id_table = device_table +}; + + +static int __init ov534_init(void) +{ + int retval; + retval = usb_register(&ov534_driver); + if (retval) + info("usb_register failed!"); + else + info("%s v%d.%d.%d module loaded", + OV534_MODULE_NAME, + OV534_MAJOR_VERSION, + OV534_MINOR_VERSION, + OV534_RELEASE); + return retval; +} + + +static void __exit ov534_exit(void) +{ + info(OV534_MODULE_NAME " module unloaded"); + usb_deregister(&ov534_driver); +} + + +module_init(ov534_init); +module_exit(ov534_exit); + +MODULE_AUTHOR(OV534_AUTHOR); +MODULE_DESCRIPTION(OV534_MODULE_DESC); +MODULE_LICENSE("GPL"); -- video4linux-list mailing list Unsubscribe mailto:video4linux-list-request@redhat.com?subject=unsubs... https://www.redhat.com/mailman/listinfo/video4linux-list