961 lines
24 KiB
C
961 lines
24 KiB
C
/* Linux driver for Philips webcam
|
|
USB and Video4Linux interface part.
|
|
(C) 1999-2004 Nemosoft Unv.
|
|
(C) 2004-2006 Luc Saillard (luc@saillard.org)
|
|
|
|
NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx
|
|
driver and thus may have bugs that are not present in the original version.
|
|
Please send bug reports and support requests to <luc@saillard.org>.
|
|
The decompression routines have been implemented by reverse-engineering the
|
|
Nemosoft binary pwcx module. Caveat emptor.
|
|
|
|
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/errno.h>
|
|
#include <linux/init.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/module.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <asm/io.h>
|
|
|
|
#include "pwc.h"
|
|
|
|
static struct v4l2_queryctrl pwc_controls[] = {
|
|
{
|
|
.id = V4L2_CID_BRIGHTNESS,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Brightness",
|
|
.minimum = 0,
|
|
.maximum = 128,
|
|
.step = 1,
|
|
.default_value = 64,
|
|
},
|
|
{
|
|
.id = V4L2_CID_CONTRAST,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Contrast",
|
|
.minimum = 0,
|
|
.maximum = 64,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_SATURATION,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Saturation",
|
|
.minimum = -100,
|
|
.maximum = 100,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_GAMMA,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Gamma",
|
|
.minimum = 0,
|
|
.maximum = 32,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_RED_BALANCE,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Red Gain",
|
|
.minimum = 0,
|
|
.maximum = 256,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_BLUE_BALANCE,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Blue Gain",
|
|
.minimum = 0,
|
|
.maximum = 256,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_AUTO_WHITE_BALANCE,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
.name = "Auto White Balance",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_EXPOSURE,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Shutter Speed (Exposure)",
|
|
.minimum = 0,
|
|
.maximum = 256,
|
|
.step = 1,
|
|
.default_value = 200,
|
|
},
|
|
{
|
|
.id = V4L2_CID_AUTOGAIN,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
.name = "Auto Gain Enabled",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
.step = 1,
|
|
.default_value = 1,
|
|
},
|
|
{
|
|
.id = V4L2_CID_GAIN,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Gain Level",
|
|
.minimum = 0,
|
|
.maximum = 256,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_SAVE_USER,
|
|
.type = V4L2_CTRL_TYPE_BUTTON,
|
|
.name = "Save User Settings",
|
|
.minimum = 0,
|
|
.maximum = 0,
|
|
.step = 0,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_RESTORE_USER,
|
|
.type = V4L2_CTRL_TYPE_BUTTON,
|
|
.name = "Restore User Settings",
|
|
.minimum = 0,
|
|
.maximum = 0,
|
|
.step = 0,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_RESTORE_FACTORY,
|
|
.type = V4L2_CTRL_TYPE_BUTTON,
|
|
.name = "Restore Factory Settings",
|
|
.minimum = 0,
|
|
.maximum = 0,
|
|
.step = 0,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_COLOUR_MODE,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
.name = "Colour mode",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_AUTOCONTOUR,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
.name = "Auto contour",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_CONTOUR,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Contour",
|
|
.minimum = 0,
|
|
.maximum = 63,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_BACKLIGHT,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
.name = "Backlight compensation",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_FLICKERLESS,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
.name = "Flickerless",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_NOISE_REDUCTION,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Noise reduction",
|
|
.minimum = 0,
|
|
.maximum = 3,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
},
|
|
};
|
|
|
|
|
|
static void pwc_vidioc_fill_fmt(const struct pwc_device *pdev, struct v4l2_format *f)
|
|
{
|
|
memset(&f->fmt.pix, 0, sizeof(struct v4l2_pix_format));
|
|
f->fmt.pix.width = pdev->view.x;
|
|
f->fmt.pix.height = pdev->view.y;
|
|
f->fmt.pix.field = V4L2_FIELD_NONE;
|
|
if (pdev->pixfmt == V4L2_PIX_FMT_YUV420) {
|
|
f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420;
|
|
f->fmt.pix.bytesperline = (f->fmt.pix.width * 3)/2;
|
|
f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
|
|
} else {
|
|
/* vbandlength contains 4 lines ... */
|
|
f->fmt.pix.bytesperline = pdev->vbandlength/4;
|
|
f->fmt.pix.sizeimage = pdev->frame_size + sizeof(struct pwc_raw_frame);
|
|
if (DEVICE_USE_CODEC1(pdev->type))
|
|
f->fmt.pix.pixelformat = V4L2_PIX_FMT_PWC1;
|
|
else
|
|
f->fmt.pix.pixelformat = V4L2_PIX_FMT_PWC2;
|
|
}
|
|
PWC_DEBUG_IOCTL("pwc_vidioc_fill_fmt() "
|
|
"width=%d, height=%d, bytesperline=%d, sizeimage=%d, pixelformat=%c%c%c%c\n",
|
|
f->fmt.pix.width,
|
|
f->fmt.pix.height,
|
|
f->fmt.pix.bytesperline,
|
|
f->fmt.pix.sizeimage,
|
|
(f->fmt.pix.pixelformat)&255,
|
|
(f->fmt.pix.pixelformat>>8)&255,
|
|
(f->fmt.pix.pixelformat>>16)&255,
|
|
(f->fmt.pix.pixelformat>>24)&255);
|
|
}
|
|
|
|
/* ioctl(VIDIOC_TRY_FMT) */
|
|
static int pwc_vidioc_try_fmt(struct pwc_device *pdev, struct v4l2_format *f)
|
|
{
|
|
if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
|
|
PWC_DEBUG_IOCTL("Bad video type must be V4L2_BUF_TYPE_VIDEO_CAPTURE\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (f->fmt.pix.pixelformat) {
|
|
case V4L2_PIX_FMT_YUV420:
|
|
break;
|
|
case V4L2_PIX_FMT_PWC1:
|
|
if (DEVICE_USE_CODEC23(pdev->type)) {
|
|
PWC_DEBUG_IOCTL("codec1 is only supported for old pwc webcam\n");
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
case V4L2_PIX_FMT_PWC2:
|
|
if (DEVICE_USE_CODEC1(pdev->type)) {
|
|
PWC_DEBUG_IOCTL("codec23 is only supported for new pwc webcam\n");
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
default:
|
|
PWC_DEBUG_IOCTL("Unsupported pixel format\n");
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
if (f->fmt.pix.width > pdev->view_max.x)
|
|
f->fmt.pix.width = pdev->view_max.x;
|
|
else if (f->fmt.pix.width < pdev->view_min.x)
|
|
f->fmt.pix.width = pdev->view_min.x;
|
|
|
|
if (f->fmt.pix.height > pdev->view_max.y)
|
|
f->fmt.pix.height = pdev->view_max.y;
|
|
else if (f->fmt.pix.height < pdev->view_min.y)
|
|
f->fmt.pix.height = pdev->view_min.y;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ioctl(VIDIOC_SET_FMT) */
|
|
static int pwc_vidioc_set_fmt(struct pwc_device *pdev, struct v4l2_format *f)
|
|
{
|
|
int ret, fps, snapshot, compression, pixelformat;
|
|
|
|
ret = pwc_vidioc_try_fmt(pdev, f);
|
|
if (ret<0)
|
|
return ret;
|
|
|
|
pixelformat = f->fmt.pix.pixelformat;
|
|
compression = pdev->vcompression;
|
|
snapshot = 0;
|
|
fps = pdev->vframes;
|
|
if (f->fmt.pix.priv) {
|
|
compression = (f->fmt.pix.priv & PWC_QLT_MASK) >> PWC_QLT_SHIFT;
|
|
snapshot = !!(f->fmt.pix.priv & PWC_FPS_SNAPSHOT);
|
|
fps = (f->fmt.pix.priv & PWC_FPS_FRMASK) >> PWC_FPS_SHIFT;
|
|
if (fps == 0)
|
|
fps = pdev->vframes;
|
|
}
|
|
|
|
if (pixelformat != V4L2_PIX_FMT_YUV420 &&
|
|
pixelformat != V4L2_PIX_FMT_PWC1 &&
|
|
pixelformat != V4L2_PIX_FMT_PWC2)
|
|
return -EINVAL;
|
|
|
|
PWC_DEBUG_IOCTL("Try to change format to: width=%d height=%d fps=%d "
|
|
"compression=%d snapshot=%d format=%c%c%c%c\n",
|
|
f->fmt.pix.width, f->fmt.pix.height, fps,
|
|
compression, snapshot,
|
|
(pixelformat)&255,
|
|
(pixelformat>>8)&255,
|
|
(pixelformat>>16)&255,
|
|
(pixelformat>>24)&255);
|
|
|
|
ret = pwc_try_video_mode(pdev,
|
|
f->fmt.pix.width,
|
|
f->fmt.pix.height,
|
|
fps,
|
|
compression,
|
|
snapshot);
|
|
|
|
PWC_DEBUG_IOCTL("pwc_try_video_mode(), return=%d\n", ret);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
pdev->pixfmt = pixelformat;
|
|
|
|
pwc_vidioc_fill_fmt(pdev, f);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
long pwc_video_do_ioctl(struct file *file, unsigned int cmd, void *arg)
|
|
{
|
|
struct video_device *vdev = video_devdata(file);
|
|
struct pwc_device *pdev;
|
|
DECLARE_WAITQUEUE(wait, current);
|
|
|
|
if (vdev == NULL)
|
|
return -EFAULT;
|
|
pdev = video_get_drvdata(vdev);
|
|
if (pdev == NULL)
|
|
return -EFAULT;
|
|
|
|
#ifdef CONFIG_USB_PWC_DEBUG
|
|
if (PWC_DEBUG_LEVEL_IOCTL & pwc_trace) {
|
|
v4l_printk_ioctl(cmd);
|
|
printk("\n");
|
|
}
|
|
#endif
|
|
|
|
|
|
switch (cmd) {
|
|
#ifdef CONFIG_VIDEO_V4L1_COMPAT
|
|
/* mmap() functions */
|
|
case VIDIOCGMBUF:
|
|
{
|
|
/* Tell the user program how much memory is needed for a mmap() */
|
|
struct video_mbuf *vm = arg;
|
|
int i;
|
|
|
|
memset(vm, 0, sizeof(*vm));
|
|
vm->size = pwc_mbufs * pdev->len_per_image;
|
|
vm->frames = pwc_mbufs; /* double buffering should be enough for most applications */
|
|
for (i = 0; i < pwc_mbufs; i++)
|
|
vm->offsets[i] = i * pdev->len_per_image;
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
/* V4L2 Layer */
|
|
case VIDIOC_QUERYCAP:
|
|
{
|
|
struct v4l2_capability *cap = arg;
|
|
|
|
PWC_DEBUG_IOCTL("ioctl(VIDIOC_QUERYCAP) This application "\
|
|
"try to use the v4l2 layer\n");
|
|
strcpy(cap->driver,PWC_NAME);
|
|
strlcpy(cap->card, vdev->name, sizeof(cap->card));
|
|
usb_make_path(pdev->udev,cap->bus_info,sizeof(cap->bus_info));
|
|
cap->version = PWC_VERSION_CODE;
|
|
cap->capabilities =
|
|
V4L2_CAP_VIDEO_CAPTURE |
|
|
V4L2_CAP_STREAMING |
|
|
V4L2_CAP_READWRITE;
|
|
return 0;
|
|
}
|
|
|
|
case VIDIOC_ENUMINPUT:
|
|
{
|
|
struct v4l2_input *i = arg;
|
|
|
|
if ( i->index ) /* Only one INPUT is supported */
|
|
return -EINVAL;
|
|
|
|
memset(i, 0, sizeof(struct v4l2_input));
|
|
strcpy(i->name, "usb");
|
|
return 0;
|
|
}
|
|
|
|
case VIDIOC_G_INPUT:
|
|
{
|
|
int *i = arg;
|
|
*i = 0; /* Only one INPUT is supported */
|
|
return 0;
|
|
}
|
|
case VIDIOC_S_INPUT:
|
|
{
|
|
int *i = arg;
|
|
|
|
if ( *i ) { /* Only one INPUT is supported */
|
|
PWC_DEBUG_IOCTL("Only one input source is"\
|
|
" supported with this webcam.\n");
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* TODO: */
|
|
case VIDIOC_QUERYCTRL:
|
|
{
|
|
struct v4l2_queryctrl *c = arg;
|
|
int i;
|
|
|
|
PWC_DEBUG_IOCTL("ioctl(VIDIOC_QUERYCTRL) query id=%d\n", c->id);
|
|
for (i=0; i<sizeof(pwc_controls)/sizeof(struct v4l2_queryctrl); i++) {
|
|
if (pwc_controls[i].id == c->id) {
|
|
PWC_DEBUG_IOCTL("ioctl(VIDIOC_QUERYCTRL) found\n");
|
|
memcpy(c,&pwc_controls[i],sizeof(struct v4l2_queryctrl));
|
|
return 0;
|
|
}
|
|
}
|
|
PWC_DEBUG_IOCTL("ioctl(VIDIOC_QUERYCTRL) not found\n");
|
|
|
|
return -EINVAL;
|
|
}
|
|
case VIDIOC_G_CTRL:
|
|
{
|
|
struct v4l2_control *c = arg;
|
|
int ret;
|
|
|
|
switch (c->id)
|
|
{
|
|
case V4L2_CID_BRIGHTNESS:
|
|
c->value = pwc_get_brightness(pdev);
|
|
if (c->value<0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_CONTRAST:
|
|
c->value = pwc_get_contrast(pdev);
|
|
if (c->value<0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_SATURATION:
|
|
ret = pwc_get_saturation(pdev, &c->value);
|
|
if (ret<0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_GAMMA:
|
|
c->value = pwc_get_gamma(pdev);
|
|
if (c->value<0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_RED_BALANCE:
|
|
ret = pwc_get_red_gain(pdev, &c->value);
|
|
if (ret<0)
|
|
return -EINVAL;
|
|
c->value >>= 8;
|
|
return 0;
|
|
case V4L2_CID_BLUE_BALANCE:
|
|
ret = pwc_get_blue_gain(pdev, &c->value);
|
|
if (ret<0)
|
|
return -EINVAL;
|
|
c->value >>= 8;
|
|
return 0;
|
|
case V4L2_CID_AUTO_WHITE_BALANCE:
|
|
ret = pwc_get_awb(pdev);
|
|
if (ret<0)
|
|
return -EINVAL;
|
|
c->value = (ret == PWC_WB_MANUAL)?0:1;
|
|
return 0;
|
|
case V4L2_CID_GAIN:
|
|
ret = pwc_get_agc(pdev, &c->value);
|
|
if (ret<0)
|
|
return -EINVAL;
|
|
c->value >>= 8;
|
|
return 0;
|
|
case V4L2_CID_AUTOGAIN:
|
|
ret = pwc_get_agc(pdev, &c->value);
|
|
if (ret<0)
|
|
return -EINVAL;
|
|
c->value = (c->value < 0)?1:0;
|
|
return 0;
|
|
case V4L2_CID_EXPOSURE:
|
|
ret = pwc_get_shutter_speed(pdev, &c->value);
|
|
if (ret<0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_PRIVATE_COLOUR_MODE:
|
|
ret = pwc_get_colour_mode(pdev, &c->value);
|
|
if (ret < 0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_PRIVATE_AUTOCONTOUR:
|
|
ret = pwc_get_contour(pdev, &c->value);
|
|
if (ret < 0)
|
|
return -EINVAL;
|
|
c->value=(c->value == -1?1:0);
|
|
return 0;
|
|
case V4L2_CID_PRIVATE_CONTOUR:
|
|
ret = pwc_get_contour(pdev, &c->value);
|
|
if (ret < 0)
|
|
return -EINVAL;
|
|
c->value >>= 10;
|
|
return 0;
|
|
case V4L2_CID_PRIVATE_BACKLIGHT:
|
|
ret = pwc_get_backlight(pdev, &c->value);
|
|
if (ret < 0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_PRIVATE_FLICKERLESS:
|
|
ret = pwc_get_flicker(pdev, &c->value);
|
|
if (ret < 0)
|
|
return -EINVAL;
|
|
c->value=(c->value?1:0);
|
|
return 0;
|
|
case V4L2_CID_PRIVATE_NOISE_REDUCTION:
|
|
ret = pwc_get_dynamic_noise(pdev, &c->value);
|
|
if (ret < 0)
|
|
return -EINVAL;
|
|
return 0;
|
|
|
|
case V4L2_CID_PRIVATE_SAVE_USER:
|
|
case V4L2_CID_PRIVATE_RESTORE_USER:
|
|
case V4L2_CID_PRIVATE_RESTORE_FACTORY:
|
|
return -EINVAL;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
case VIDIOC_S_CTRL:
|
|
{
|
|
struct v4l2_control *c = arg;
|
|
int ret;
|
|
|
|
switch (c->id)
|
|
{
|
|
case V4L2_CID_BRIGHTNESS:
|
|
c->value <<= 9;
|
|
ret = pwc_set_brightness(pdev, c->value);
|
|
if (ret<0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_CONTRAST:
|
|
c->value <<= 10;
|
|
ret = pwc_set_contrast(pdev, c->value);
|
|
if (ret<0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_SATURATION:
|
|
ret = pwc_set_saturation(pdev, c->value);
|
|
if (ret<0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_GAMMA:
|
|
c->value <<= 11;
|
|
ret = pwc_set_gamma(pdev, c->value);
|
|
if (ret<0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_RED_BALANCE:
|
|
c->value <<= 8;
|
|
ret = pwc_set_red_gain(pdev, c->value);
|
|
if (ret<0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_BLUE_BALANCE:
|
|
c->value <<= 8;
|
|
ret = pwc_set_blue_gain(pdev, c->value);
|
|
if (ret<0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_AUTO_WHITE_BALANCE:
|
|
c->value = (c->value == 0)?PWC_WB_MANUAL:PWC_WB_AUTO;
|
|
ret = pwc_set_awb(pdev, c->value);
|
|
if (ret<0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_EXPOSURE:
|
|
c->value <<= 8;
|
|
ret = pwc_set_shutter_speed(pdev, c->value?0:1, c->value);
|
|
if (ret<0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_AUTOGAIN:
|
|
/* autogain off means nothing without a gain */
|
|
if (c->value == 0)
|
|
return 0;
|
|
ret = pwc_set_agc(pdev, c->value, 0);
|
|
if (ret<0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_GAIN:
|
|
c->value <<= 8;
|
|
ret = pwc_set_agc(pdev, 0, c->value);
|
|
if (ret<0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_PRIVATE_SAVE_USER:
|
|
if (pwc_save_user(pdev))
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_PRIVATE_RESTORE_USER:
|
|
if (pwc_restore_user(pdev))
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_PRIVATE_RESTORE_FACTORY:
|
|
if (pwc_restore_factory(pdev))
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_PRIVATE_COLOUR_MODE:
|
|
ret = pwc_set_colour_mode(pdev, c->value);
|
|
if (ret < 0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_PRIVATE_AUTOCONTOUR:
|
|
c->value=(c->value == 1)?-1:0;
|
|
ret = pwc_set_contour(pdev, c->value);
|
|
if (ret < 0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_PRIVATE_CONTOUR:
|
|
c->value <<= 10;
|
|
ret = pwc_set_contour(pdev, c->value);
|
|
if (ret < 0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_PRIVATE_BACKLIGHT:
|
|
ret = pwc_set_backlight(pdev, c->value);
|
|
if (ret < 0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case V4L2_CID_PRIVATE_FLICKERLESS:
|
|
ret = pwc_set_flicker(pdev, c->value);
|
|
if (ret < 0)
|
|
return -EINVAL;
|
|
case V4L2_CID_PRIVATE_NOISE_REDUCTION:
|
|
ret = pwc_set_dynamic_noise(pdev, c->value);
|
|
if (ret < 0)
|
|
return -EINVAL;
|
|
return 0;
|
|
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
case VIDIOC_ENUM_FMT:
|
|
{
|
|
struct v4l2_fmtdesc *f = arg;
|
|
int index;
|
|
|
|
if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
return -EINVAL;
|
|
|
|
/* We only support two format: the raw format, and YUV */
|
|
index = f->index;
|
|
memset(f,0,sizeof(struct v4l2_fmtdesc));
|
|
f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
f->index = index;
|
|
switch(index)
|
|
{
|
|
case 0:
|
|
/* RAW format */
|
|
f->pixelformat = pdev->type<=646?V4L2_PIX_FMT_PWC1:V4L2_PIX_FMT_PWC2;
|
|
f->flags = V4L2_FMT_FLAG_COMPRESSED;
|
|
strlcpy(f->description,"Raw Philips Webcam",sizeof(f->description));
|
|
break;
|
|
case 1:
|
|
f->pixelformat = V4L2_PIX_FMT_YUV420;
|
|
strlcpy(f->description,"4:2:0, planar, Y-Cb-Cr",sizeof(f->description));
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
case VIDIOC_G_FMT:
|
|
{
|
|
struct v4l2_format *f = arg;
|
|
|
|
PWC_DEBUG_IOCTL("ioctl(VIDIOC_G_FMT) return size %dx%d\n",pdev->image.x,pdev->image.y);
|
|
if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
return -EINVAL;
|
|
|
|
pwc_vidioc_fill_fmt(pdev, f);
|
|
|
|
return 0;
|
|
}
|
|
|
|
case VIDIOC_TRY_FMT:
|
|
return pwc_vidioc_try_fmt(pdev, arg);
|
|
|
|
case VIDIOC_S_FMT:
|
|
return pwc_vidioc_set_fmt(pdev, arg);
|
|
|
|
case VIDIOC_G_STD:
|
|
{
|
|
v4l2_std_id *std = arg;
|
|
*std = V4L2_STD_UNKNOWN;
|
|
return 0;
|
|
}
|
|
|
|
case VIDIOC_S_STD:
|
|
{
|
|
v4l2_std_id *std = arg;
|
|
if (*std != V4L2_STD_UNKNOWN)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
case VIDIOC_ENUMSTD:
|
|
{
|
|
struct v4l2_standard *std = arg;
|
|
if (std->index != 0)
|
|
return -EINVAL;
|
|
std->id = V4L2_STD_UNKNOWN;
|
|
strlcpy(std->name, "webcam", sizeof(std->name));
|
|
return 0;
|
|
}
|
|
|
|
case VIDIOC_REQBUFS:
|
|
{
|
|
struct v4l2_requestbuffers *rb = arg;
|
|
int nbuffers;
|
|
|
|
PWC_DEBUG_IOCTL("ioctl(VIDIOC_REQBUFS) count=%d\n",rb->count);
|
|
if (rb->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
return -EINVAL;
|
|
if (rb->memory != V4L2_MEMORY_MMAP)
|
|
return -EINVAL;
|
|
|
|
nbuffers = rb->count;
|
|
if (nbuffers < 2)
|
|
nbuffers = 2;
|
|
else if (nbuffers > pwc_mbufs)
|
|
nbuffers = pwc_mbufs;
|
|
/* Force to use our # of buffers */
|
|
rb->count = pwc_mbufs;
|
|
return 0;
|
|
}
|
|
|
|
case VIDIOC_QUERYBUF:
|
|
{
|
|
struct v4l2_buffer *buf = arg;
|
|
int index;
|
|
|
|
PWC_DEBUG_IOCTL("ioctl(VIDIOC_QUERYBUF) index=%d\n",buf->index);
|
|
if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
|
|
PWC_DEBUG_IOCTL("ioctl(VIDIOC_QUERYBUF) Bad type\n");
|
|
return -EINVAL;
|
|
}
|
|
if (buf->memory != V4L2_MEMORY_MMAP) {
|
|
PWC_DEBUG_IOCTL("ioctl(VIDIOC_QUERYBUF) Bad memory type\n");
|
|
return -EINVAL;
|
|
}
|
|
index = buf->index;
|
|
if (index < 0 || index >= pwc_mbufs) {
|
|
PWC_DEBUG_IOCTL("ioctl(VIDIOC_QUERYBUF) Bad index %d\n", buf->index);
|
|
return -EINVAL;
|
|
}
|
|
|
|
memset(buf, 0, sizeof(struct v4l2_buffer));
|
|
buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
buf->index = index;
|
|
buf->m.offset = index * pdev->len_per_image;
|
|
if (pdev->pixfmt != V4L2_PIX_FMT_YUV420)
|
|
buf->bytesused = pdev->frame_size + sizeof(struct pwc_raw_frame);
|
|
else
|
|
buf->bytesused = pdev->view.size;
|
|
buf->field = V4L2_FIELD_NONE;
|
|
buf->memory = V4L2_MEMORY_MMAP;
|
|
//buf->flags = V4L2_BUF_FLAG_MAPPED;
|
|
buf->length = pdev->len_per_image;
|
|
|
|
PWC_DEBUG_READ("VIDIOC_QUERYBUF: index=%d\n",buf->index);
|
|
PWC_DEBUG_READ("VIDIOC_QUERYBUF: m.offset=%d\n",buf->m.offset);
|
|
PWC_DEBUG_READ("VIDIOC_QUERYBUF: bytesused=%d\n",buf->bytesused);
|
|
|
|
return 0;
|
|
}
|
|
|
|
case VIDIOC_QBUF:
|
|
{
|
|
struct v4l2_buffer *buf = arg;
|
|
|
|
PWC_DEBUG_IOCTL("ioctl(VIDIOC_QBUF) index=%d\n",buf->index);
|
|
if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
return -EINVAL;
|
|
if (buf->memory != V4L2_MEMORY_MMAP)
|
|
return -EINVAL;
|
|
if (buf->index >= pwc_mbufs)
|
|
return -EINVAL;
|
|
|
|
buf->flags |= V4L2_BUF_FLAG_QUEUED;
|
|
buf->flags &= ~V4L2_BUF_FLAG_DONE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
case VIDIOC_DQBUF:
|
|
{
|
|
struct v4l2_buffer *buf = arg;
|
|
int ret;
|
|
|
|
PWC_DEBUG_IOCTL("ioctl(VIDIOC_DQBUF)\n");
|
|
|
|
if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
return -EINVAL;
|
|
|
|
/* Add ourselves to the frame wait-queue.
|
|
|
|
FIXME: needs auditing for safety.
|
|
QUESTION: In what respect? I think that using the
|
|
frameq is safe now.
|
|
*/
|
|
add_wait_queue(&pdev->frameq, &wait);
|
|
while (pdev->full_frames == NULL) {
|
|
if (pdev->error_status) {
|
|
remove_wait_queue(&pdev->frameq, &wait);
|
|
set_current_state(TASK_RUNNING);
|
|
return -pdev->error_status;
|
|
}
|
|
|
|
if (signal_pending(current)) {
|
|
remove_wait_queue(&pdev->frameq, &wait);
|
|
set_current_state(TASK_RUNNING);
|
|
return -ERESTARTSYS;
|
|
}
|
|
schedule();
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
}
|
|
remove_wait_queue(&pdev->frameq, &wait);
|
|
set_current_state(TASK_RUNNING);
|
|
|
|
PWC_DEBUG_IOCTL("VIDIOC_DQBUF: frame ready.\n");
|
|
/* Decompress data in pdev->images[pdev->fill_image] */
|
|
ret = pwc_handle_frame(pdev);
|
|
if (ret)
|
|
return -EFAULT;
|
|
PWC_DEBUG_IOCTL("VIDIOC_DQBUF: after pwc_handle_frame\n");
|
|
|
|
buf->index = pdev->fill_image;
|
|
if (pdev->pixfmt != V4L2_PIX_FMT_YUV420)
|
|
buf->bytesused = pdev->frame_size + sizeof(struct pwc_raw_frame);
|
|
else
|
|
buf->bytesused = pdev->view.size;
|
|
buf->flags = V4L2_BUF_FLAG_MAPPED;
|
|
buf->field = V4L2_FIELD_NONE;
|
|
do_gettimeofday(&buf->timestamp);
|
|
buf->sequence = 0;
|
|
buf->memory = V4L2_MEMORY_MMAP;
|
|
buf->m.offset = pdev->fill_image * pdev->len_per_image;
|
|
buf->length = pdev->len_per_image;
|
|
pwc_next_image(pdev);
|
|
|
|
PWC_DEBUG_IOCTL("VIDIOC_DQBUF: buf->index=%d\n",buf->index);
|
|
PWC_DEBUG_IOCTL("VIDIOC_DQBUF: buf->length=%d\n",buf->length);
|
|
PWC_DEBUG_IOCTL("VIDIOC_DQBUF: m.offset=%d\n",buf->m.offset);
|
|
PWC_DEBUG_IOCTL("VIDIOC_DQBUF: bytesused=%d\n",buf->bytesused);
|
|
PWC_DEBUG_IOCTL("VIDIOC_DQBUF: leaving\n");
|
|
return 0;
|
|
|
|
}
|
|
|
|
case VIDIOC_STREAMON:
|
|
{
|
|
/* WARNING: pwc_try_video_mode() called pwc_isoc_init */
|
|
pwc_isoc_init(pdev);
|
|
return 0;
|
|
}
|
|
|
|
case VIDIOC_STREAMOFF:
|
|
{
|
|
pwc_isoc_cleanup(pdev);
|
|
return 0;
|
|
}
|
|
|
|
case VIDIOC_ENUM_FRAMESIZES:
|
|
{
|
|
struct v4l2_frmsizeenum *fsize = arg;
|
|
unsigned int i = 0, index = fsize->index;
|
|
|
|
if (fsize->pixel_format == V4L2_PIX_FMT_YUV420) {
|
|
for (i = 0; i < PSZ_MAX; i++) {
|
|
if (pdev->image_mask & (1UL << i)) {
|
|
if (!index--) {
|
|
fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
|
|
fsize->discrete.width = pwc_image_sizes[i].x;
|
|
fsize->discrete.height = pwc_image_sizes[i].y;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
} else if (fsize->index == 0 &&
|
|
((fsize->pixel_format == V4L2_PIX_FMT_PWC1 && DEVICE_USE_CODEC1(pdev->type)) ||
|
|
(fsize->pixel_format == V4L2_PIX_FMT_PWC2 && DEVICE_USE_CODEC23(pdev->type)))) {
|
|
|
|
fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
|
|
fsize->discrete.width = pdev->abs_max.x;
|
|
fsize->discrete.height = pdev->abs_max.y;
|
|
return 0;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
case VIDIOC_ENUM_FRAMEINTERVALS:
|
|
{
|
|
struct v4l2_frmivalenum *fival = arg;
|
|
int size = -1;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < PSZ_MAX; i++) {
|
|
if (pwc_image_sizes[i].x == fival->width &&
|
|
pwc_image_sizes[i].y == fival->height) {
|
|
size = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* TODO: Support raw format */
|
|
if (size < 0 || fival->pixel_format != V4L2_PIX_FMT_YUV420) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
i = pwc_get_fps(pdev, fival->index, size);
|
|
if (!i)
|
|
return -EINVAL;
|
|
|
|
fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
|
|
fival->discrete.numerator = 1;
|
|
fival->discrete.denominator = i;
|
|
|
|
return 0;
|
|
}
|
|
|
|
default:
|
|
return pwc_ioctl(pdev, cmd, arg);
|
|
} /* ..switch */
|
|
return 0;
|
|
}
|
|
|
|
/* vim: set cino= formatoptions=croql cindent shiftwidth=8 tabstop=8: */
|