0edf2e5e2b
All of the hard problems for BKL removal appear to be solved in the v4l-dvb/master tree. This removes the BKL from the various open functions that do not need it, or only use it to protect an open count. The zoran driver is nontrivial in this regard, so I introduce a new mutex that locks both the open/release and the ioctl functions. Someone with access to the hardware can probably improve that by using the existing lock in all cases. Finally, all drivers that still use the locked version of the ioctl function now get called under a new mutex instead of the BKL. Signed-off-by: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
953 lines
21 KiB
C
953 lines
21 KiB
C
/*
|
|
* USB ViCam WebCam driver
|
|
* Copyright (c) 2002 Joe Burks (jburks@wavicle.org),
|
|
* Christopher L Cheney (ccheney@cheney.cx),
|
|
* Pavel Machek (pavel@ucw.cz),
|
|
* John Tyner (jtyner@cs.ucr.edu),
|
|
* Monroe Williams (monroe@pobox.com)
|
|
*
|
|
* Supports 3COM HomeConnect PC Digital WebCam
|
|
* Supports Compro PS39U WebCam
|
|
*
|
|
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
* This source code is based heavily on the CPiA webcam driver which was
|
|
* written by Peter Pregler, Scott J. Bertin and Johannes Erdfelt
|
|
*
|
|
* Portions of this code were also copied from usbvideo.c
|
|
*
|
|
* Special thanks to the whole team at Sourceforge for help making
|
|
* this driver become a reality. Notably:
|
|
* Andy Armstrong who reverse engineered the color encoding and
|
|
* Pavel Machek and Chris Cheney who worked on reverse engineering the
|
|
* camera controls and wrote the first generation driver.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/videodev.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/ihex.h>
|
|
#include "usbvideo.h"
|
|
|
|
// #define VICAM_DEBUG
|
|
|
|
#ifdef VICAM_DEBUG
|
|
#define ADBG(lineno,fmt,args...) printk(fmt, jiffies, __func__, lineno, ##args)
|
|
#define DBG(fmt,args...) ADBG((__LINE__),KERN_DEBUG __FILE__"(%ld):%s (%d):"fmt,##args)
|
|
#else
|
|
#define DBG(fmn,args...) do {} while(0)
|
|
#endif
|
|
|
|
#define DRIVER_AUTHOR "Joe Burks, jburks@wavicle.org"
|
|
#define DRIVER_DESC "ViCam WebCam Driver"
|
|
|
|
/* Define these values to match your device */
|
|
#define USB_VICAM_VENDOR_ID 0x04c1
|
|
#define USB_VICAM_PRODUCT_ID 0x009d
|
|
#define USB_COMPRO_VENDOR_ID 0x0602
|
|
#define USB_COMPRO_PRODUCT_ID 0x1001
|
|
|
|
#define VICAM_BYTES_PER_PIXEL 3
|
|
#define VICAM_MAX_READ_SIZE (512*242+128)
|
|
#define VICAM_MAX_FRAME_SIZE (VICAM_BYTES_PER_PIXEL*320*240)
|
|
#define VICAM_FRAMES 2
|
|
|
|
#define VICAM_HEADER_SIZE 64
|
|
|
|
/* rvmalloc / rvfree copied from usbvideo.c
|
|
*
|
|
* Not sure why these are not yet non-statics which I can reference through
|
|
* usbvideo.h the same as it is in 2.4.20. I bet this will get fixed sometime
|
|
* in the future.
|
|
*
|
|
*/
|
|
static void *rvmalloc(unsigned long size)
|
|
{
|
|
void *mem;
|
|
unsigned long adr;
|
|
|
|
size = PAGE_ALIGN(size);
|
|
mem = vmalloc_32(size);
|
|
if (!mem)
|
|
return NULL;
|
|
|
|
memset(mem, 0, size); /* Clear the ram out, no junk to the user */
|
|
adr = (unsigned long) mem;
|
|
while (size > 0) {
|
|
SetPageReserved(vmalloc_to_page((void *)adr));
|
|
adr += PAGE_SIZE;
|
|
size -= PAGE_SIZE;
|
|
}
|
|
|
|
return mem;
|
|
}
|
|
|
|
static void rvfree(void *mem, unsigned long size)
|
|
{
|
|
unsigned long adr;
|
|
|
|
if (!mem)
|
|
return;
|
|
|
|
adr = (unsigned long) mem;
|
|
while ((long) size > 0) {
|
|
ClearPageReserved(vmalloc_to_page((void *)adr));
|
|
adr += PAGE_SIZE;
|
|
size -= PAGE_SIZE;
|
|
}
|
|
vfree(mem);
|
|
}
|
|
|
|
struct vicam_camera {
|
|
u16 shutter_speed; // capture shutter speed
|
|
u16 gain; // capture gain
|
|
|
|
u8 *raw_image; // raw data captured from the camera
|
|
u8 *framebuf; // processed data in RGB24 format
|
|
u8 *cntrlbuf; // area used to send control msgs
|
|
|
|
struct video_device vdev; // v4l video device
|
|
struct usb_device *udev; // usb device
|
|
|
|
/* guard against simultaneous accesses to the camera */
|
|
struct mutex cam_lock;
|
|
|
|
int is_initialized;
|
|
u8 open_count;
|
|
u8 bulkEndpoint;
|
|
int needsDummyRead;
|
|
};
|
|
|
|
static int vicam_probe( struct usb_interface *intf, const struct usb_device_id *id);
|
|
static void vicam_disconnect(struct usb_interface *intf);
|
|
static void read_frame(struct vicam_camera *cam, int framenum);
|
|
static void vicam_decode_color(const u8 *, u8 *);
|
|
|
|
static int __send_control_msg(struct vicam_camera *cam,
|
|
u8 request,
|
|
u16 value,
|
|
u16 index,
|
|
unsigned char *cp,
|
|
u16 size)
|
|
{
|
|
int status;
|
|
|
|
/* cp must be memory that has been allocated by kmalloc */
|
|
|
|
status = usb_control_msg(cam->udev,
|
|
usb_sndctrlpipe(cam->udev, 0),
|
|
request,
|
|
USB_DIR_OUT | USB_TYPE_VENDOR |
|
|
USB_RECIP_DEVICE, value, index,
|
|
cp, size, 1000);
|
|
|
|
status = min(status, 0);
|
|
|
|
if (status < 0) {
|
|
printk(KERN_INFO "Failed sending control message, error %d.\n",
|
|
status);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static int send_control_msg(struct vicam_camera *cam,
|
|
u8 request,
|
|
u16 value,
|
|
u16 index,
|
|
unsigned char *cp,
|
|
u16 size)
|
|
{
|
|
int status = -ENODEV;
|
|
mutex_lock(&cam->cam_lock);
|
|
if (cam->udev) {
|
|
status = __send_control_msg(cam, request, value,
|
|
index, cp, size);
|
|
}
|
|
mutex_unlock(&cam->cam_lock);
|
|
return status;
|
|
}
|
|
static int
|
|
initialize_camera(struct vicam_camera *cam)
|
|
{
|
|
int err;
|
|
const struct ihex_binrec *rec;
|
|
const struct firmware *uninitialized_var(fw);
|
|
|
|
err = request_ihex_firmware(&fw, "vicam/firmware.fw", &cam->udev->dev);
|
|
if (err) {
|
|
printk(KERN_ERR "Failed to load \"vicam/firmware.fw\": %d\n",
|
|
err);
|
|
return err;
|
|
}
|
|
|
|
for (rec = (void *)fw->data; rec; rec = ihex_next_binrec(rec)) {
|
|
memcpy(cam->cntrlbuf, rec->data, be16_to_cpu(rec->len));
|
|
|
|
err = send_control_msg(cam, 0xff, 0, 0,
|
|
cam->cntrlbuf, be16_to_cpu(rec->len));
|
|
if (err)
|
|
break;
|
|
}
|
|
|
|
release_firmware(fw);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
set_camera_power(struct vicam_camera *cam, int state)
|
|
{
|
|
int status;
|
|
|
|
if ((status = send_control_msg(cam, 0x50, state, 0, NULL, 0)) < 0)
|
|
return status;
|
|
|
|
if (state) {
|
|
send_control_msg(cam, 0x55, 1, 0, NULL, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long
|
|
vicam_ioctl(struct file *file, unsigned int ioctlnr, unsigned long arg)
|
|
{
|
|
void __user *user_arg = (void __user *)arg;
|
|
struct vicam_camera *cam = file->private_data;
|
|
long retval = 0;
|
|
|
|
if (!cam)
|
|
return -ENODEV;
|
|
|
|
switch (ioctlnr) {
|
|
/* query capabilities */
|
|
case VIDIOCGCAP:
|
|
{
|
|
struct video_capability b;
|
|
|
|
DBG("VIDIOCGCAP\n");
|
|
memset(&b, 0, sizeof(b));
|
|
strcpy(b.name, "ViCam-based Camera");
|
|
b.type = VID_TYPE_CAPTURE;
|
|
b.channels = 1;
|
|
b.audios = 0;
|
|
b.maxwidth = 320; /* VIDEOSIZE_CIF */
|
|
b.maxheight = 240;
|
|
b.minwidth = 320; /* VIDEOSIZE_48_48 */
|
|
b.minheight = 240;
|
|
|
|
if (copy_to_user(user_arg, &b, sizeof(b)))
|
|
retval = -EFAULT;
|
|
|
|
break;
|
|
}
|
|
/* get/set video source - we are a camera and nothing else */
|
|
case VIDIOCGCHAN:
|
|
{
|
|
struct video_channel v;
|
|
|
|
DBG("VIDIOCGCHAN\n");
|
|
if (copy_from_user(&v, user_arg, sizeof(v))) {
|
|
retval = -EFAULT;
|
|
break;
|
|
}
|
|
if (v.channel != 0) {
|
|
retval = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
v.channel = 0;
|
|
strcpy(v.name, "Camera");
|
|
v.tuners = 0;
|
|
v.flags = 0;
|
|
v.type = VIDEO_TYPE_CAMERA;
|
|
v.norm = 0;
|
|
|
|
if (copy_to_user(user_arg, &v, sizeof(v)))
|
|
retval = -EFAULT;
|
|
break;
|
|
}
|
|
|
|
case VIDIOCSCHAN:
|
|
{
|
|
int v;
|
|
|
|
if (copy_from_user(&v, user_arg, sizeof(v)))
|
|
retval = -EFAULT;
|
|
DBG("VIDIOCSCHAN %d\n", v);
|
|
|
|
if (retval == 0 && v != 0)
|
|
retval = -EINVAL;
|
|
|
|
break;
|
|
}
|
|
|
|
/* image properties */
|
|
case VIDIOCGPICT:
|
|
{
|
|
struct video_picture vp;
|
|
DBG("VIDIOCGPICT\n");
|
|
memset(&vp, 0, sizeof (struct video_picture));
|
|
vp.brightness = cam->gain << 8;
|
|
vp.depth = 24;
|
|
vp.palette = VIDEO_PALETTE_RGB24;
|
|
if (copy_to_user(user_arg, &vp, sizeof (struct video_picture)))
|
|
retval = -EFAULT;
|
|
break;
|
|
}
|
|
|
|
case VIDIOCSPICT:
|
|
{
|
|
struct video_picture vp;
|
|
|
|
if (copy_from_user(&vp, user_arg, sizeof(vp))) {
|
|
retval = -EFAULT;
|
|
break;
|
|
}
|
|
|
|
DBG("VIDIOCSPICT depth = %d, pal = %d\n", vp.depth,
|
|
vp.palette);
|
|
|
|
cam->gain = vp.brightness >> 8;
|
|
|
|
if (vp.depth != 24
|
|
|| vp.palette != VIDEO_PALETTE_RGB24)
|
|
retval = -EINVAL;
|
|
|
|
break;
|
|
}
|
|
|
|
/* get/set capture window */
|
|
case VIDIOCGWIN:
|
|
{
|
|
struct video_window vw;
|
|
vw.x = 0;
|
|
vw.y = 0;
|
|
vw.width = 320;
|
|
vw.height = 240;
|
|
vw.chromakey = 0;
|
|
vw.flags = 0;
|
|
vw.clips = NULL;
|
|
vw.clipcount = 0;
|
|
|
|
DBG("VIDIOCGWIN\n");
|
|
|
|
if (copy_to_user(user_arg, (void *)&vw, sizeof(vw)))
|
|
retval = -EFAULT;
|
|
|
|
// I'm not sure what the deal with a capture window is, it is very poorly described
|
|
// in the doc. So I won't support it now.
|
|
break;
|
|
}
|
|
|
|
case VIDIOCSWIN:
|
|
{
|
|
|
|
struct video_window vw;
|
|
|
|
if (copy_from_user(&vw, user_arg, sizeof(vw))) {
|
|
retval = -EFAULT;
|
|
break;
|
|
}
|
|
|
|
DBG("VIDIOCSWIN %d x %d\n", vw.width, vw.height);
|
|
|
|
if ( vw.width != 320 || vw.height != 240 )
|
|
retval = -EFAULT;
|
|
|
|
break;
|
|
}
|
|
|
|
/* mmap interface */
|
|
case VIDIOCGMBUF:
|
|
{
|
|
struct video_mbuf vm;
|
|
int i;
|
|
|
|
DBG("VIDIOCGMBUF\n");
|
|
memset(&vm, 0, sizeof (vm));
|
|
vm.size =
|
|
VICAM_MAX_FRAME_SIZE * VICAM_FRAMES;
|
|
vm.frames = VICAM_FRAMES;
|
|
for (i = 0; i < VICAM_FRAMES; i++)
|
|
vm.offsets[i] = VICAM_MAX_FRAME_SIZE * i;
|
|
|
|
if (copy_to_user(user_arg, (void *)&vm, sizeof(vm)))
|
|
retval = -EFAULT;
|
|
|
|
break;
|
|
}
|
|
|
|
case VIDIOCMCAPTURE:
|
|
{
|
|
struct video_mmap vm;
|
|
// int video_size;
|
|
|
|
if (copy_from_user((void *)&vm, user_arg, sizeof(vm))) {
|
|
retval = -EFAULT;
|
|
break;
|
|
}
|
|
|
|
DBG("VIDIOCMCAPTURE frame=%d, height=%d, width=%d, format=%d.\n",vm.frame,vm.width,vm.height,vm.format);
|
|
|
|
if ( vm.frame >= VICAM_FRAMES || vm.format != VIDEO_PALETTE_RGB24 )
|
|
retval = -EINVAL;
|
|
|
|
// in theory right here we'd start the image capturing
|
|
// (fill in a bulk urb and submit it asynchronously)
|
|
//
|
|
// Instead we're going to do a total hack job for now and
|
|
// retrieve the frame in VIDIOCSYNC
|
|
|
|
break;
|
|
}
|
|
|
|
case VIDIOCSYNC:
|
|
{
|
|
int frame;
|
|
|
|
if (copy_from_user((void *)&frame, user_arg, sizeof(int))) {
|
|
retval = -EFAULT;
|
|
break;
|
|
}
|
|
DBG("VIDIOCSYNC: %d\n", frame);
|
|
|
|
read_frame(cam, frame);
|
|
vicam_decode_color(cam->raw_image,
|
|
cam->framebuf +
|
|
frame * VICAM_MAX_FRAME_SIZE );
|
|
|
|
break;
|
|
}
|
|
|
|
/* pointless to implement overlay with this camera */
|
|
case VIDIOCCAPTURE:
|
|
case VIDIOCGFBUF:
|
|
case VIDIOCSFBUF:
|
|
case VIDIOCKEY:
|
|
retval = -EINVAL;
|
|
break;
|
|
|
|
/* tuner interface - we have none */
|
|
case VIDIOCGTUNER:
|
|
case VIDIOCSTUNER:
|
|
case VIDIOCGFREQ:
|
|
case VIDIOCSFREQ:
|
|
retval = -EINVAL;
|
|
break;
|
|
|
|
/* audio interface - we have none */
|
|
case VIDIOCGAUDIO:
|
|
case VIDIOCSAUDIO:
|
|
retval = -EINVAL;
|
|
break;
|
|
default:
|
|
retval = -ENOIOCTLCMD;
|
|
break;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int
|
|
vicam_open(struct file *file)
|
|
{
|
|
struct vicam_camera *cam = video_drvdata(file);
|
|
|
|
DBG("open\n");
|
|
|
|
if (!cam) {
|
|
printk(KERN_ERR
|
|
"vicam video_device improperly initialized");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* cam_lock/open_count protects us from simultaneous opens
|
|
* ... for now. we probably shouldn't rely on this fact forever.
|
|
*/
|
|
|
|
mutex_lock(&cam->cam_lock);
|
|
if (cam->open_count > 0) {
|
|
printk(KERN_INFO
|
|
"vicam_open called on already opened camera");
|
|
mutex_unlock(&cam->cam_lock);
|
|
return -EBUSY;
|
|
}
|
|
|
|
cam->raw_image = kmalloc(VICAM_MAX_READ_SIZE, GFP_KERNEL);
|
|
if (!cam->raw_image) {
|
|
mutex_unlock(&cam->cam_lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
cam->framebuf = rvmalloc(VICAM_MAX_FRAME_SIZE * VICAM_FRAMES);
|
|
if (!cam->framebuf) {
|
|
kfree(cam->raw_image);
|
|
mutex_unlock(&cam->cam_lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
cam->cntrlbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
|
if (!cam->cntrlbuf) {
|
|
kfree(cam->raw_image);
|
|
rvfree(cam->framebuf, VICAM_MAX_FRAME_SIZE * VICAM_FRAMES);
|
|
mutex_unlock(&cam->cam_lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
cam->needsDummyRead = 1;
|
|
cam->open_count++;
|
|
|
|
file->private_data = cam;
|
|
mutex_unlock(&cam->cam_lock);
|
|
|
|
|
|
// First upload firmware, then turn the camera on
|
|
|
|
if (!cam->is_initialized) {
|
|
initialize_camera(cam);
|
|
|
|
cam->is_initialized = 1;
|
|
}
|
|
|
|
set_camera_power(cam, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
vicam_close(struct file *file)
|
|
{
|
|
struct vicam_camera *cam = file->private_data;
|
|
int open_count;
|
|
struct usb_device *udev;
|
|
|
|
DBG("close\n");
|
|
|
|
/* it's not the end of the world if
|
|
* we fail to turn the camera off.
|
|
*/
|
|
|
|
set_camera_power(cam, 0);
|
|
|
|
kfree(cam->raw_image);
|
|
rvfree(cam->framebuf, VICAM_MAX_FRAME_SIZE * VICAM_FRAMES);
|
|
kfree(cam->cntrlbuf);
|
|
|
|
mutex_lock(&cam->cam_lock);
|
|
|
|
cam->open_count--;
|
|
open_count = cam->open_count;
|
|
udev = cam->udev;
|
|
|
|
mutex_unlock(&cam->cam_lock);
|
|
|
|
if (!open_count && !udev) {
|
|
kfree(cam);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void vicam_decode_color(const u8 *data, u8 *rgb)
|
|
{
|
|
/* vicam_decode_color - Convert from Vicam Y-Cr-Cb to RGB
|
|
* Copyright (C) 2002 Monroe Williams (monroe@pobox.com)
|
|
*/
|
|
|
|
int i, prevY, nextY;
|
|
|
|
prevY = 512;
|
|
nextY = 512;
|
|
|
|
data += VICAM_HEADER_SIZE;
|
|
|
|
for( i = 0; i < 240; i++, data += 512 ) {
|
|
const int y = ( i * 242 ) / 240;
|
|
|
|
int j, prevX, nextX;
|
|
int Y, Cr, Cb;
|
|
|
|
if ( y == 242 - 1 ) {
|
|
nextY = -512;
|
|
}
|
|
|
|
prevX = 1;
|
|
nextX = 1;
|
|
|
|
for ( j = 0; j < 320; j++, rgb += 3 ) {
|
|
const int x = ( j * 512 ) / 320;
|
|
const u8 * const src = &data[x];
|
|
|
|
if ( x == 512 - 1 ) {
|
|
nextX = -1;
|
|
}
|
|
|
|
Cr = ( src[prevX] - src[0] ) +
|
|
( src[nextX] - src[0] );
|
|
Cr /= 2;
|
|
|
|
Cb = ( src[prevY] - src[prevX + prevY] ) +
|
|
( src[prevY] - src[nextX + prevY] ) +
|
|
( src[nextY] - src[prevX + nextY] ) +
|
|
( src[nextY] - src[nextX + nextY] );
|
|
Cb /= 4;
|
|
|
|
Y = 1160 * ( src[0] + ( Cr / 2 ) - 16 );
|
|
|
|
if ( i & 1 ) {
|
|
int Ct = Cr;
|
|
Cr = Cb;
|
|
Cb = Ct;
|
|
}
|
|
|
|
if ( ( x ^ i ) & 1 ) {
|
|
Cr = -Cr;
|
|
Cb = -Cb;
|
|
}
|
|
|
|
rgb[0] = clamp( ( ( Y + ( 2017 * Cb ) ) +
|
|
500 ) / 900, 0, 255 );
|
|
rgb[1] = clamp( ( ( Y - ( 392 * Cb ) -
|
|
( 813 * Cr ) ) +
|
|
500 ) / 1000, 0, 255 );
|
|
rgb[2] = clamp( ( ( Y + ( 1594 * Cr ) ) +
|
|
500 ) / 1300, 0, 255 );
|
|
|
|
prevX = -1;
|
|
}
|
|
|
|
prevY = -512;
|
|
}
|
|
}
|
|
|
|
static void
|
|
read_frame(struct vicam_camera *cam, int framenum)
|
|
{
|
|
unsigned char *request = cam->cntrlbuf;
|
|
int realShutter;
|
|
int n;
|
|
int actual_length;
|
|
|
|
if (cam->needsDummyRead) {
|
|
cam->needsDummyRead = 0;
|
|
read_frame(cam, framenum);
|
|
}
|
|
|
|
memset(request, 0, 16);
|
|
request[0] = cam->gain; // 0 = 0% gain, FF = 100% gain
|
|
|
|
request[1] = 0; // 512x242 capture
|
|
|
|
request[2] = 0x90; // the function of these two bytes
|
|
request[3] = 0x07; // is not yet understood
|
|
|
|
if (cam->shutter_speed > 60) {
|
|
// Short exposure
|
|
realShutter =
|
|
((-15631900 / cam->shutter_speed) + 260533) / 1000;
|
|
request[4] = realShutter & 0xFF;
|
|
request[5] = (realShutter >> 8) & 0xFF;
|
|
request[6] = 0x03;
|
|
request[7] = 0x01;
|
|
} else {
|
|
// Long exposure
|
|
realShutter = 15600 / cam->shutter_speed - 1;
|
|
request[4] = 0;
|
|
request[5] = 0;
|
|
request[6] = realShutter & 0xFF;
|
|
request[7] = realShutter >> 8;
|
|
}
|
|
|
|
// Per John Markus Bjørndalen, byte at index 8 causes problems if it isn't 0
|
|
request[8] = 0;
|
|
// bytes 9-15 do not seem to affect exposure or image quality
|
|
|
|
mutex_lock(&cam->cam_lock);
|
|
|
|
if (!cam->udev) {
|
|
goto done;
|
|
}
|
|
|
|
n = __send_control_msg(cam, 0x51, 0x80, 0, request, 16);
|
|
|
|
if (n < 0) {
|
|
printk(KERN_ERR
|
|
" Problem sending frame capture control message");
|
|
goto done;
|
|
}
|
|
|
|
n = usb_bulk_msg(cam->udev,
|
|
usb_rcvbulkpipe(cam->udev, cam->bulkEndpoint),
|
|
cam->raw_image,
|
|
512 * 242 + 128, &actual_length, 10000);
|
|
|
|
if (n < 0) {
|
|
printk(KERN_ERR "Problem during bulk read of frame data: %d\n",
|
|
n);
|
|
}
|
|
|
|
done:
|
|
mutex_unlock(&cam->cam_lock);
|
|
}
|
|
|
|
static ssize_t
|
|
vicam_read( struct file *file, char __user *buf, size_t count, loff_t *ppos )
|
|
{
|
|
struct vicam_camera *cam = file->private_data;
|
|
|
|
DBG("read %d bytes.\n", (int) count);
|
|
|
|
if (*ppos >= VICAM_MAX_FRAME_SIZE) {
|
|
*ppos = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (*ppos == 0) {
|
|
read_frame(cam, 0);
|
|
vicam_decode_color(cam->raw_image,
|
|
cam->framebuf +
|
|
0 * VICAM_MAX_FRAME_SIZE);
|
|
}
|
|
|
|
count = min_t(size_t, count, VICAM_MAX_FRAME_SIZE - *ppos);
|
|
|
|
if (copy_to_user(buf, &cam->framebuf[*ppos], count)) {
|
|
count = -EFAULT;
|
|
} else {
|
|
*ppos += count;
|
|
}
|
|
|
|
if (count == VICAM_MAX_FRAME_SIZE) {
|
|
*ppos = 0;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
static int
|
|
vicam_mmap(struct file *file, struct vm_area_struct *vma)
|
|
{
|
|
// TODO: allocate the raw frame buffer if necessary
|
|
unsigned long page, pos;
|
|
unsigned long start = vma->vm_start;
|
|
unsigned long size = vma->vm_end-vma->vm_start;
|
|
struct vicam_camera *cam = file->private_data;
|
|
|
|
if (!cam)
|
|
return -ENODEV;
|
|
|
|
DBG("vicam_mmap: %ld\n", size);
|
|
|
|
/* We let mmap allocate as much as it wants because Linux was adding 2048 bytes
|
|
* to the size the application requested for mmap and it was screwing apps up.
|
|
if (size > VICAM_FRAMES*VICAM_MAX_FRAME_SIZE)
|
|
return -EINVAL;
|
|
*/
|
|
|
|
pos = (unsigned long)cam->framebuf;
|
|
while (size > 0) {
|
|
page = vmalloc_to_pfn((void *)pos);
|
|
if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED))
|
|
return -EAGAIN;
|
|
|
|
start += PAGE_SIZE;
|
|
pos += PAGE_SIZE;
|
|
if (size > PAGE_SIZE)
|
|
size -= PAGE_SIZE;
|
|
else
|
|
size = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct v4l2_file_operations vicam_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = vicam_open,
|
|
.release = vicam_close,
|
|
.read = vicam_read,
|
|
.mmap = vicam_mmap,
|
|
.ioctl = vicam_ioctl,
|
|
};
|
|
|
|
static struct video_device vicam_template = {
|
|
.name = "ViCam-based USB Camera",
|
|
.fops = &vicam_fops,
|
|
.release = video_device_release_empty,
|
|
};
|
|
|
|
/* 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)},
|
|
{USB_DEVICE(USB_COMPRO_VENDOR_ID, USB_COMPRO_PRODUCT_ID)},
|
|
{} /* Terminating entry */
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(usb, vicam_table);
|
|
|
|
static struct usb_driver vicam_driver = {
|
|
.name = "vicam",
|
|
.probe = vicam_probe,
|
|
.disconnect = vicam_disconnect,
|
|
.id_table = vicam_table
|
|
};
|
|
|
|
/**
|
|
* vicam_probe
|
|
* @intf: the interface
|
|
* @id: the device id
|
|
*
|
|
* Called by the usb core when a new device is connected that it thinks
|
|
* this driver might be interested in.
|
|
*/
|
|
static int
|
|
vicam_probe( struct usb_interface *intf, const struct usb_device_id *id)
|
|
{
|
|
struct usb_device *dev = interface_to_usbdev(intf);
|
|
int bulkEndpoint = 0;
|
|
const struct usb_host_interface *interface;
|
|
const struct usb_endpoint_descriptor *endpoint;
|
|
struct vicam_camera *cam;
|
|
|
|
printk(KERN_INFO "ViCam based webcam connected\n");
|
|
|
|
interface = intf->cur_altsetting;
|
|
|
|
DBG(KERN_DEBUG "Interface %d. has %u. endpoints!\n",
|
|
interface->desc.bInterfaceNumber, (unsigned) (interface->desc.bNumEndpoints));
|
|
endpoint = &interface->endpoint[0].desc;
|
|
|
|
if (usb_endpoint_is_bulk_in(endpoint)) {
|
|
/* we found a bulk in endpoint */
|
|
bulkEndpoint = endpoint->bEndpointAddress;
|
|
} else {
|
|
printk(KERN_ERR
|
|
"No bulk in endpoint was found ?! (this is bad)\n");
|
|
}
|
|
|
|
if ((cam =
|
|
kzalloc(sizeof (struct vicam_camera), GFP_KERNEL)) == NULL) {
|
|
printk(KERN_WARNING
|
|
"could not allocate kernel memory for vicam_camera struct\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
|
|
cam->shutter_speed = 15;
|
|
|
|
mutex_init(&cam->cam_lock);
|
|
|
|
memcpy(&cam->vdev, &vicam_template, sizeof(vicam_template));
|
|
video_set_drvdata(&cam->vdev, cam);
|
|
|
|
cam->udev = dev;
|
|
cam->bulkEndpoint = bulkEndpoint;
|
|
|
|
if (video_register_device(&cam->vdev, VFL_TYPE_GRABBER, -1) < 0) {
|
|
kfree(cam);
|
|
printk(KERN_WARNING "video_register_device failed\n");
|
|
return -EIO;
|
|
}
|
|
|
|
printk(KERN_INFO "ViCam webcam driver now controlling device %s\n",
|
|
video_device_node_name(&cam->vdev));
|
|
|
|
usb_set_intfdata (intf, cam);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
vicam_disconnect(struct usb_interface *intf)
|
|
{
|
|
int open_count;
|
|
struct vicam_camera *cam = usb_get_intfdata (intf);
|
|
usb_set_intfdata (intf, NULL);
|
|
|
|
/* we must unregister the device before taking its
|
|
* cam_lock. This is because the video open call
|
|
* holds the same lock as video unregister. if we
|
|
* unregister inside of the cam_lock and open also
|
|
* uses the cam_lock, we get deadlock.
|
|
*/
|
|
|
|
video_unregister_device(&cam->vdev);
|
|
|
|
/* stop the camera from being used */
|
|
|
|
mutex_lock(&cam->cam_lock);
|
|
|
|
/* mark the camera as gone */
|
|
|
|
cam->udev = NULL;
|
|
|
|
/* the only thing left to do is synchronize with
|
|
* our close/release function on who should release
|
|
* the camera memory. if there are any users using the
|
|
* camera, it's their job. if there are no users,
|
|
* it's ours.
|
|
*/
|
|
|
|
open_count = cam->open_count;
|
|
|
|
mutex_unlock(&cam->cam_lock);
|
|
|
|
if (!open_count) {
|
|
kfree(cam);
|
|
}
|
|
|
|
printk(KERN_DEBUG "ViCam-based WebCam disconnected\n");
|
|
}
|
|
|
|
/*
|
|
*/
|
|
static int __init
|
|
usb_vicam_init(void)
|
|
{
|
|
int retval;
|
|
DBG(KERN_INFO "ViCam-based WebCam driver startup\n");
|
|
retval = usb_register(&vicam_driver);
|
|
if (retval)
|
|
printk(KERN_WARNING "usb_register failed!\n");
|
|
return retval;
|
|
}
|
|
|
|
static void __exit
|
|
usb_vicam_exit(void)
|
|
{
|
|
DBG(KERN_INFO
|
|
"ViCam-based WebCam driver shutdown\n");
|
|
|
|
usb_deregister(&vicam_driver);
|
|
}
|
|
|
|
module_init(usb_vicam_init);
|
|
module_exit(usb_vicam_exit);
|
|
|
|
MODULE_AUTHOR(DRIVER_AUTHOR);
|
|
MODULE_DESCRIPTION(DRIVER_DESC);
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_FIRMWARE("vicam/firmware.fw");
|