/* 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: */