#define DRIVER_DESC "Empia em28xx based USB video device driver"
-#define EM28XX_VERSION "0.1.3"
+#define EM28XX_VERSION "0.2.0"
#define em28xx_videodbg(fmt, arg...) do {\
if (video_debug) \
q = &dev->vb_vidq;
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q->io_modes = VB2_READ | VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+ q->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
q->drv_priv = dev;
q->buf_struct_size = sizeof(struct em28xx_buffer);
q->ops = &em28xx_video_qops;
q = &dev->vb_vbiq;
q->type = V4L2_BUF_TYPE_VBI_CAPTURE;
q->io_modes = VB2_READ | VB2_MMAP | VB2_USERPTR;
+ q->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
q->drv_priv = dev;
q->buf_struct_size = sizeof(struct em28xx_buffer);
q->ops = &em28xx_vbi_qops;
static int em28xx_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct em28xx *dev = container_of(ctrl->handler, struct em28xx, ctrl_handler);
+ int ret = -EINVAL;
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
dev->mute = ctrl->val;
+ ret = em28xx_audio_analog_set(dev);
break;
case V4L2_CID_AUDIO_VOLUME:
dev->volume = ctrl->val;
+ ret = em28xx_audio_analog_set(dev);
+ break;
+ case V4L2_CID_CONTRAST:
+ ret = em28xx_write_reg(dev, EM28XX_R20_YGAIN, ctrl->val);
+ break;
+ case V4L2_CID_BRIGHTNESS:
+ ret = em28xx_write_reg(dev, EM28XX_R21_YOFFSET, ctrl->val);
+ break;
+ case V4L2_CID_SATURATION:
+ ret = em28xx_write_reg(dev, EM28XX_R22_UVGAIN, ctrl->val);
+ break;
+ case V4L2_CID_BLUE_BALANCE:
+ ret = em28xx_write_reg(dev, EM28XX_R23_UOFFSET, ctrl->val);
+ break;
+ case V4L2_CID_RED_BALANCE:
+ ret = em28xx_write_reg(dev, EM28XX_R24_VOFFSET, ctrl->val);
+ break;
+ case V4L2_CID_SHARPNESS:
+ ret = em28xx_write_reg(dev, EM28XX_R25_SHARPNESS, ctrl->val);
break;
}
- return em28xx_audio_analog_set(dev);
+ return (ret < 0) ? ret : 0;
}
const struct v4l2_ctrl_ops em28xx_ctrl_ops = {
.s_ctrl = em28xx_s_ctrl,
};
-static int check_dev(struct em28xx *dev)
-{
- if (dev->disconnected) {
- em28xx_errdev("v4l2 ioctl: device not present\n");
- return -ENODEV;
- }
- return 0;
-}
-
-static void get_scale(struct em28xx *dev,
+static void size_to_scale(struct em28xx *dev,
unsigned int width, unsigned int height,
unsigned int *hscale, unsigned int *vscale)
{
unsigned int maxh = norm_maxh(dev);
*hscale = (((unsigned long)maxw) << 12) / width - 4096L;
- if (*hscale >= 0x4000)
- *hscale = 0x3fff;
+ if (*hscale > EM28XX_HVSCALE_MAX)
+ *hscale = EM28XX_HVSCALE_MAX;
*vscale = (((unsigned long)maxh) << 12) / height - 4096L;
- if (*vscale >= 0x4000)
- *vscale = 0x3fff;
+ if (*vscale > EM28XX_HVSCALE_MAX)
+ *vscale = EM28XX_HVSCALE_MAX;
+}
+
+static void scale_to_size(struct em28xx *dev,
+ unsigned int hscale, unsigned int vscale,
+ unsigned int *width, unsigned int *height)
+{
+ unsigned int maxw = norm_maxw(dev);
+ unsigned int maxh = norm_maxh(dev);
+
+ *width = (((unsigned long)maxw) << 12) / (hscale + 4096L);
+ *height = (((unsigned long)maxh) << 12) / (vscale + 4096L);
}
/* ------------------------------------------------------------------
1, 0);
}
- get_scale(dev, width, height, &hscale, &vscale);
-
- width = (((unsigned long)maxw) << 12) / (hscale + 4096L);
- height = (((unsigned long)maxh) << 12) / (vscale + 4096L);
+ size_to_scale(dev, width, height, &hscale, &vscale);
+ scale_to_size(dev, hscale, hscale, &width, &height);
f->fmt.pix.width = width;
f->fmt.pix.height = height;
dev->height = height;
/* set new image size */
- get_scale(dev, dev->width, dev->height, &dev->hscale, &dev->vscale);
+ size_to_scale(dev, dev->width, dev->height, &dev->hscale, &dev->vscale);
em28xx_resolution_set(dev);
{
struct em28xx_fh *fh = priv;
struct em28xx *dev = fh->dev;
- int rc;
-
- if (dev->board.is_webcam)
- return -ENOTTY;
- rc = check_dev(dev);
- if (rc < 0)
- return rc;
*norm = dev->norm;
{
struct em28xx_fh *fh = priv;
struct em28xx *dev = fh->dev;
- int rc;
-
- if (dev->board.is_webcam)
- return -ENOTTY;
- rc = check_dev(dev);
- if (rc < 0)
- return rc;
v4l2_device_call_all(&dev->v4l2_dev, 0, video, querystd, norm);
return 0;
}
-static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id *norm)
+static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id norm)
{
struct em28xx_fh *fh = priv;
struct em28xx *dev = fh->dev;
struct v4l2_format f;
- int rc;
- if (dev->board.is_webcam)
- return -ENOTTY;
- if (*norm == dev->norm)
+ if (norm == dev->norm)
return 0;
- rc = check_dev(dev);
- if (rc < 0)
- return rc;
if (dev->streaming_users > 0)
return -EBUSY;
- dev->norm = *norm;
+ dev->norm = norm;
/* Adjusts width/height, if needed */
f.fmt.pix.width = 720;
- f.fmt.pix.height = (*norm & V4L2_STD_525_60) ? 480 : 576;
+ f.fmt.pix.height = (norm & V4L2_STD_525_60) ? 480 : 576;
vidioc_try_fmt_vid_cap(file, priv, &f);
/* set new image size */
dev->width = f.fmt.pix.width;
dev->height = f.fmt.pix.height;
- get_scale(dev, dev->width, dev->height, &dev->hscale, &dev->vscale);
+ size_to_scale(dev, dev->width, dev->height, &dev->hscale, &dev->vscale);
em28xx_resolution_set(dev);
v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_std, dev->norm);
struct em28xx *dev = fh->dev;
int rc = 0;
- if (p->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
- return -EINVAL;
-
p->parm.capture.readbuffers = EM28XX_MIN_BUF;
if (dev->board.is_webcam)
rc = v4l2_device_call_until_err(&dev->v4l2_dev, 0,
struct em28xx_fh *fh = priv;
struct em28xx *dev = fh->dev;
- if (!dev->board.is_webcam)
- return -ENOTTY;
-
- if (p->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
- return -EINVAL;
-
p->parm.capture.readbuffers = EM28XX_MIN_BUF;
return v4l2_device_call_until_err(&dev->v4l2_dev, 0, video, s_parm, p);
}
{
struct em28xx_fh *fh = priv;
struct em28xx *dev = fh->dev;
- int rc;
-
- rc = check_dev(dev);
- if (rc < 0)
- return rc;
if (i >= MAX_EM28XX_INPUT)
return -EINVAL;
struct em28xx_fh *fh = priv;
struct em28xx *dev = fh->dev;
- if (!dev->audio_mode.has_audio)
- return -EINVAL;
-
switch (a->index) {
case EM28XX_AMUX_VIDEO:
strcpy(a->name, "Television");
struct em28xx_fh *fh = priv;
struct em28xx *dev = fh->dev;
-
- if (!dev->audio_mode.has_audio)
- return -EINVAL;
-
if (a->index >= MAX_EM28XX_INPUT)
return -EINVAL;
if (0 == INPUT(a->index)->type)
{
struct em28xx_fh *fh = priv;
struct em28xx *dev = fh->dev;
- int rc;
-
- rc = check_dev(dev);
- if (rc < 0)
- return rc;
if (0 != t->index)
return -EINVAL;
}
static int vidioc_s_tuner(struct file *file, void *priv,
- struct v4l2_tuner *t)
+ const struct v4l2_tuner *t)
{
struct em28xx_fh *fh = priv;
struct em28xx *dev = fh->dev;
- int rc;
-
- rc = check_dev(dev);
- if (rc < 0)
- return rc;
if (0 != t->index)
return -EINVAL;
}
static int vidioc_s_frequency(struct file *file, void *priv,
- struct v4l2_frequency *f)
+ const struct v4l2_frequency *f)
{
+ struct v4l2_frequency new_freq = *f;
struct em28xx_fh *fh = priv;
struct em28xx *dev = fh->dev;
- int rc;
-
- rc = check_dev(dev);
- if (rc < 0)
- return rc;
if (0 != f->tuner)
return -EINVAL;
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, f);
- v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_frequency, f);
- dev->ctl_freq = f->frequency;
+ v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_frequency, &new_freq);
+ dev->ctl_freq = new_freq.frequency;
return 0;
}
-#ifdef CONFIG_VIDEO_ADV_DEBUG
-static int em28xx_reg_len(int reg)
-{
- switch (reg) {
- case EM28XX_R40_AC97LSB:
- case EM28XX_R30_HSCALELOW:
- case EM28XX_R32_VSCALELOW:
- return 2;
- default:
- return 1;
- }
-}
-
static int vidioc_g_chip_ident(struct file *file, void *priv,
struct v4l2_dbg_chip_ident *chip)
{
return 0;
}
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int em28xx_reg_len(int reg)
+{
+ switch (reg) {
+ case EM28XX_R40_AC97LSB:
+ case EM28XX_R30_HSCALELOW:
+ case EM28XX_R32_VSCALELOW:
+ return 2;
+ default:
+ return 1;
+ }
+}
static int vidioc_g_register(struct file *file, void *priv,
struct v4l2_dbg_register *reg)
}
static int vidioc_s_register(struct file *file, void *priv,
- struct v4l2_dbg_register *reg)
+ const struct v4l2_dbg_register *reg)
{
struct em28xx_fh *fh = priv;
struct em28xx *dev = fh->dev;
#endif
-static int vidioc_cropcap(struct file *file, void *priv,
- struct v4l2_cropcap *cc)
-{
- struct em28xx_fh *fh = priv;
- struct em28xx *dev = fh->dev;
-
- if (cc->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
- return -EINVAL;
-
- cc->bounds.left = 0;
- cc->bounds.top = 0;
- cc->bounds.width = dev->width;
- cc->bounds.height = dev->height;
- cc->defrect = cc->bounds;
- cc->pixelaspect.numerator = 54; /* 4:3 FIXME: remove magic numbers */
- cc->pixelaspect.denominator = 59;
-
- return 0;
-}
-
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
/* Report a continuous range */
fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
- fsize->stepwise.min_width = 48;
- fsize->stepwise.min_height = 32;
+ scale_to_size(dev, EM28XX_HVSCALE_MAX, EM28XX_HVSCALE_MAX,
+ &fsize->stepwise.min_width, &fsize->stepwise.min_height);
+ if (fsize->stepwise.min_width < 48)
+ fsize->stepwise.min_width = 48;
+ if (fsize->stepwise.min_height < 38)
+ fsize->stepwise.min_height = 38;
fsize->stepwise.max_width = maxw;
fsize->stepwise.max_height = maxh;
fsize->stepwise.step_width = 1;
return 0;
}
-static int vidioc_s_fmt_vbi_cap(struct file *file, void *priv,
- struct v4l2_format *format)
-{
- struct em28xx_fh *fh = priv;
- struct em28xx *dev = fh->dev;
-
- format->fmt.vbi.samples_per_line = dev->vbi_width;
- format->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
- format->fmt.vbi.offset = 0;
- format->fmt.vbi.flags = 0;
- format->fmt.vbi.sampling_rate = 6750000 * 4 / 2;
- format->fmt.vbi.count[0] = dev->vbi_height;
- format->fmt.vbi.count[1] = dev->vbi_height;
- memset(format->fmt.vbi.reserved, 0, sizeof(format->fmt.vbi.reserved));
-
- /* Varies by video standard (NTSC, PAL, etc.) */
- if (dev->norm & V4L2_STD_525_60) {
- /* NTSC */
- format->fmt.vbi.start[0] = 10;
- format->fmt.vbi.start[1] = 273;
- } else if (dev->norm & V4L2_STD_625_50) {
- /* PAL */
- format->fmt.vbi.start[0] = 6;
- format->fmt.vbi.start[1] = 318;
- }
-
- return 0;
-}
-
/* ----------------------------------------------------------- */
/* RADIO ESPECIFIC IOCTLS */
/* ----------------------------------------------------------- */
return -EINVAL;
strcpy(t->name, "Radio");
- t->type = V4L2_TUNER_RADIO;
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_tuner, t);
}
static int radio_s_tuner(struct file *file, void *priv,
- struct v4l2_tuner *t)
+ const struct v4l2_tuner *t)
{
struct em28xx *dev = ((struct em28xx_fh *)priv)->dev;
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
.vidioc_g_fmt_vbi_cap = vidioc_g_fmt_vbi_cap,
.vidioc_try_fmt_vbi_cap = vidioc_g_fmt_vbi_cap,
- .vidioc_s_fmt_vbi_cap = vidioc_s_fmt_vbi_cap,
+ .vidioc_s_fmt_vbi_cap = vidioc_g_fmt_vbi_cap,
.vidioc_enum_framesizes = vidioc_enum_framesizes,
.vidioc_g_audio = vidioc_g_audio,
.vidioc_s_audio = vidioc_s_audio,
- .vidioc_cropcap = vidioc_cropcap,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_s_frequency = vidioc_s_frequency,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+ .vidioc_g_chip_ident = vidioc_g_chip_ident,
#ifdef CONFIG_VIDEO_ADV_DEBUG
.vidioc_g_register = vidioc_g_register,
.vidioc_s_register = vidioc_s_register,
- .vidioc_g_chip_ident = vidioc_g_chip_ident,
#endif
};
.vidioc_s_frequency = vidioc_s_frequency,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+ .vidioc_g_chip_ident = vidioc_g_chip_ident,
#ifdef CONFIG_VIDEO_ADV_DEBUG
.vidioc_g_register = vidioc_g_register,
.vidioc_s_register = vidioc_s_register,
(EM28XX_XCLK_AUDIO_UNMUTE | val));
em28xx_set_outfmt(dev);
- em28xx_colorlevels_set_default(dev);
em28xx_compression_disable(dev);
+ /* Add image controls */
+ /* NOTE: at this point, the subdevices are already registered, so bridge
+ * controls are only added/enabled when no subdevice provides them */
+ if (NULL == v4l2_ctrl_find(&dev->ctrl_handler, V4L2_CID_CONTRAST))
+ v4l2_ctrl_new_std(&dev->ctrl_handler, &em28xx_ctrl_ops,
+ V4L2_CID_CONTRAST,
+ 0, 0x1f, 1, CONTRAST_DEFAULT);
+ if (NULL == v4l2_ctrl_find(&dev->ctrl_handler, V4L2_CID_BRIGHTNESS))
+ v4l2_ctrl_new_std(&dev->ctrl_handler, &em28xx_ctrl_ops,
+ V4L2_CID_BRIGHTNESS,
+ -0x80, 0x7f, 1, BRIGHTNESS_DEFAULT);
+ if (NULL == v4l2_ctrl_find(&dev->ctrl_handler, V4L2_CID_SATURATION))
+ v4l2_ctrl_new_std(&dev->ctrl_handler, &em28xx_ctrl_ops,
+ V4L2_CID_SATURATION,
+ 0, 0x1f, 1, SATURATION_DEFAULT);
+ if (NULL == v4l2_ctrl_find(&dev->ctrl_handler, V4L2_CID_BLUE_BALANCE))
+ v4l2_ctrl_new_std(&dev->ctrl_handler, &em28xx_ctrl_ops,
+ V4L2_CID_BLUE_BALANCE,
+ -0x30, 0x30, 1, BLUE_BALANCE_DEFAULT);
+ if (NULL == v4l2_ctrl_find(&dev->ctrl_handler, V4L2_CID_RED_BALANCE))
+ v4l2_ctrl_new_std(&dev->ctrl_handler, &em28xx_ctrl_ops,
+ V4L2_CID_RED_BALANCE,
+ -0x30, 0x30, 1, RED_BALANCE_DEFAULT);
+ if (NULL == v4l2_ctrl_find(&dev->ctrl_handler, V4L2_CID_SHARPNESS))
+ v4l2_ctrl_new_std(&dev->ctrl_handler, &em28xx_ctrl_ops,
+ V4L2_CID_SHARPNESS,
+ 0, 0x0f, 1, SHARPNESS_DEFAULT);
+
+ /* Reset image controls */
+ em28xx_colorlevels_set_default(dev);
+ v4l2_ctrl_handler_setup(&dev->ctrl_handler);
+ if (dev->ctrl_handler.error)
+ return dev->ctrl_handler.error;
+
/* allocate and fill video video_device struct */
dev->vdev = em28xx_vdev_init(dev, &em28xx_video_template, "video");
if (!dev->vdev) {
dev->vdev->queue = &dev->vb_vidq;
dev->vdev->queue->lock = &dev->vb_queue_lock;
+ /* disable inapplicable ioctls */
+ if (dev->board.is_webcam) {
+ v4l2_disable_ioctl(dev->vdev, VIDIOC_QUERYSTD);
+ v4l2_disable_ioctl(dev->vdev, VIDIOC_G_STD);
+ v4l2_disable_ioctl(dev->vdev, VIDIOC_S_STD);
+ } else {
+ v4l2_disable_ioctl(dev->vdev, VIDIOC_S_PARM);
+ }
+ if (dev->tuner_type == TUNER_ABSENT) {
+ v4l2_disable_ioctl(dev->vdev, VIDIOC_G_TUNER);
+ v4l2_disable_ioctl(dev->vdev, VIDIOC_S_TUNER);
+ v4l2_disable_ioctl(dev->vdev, VIDIOC_G_FREQUENCY);
+ v4l2_disable_ioctl(dev->vdev, VIDIOC_S_FREQUENCY);
+ }
+ if (!dev->audio_mode.has_audio) {
+ v4l2_disable_ioctl(dev->vdev, VIDIOC_G_AUDIO);
+ v4l2_disable_ioctl(dev->vdev, VIDIOC_S_AUDIO);
+ }
+
/* register v4l2 video video_device */
ret = video_register_device(dev->vdev, VFL_TYPE_GRABBER,
video_nr[dev->devno]);
dev->vbi_dev->queue = &dev->vb_vbiq;
dev->vbi_dev->queue->lock = &dev->vb_vbi_queue_lock;
+ /* disable inapplicable ioctls */
+ v4l2_disable_ioctl(dev->vdev, VIDIOC_S_PARM);
+ if (dev->tuner_type == TUNER_ABSENT) {
+ v4l2_disable_ioctl(dev->vbi_dev, VIDIOC_G_TUNER);
+ v4l2_disable_ioctl(dev->vbi_dev, VIDIOC_S_TUNER);
+ v4l2_disable_ioctl(dev->vbi_dev, VIDIOC_G_FREQUENCY);
+ v4l2_disable_ioctl(dev->vbi_dev, VIDIOC_S_FREQUENCY);
+ }
+ if (!dev->audio_mode.has_audio) {
+ v4l2_disable_ioctl(dev->vbi_dev, VIDIOC_G_AUDIO);
+ v4l2_disable_ioctl(dev->vbi_dev, VIDIOC_S_AUDIO);
+ }
+
/* register v4l2 vbi video_device */
ret = video_register_device(dev->vbi_dev, VFL_TYPE_VBI,
vbi_nr[dev->devno]);