--- /dev/null
- cap->capabilities = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING;
+/*
+ * A virtual v4l2-mem2mem example device.
+ *
+ * This is a virtual device driver for testing mem-to-mem videobuf framework.
+ * It simulates a device that uses memory buffers for both source and
+ * destination, processes the data and issues an "irq" (simulated by a timer).
+ * The device is capable of multi-instance, multi-buffer-per-transaction
+ * operation (via the mem2mem framework).
+ *
+ * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd.
+ * Pawel Osciak, <pawel@osciak.com>
+ * Marek Szyprowski, <m.szyprowski@samsung.com>
+ *
+ * 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
+ */
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/timer.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include <linux/platform_device.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-vmalloc.h>
+
+#define MEM2MEM_TEST_MODULE_NAME "mem2mem-testdev"
+
+MODULE_DESCRIPTION("Virtual device for mem2mem framework testing");
+MODULE_AUTHOR("Pawel Osciak, <pawel@osciak.com>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("0.1.1");
+
+#define MIN_W 32
+#define MIN_H 32
+#define MAX_W 640
+#define MAX_H 480
+#define DIM_ALIGN_MASK 7 /* 8-byte alignment for line length */
+
+/* Flags that indicate a format can be used for capture/output */
+#define MEM2MEM_CAPTURE (1 << 0)
+#define MEM2MEM_OUTPUT (1 << 1)
+
+#define MEM2MEM_NAME "m2m-testdev"
+
+/* Per queue */
+#define MEM2MEM_DEF_NUM_BUFS VIDEO_MAX_FRAME
+/* In bytes, per queue */
+#define MEM2MEM_VID_MEM_LIMIT (16 * 1024 * 1024)
+
+/* Default transaction time in msec */
+#define MEM2MEM_DEF_TRANSTIME 1000
+/* Default number of buffers per transaction */
+#define MEM2MEM_DEF_TRANSLEN 1
+#define MEM2MEM_COLOR_STEP (0xff >> 4)
+#define MEM2MEM_NUM_TILES 8
+
+/* Flags that indicate processing mode */
+#define MEM2MEM_HFLIP (1 << 0)
+#define MEM2MEM_VFLIP (1 << 1)
+
+#define dprintk(dev, fmt, arg...) \
+ v4l2_dbg(1, 1, &dev->v4l2_dev, "%s: " fmt, __func__, ## arg)
+
+
+void m2mtest_dev_release(struct device *dev)
+{}
+
+static struct platform_device m2mtest_pdev = {
+ .name = MEM2MEM_NAME,
+ .dev.release = m2mtest_dev_release,
+};
+
+struct m2mtest_fmt {
+ char *name;
+ u32 fourcc;
+ int depth;
+ /* Types the format can be used for */
+ u32 types;
+};
+
+static struct m2mtest_fmt formats[] = {
+ {
+ .name = "RGB565 (BE)",
+ .fourcc = V4L2_PIX_FMT_RGB565X, /* rrrrrggg gggbbbbb */
+ .depth = 16,
+ /* Both capture and output format */
+ .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT,
+ },
+ {
+ .name = "4:2:2, packed, YUYV",
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .depth = 16,
+ /* Output-only format */
+ .types = MEM2MEM_OUTPUT,
+ },
+};
+
+#define NUM_FORMATS ARRAY_SIZE(formats)
+
+/* Per-queue, driver-specific private data */
+struct m2mtest_q_data {
+ unsigned int width;
+ unsigned int height;
+ unsigned int sizeimage;
+ struct m2mtest_fmt *fmt;
+};
+
+enum {
+ V4L2_M2M_SRC = 0,
+ V4L2_M2M_DST = 1,
+};
+
+#define V4L2_CID_TRANS_TIME_MSEC (V4L2_CID_USER_BASE + 0x1000)
+#define V4L2_CID_TRANS_NUM_BUFS (V4L2_CID_USER_BASE + 0x1001)
+
+static struct m2mtest_fmt *find_format(struct v4l2_format *f)
+{
+ struct m2mtest_fmt *fmt;
+ unsigned int k;
+
+ for (k = 0; k < NUM_FORMATS; k++) {
+ fmt = &formats[k];
+ if (fmt->fourcc == f->fmt.pix.pixelformat)
+ break;
+ }
+
+ if (k == NUM_FORMATS)
+ return NULL;
+
+ return &formats[k];
+}
+
+struct m2mtest_dev {
+ struct v4l2_device v4l2_dev;
+ struct video_device *vfd;
+
+ atomic_t num_inst;
+ struct mutex dev_mutex;
+ spinlock_t irqlock;
+
+ struct timer_list timer;
+
+ struct v4l2_m2m_dev *m2m_dev;
+};
+
+struct m2mtest_ctx {
+ struct v4l2_fh fh;
+ struct m2mtest_dev *dev;
+
+ struct v4l2_ctrl_handler hdl;
+
+ /* Processed buffers in this transaction */
+ u8 num_processed;
+
+ /* Transaction length (i.e. how many buffers per transaction) */
+ u32 translen;
+ /* Transaction time (i.e. simulated processing time) in milliseconds */
+ u32 transtime;
+
+ /* Abort requested by m2m */
+ int aborting;
+
+ /* Processing mode */
+ int mode;
+
+ enum v4l2_colorspace colorspace;
+
+ struct v4l2_m2m_ctx *m2m_ctx;
+
+ /* Source and destination queue data */
+ struct m2mtest_q_data q_data[2];
+};
+
+static inline struct m2mtest_ctx *file2ctx(struct file *file)
+{
+ return container_of(file->private_data, struct m2mtest_ctx, fh);
+}
+
+static struct m2mtest_q_data *get_q_data(struct m2mtest_ctx *ctx,
+ enum v4l2_buf_type type)
+{
+ switch (type) {
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+ return &ctx->q_data[V4L2_M2M_SRC];
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ return &ctx->q_data[V4L2_M2M_DST];
+ default:
+ BUG();
+ }
+ return NULL;
+}
+
+
+static int device_process(struct m2mtest_ctx *ctx,
+ struct vb2_buffer *in_vb,
+ struct vb2_buffer *out_vb)
+{
+ struct m2mtest_dev *dev = ctx->dev;
+ struct m2mtest_q_data *q_data;
+ u8 *p_in, *p_out;
+ int x, y, t, w;
+ int tile_w, bytes_left;
+ int width, height, bytesperline;
+
+ q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT);
+
+ width = q_data->width;
+ height = q_data->height;
+ bytesperline = (q_data->width * q_data->fmt->depth) >> 3;
+
+ p_in = vb2_plane_vaddr(in_vb, 0);
+ p_out = vb2_plane_vaddr(out_vb, 0);
+ if (!p_in || !p_out) {
+ v4l2_err(&dev->v4l2_dev,
+ "Acquiring kernel pointers to buffers failed\n");
+ return -EFAULT;
+ }
+
+ if (vb2_plane_size(in_vb, 0) > vb2_plane_size(out_vb, 0)) {
+ v4l2_err(&dev->v4l2_dev, "Output buffer is too small\n");
+ return -EINVAL;
+ }
+
+ tile_w = (width * (q_data[V4L2_M2M_DST].fmt->depth >> 3))
+ / MEM2MEM_NUM_TILES;
+ bytes_left = bytesperline - tile_w * MEM2MEM_NUM_TILES;
+ w = 0;
+
+ switch (ctx->mode) {
+ case MEM2MEM_HFLIP | MEM2MEM_VFLIP:
+ p_out += bytesperline * height - bytes_left;
+ for (y = 0; y < height; ++y) {
+ for (t = 0; t < MEM2MEM_NUM_TILES; ++t) {
+ if (w & 0x1) {
+ for (x = 0; x < tile_w; ++x)
+ *--p_out = *p_in++ +
+ MEM2MEM_COLOR_STEP;
+ } else {
+ for (x = 0; x < tile_w; ++x)
+ *--p_out = *p_in++ -
+ MEM2MEM_COLOR_STEP;
+ }
+ ++w;
+ }
+ p_in += bytes_left;
+ p_out -= bytes_left;
+ }
+ break;
+
+ case MEM2MEM_HFLIP:
+ for (y = 0; y < height; ++y) {
+ p_out += MEM2MEM_NUM_TILES * tile_w;
+ for (t = 0; t < MEM2MEM_NUM_TILES; ++t) {
+ if (w & 0x01) {
+ for (x = 0; x < tile_w; ++x)
+ *--p_out = *p_in++ +
+ MEM2MEM_COLOR_STEP;
+ } else {
+ for (x = 0; x < tile_w; ++x)
+ *--p_out = *p_in++ -
+ MEM2MEM_COLOR_STEP;
+ }
+ ++w;
+ }
+ p_in += bytes_left;
+ p_out += bytesperline;
+ }
+ break;
+
+ case MEM2MEM_VFLIP:
+ p_out += bytesperline * (height - 1);
+ for (y = 0; y < height; ++y) {
+ for (t = 0; t < MEM2MEM_NUM_TILES; ++t) {
+ if (w & 0x1) {
+ for (x = 0; x < tile_w; ++x)
+ *p_out++ = *p_in++ +
+ MEM2MEM_COLOR_STEP;
+ } else {
+ for (x = 0; x < tile_w; ++x)
+ *p_out++ = *p_in++ -
+ MEM2MEM_COLOR_STEP;
+ }
+ ++w;
+ }
+ p_in += bytes_left;
+ p_out += bytes_left - 2 * bytesperline;
+ }
+ break;
+
+ default:
+ for (y = 0; y < height; ++y) {
+ for (t = 0; t < MEM2MEM_NUM_TILES; ++t) {
+ if (w & 0x1) {
+ for (x = 0; x < tile_w; ++x)
+ *p_out++ = *p_in++ +
+ MEM2MEM_COLOR_STEP;
+ } else {
+ for (x = 0; x < tile_w; ++x)
+ *p_out++ = *p_in++ -
+ MEM2MEM_COLOR_STEP;
+ }
+ ++w;
+ }
+ p_in += bytes_left;
+ p_out += bytes_left;
+ }
+ }
+
+ return 0;
+}
+
+static void schedule_irq(struct m2mtest_dev *dev, int msec_timeout)
+{
+ dprintk(dev, "Scheduling a simulated irq\n");
+ mod_timer(&dev->timer, jiffies + msecs_to_jiffies(msec_timeout));
+}
+
+/*
+ * mem2mem callbacks
+ */
+
+/**
+ * job_ready() - check whether an instance is ready to be scheduled to run
+ */
+static int job_ready(void *priv)
+{
+ struct m2mtest_ctx *ctx = priv;
+
+ if (v4l2_m2m_num_src_bufs_ready(ctx->m2m_ctx) < ctx->translen
+ || v4l2_m2m_num_dst_bufs_ready(ctx->m2m_ctx) < ctx->translen) {
+ dprintk(ctx->dev, "Not enough buffers available\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+static void job_abort(void *priv)
+{
+ struct m2mtest_ctx *ctx = priv;
+
+ /* Will cancel the transaction in the next interrupt handler */
+ ctx->aborting = 1;
+}
+
+static void m2mtest_lock(void *priv)
+{
+ struct m2mtest_ctx *ctx = priv;
+ struct m2mtest_dev *dev = ctx->dev;
+ mutex_lock(&dev->dev_mutex);
+}
+
+static void m2mtest_unlock(void *priv)
+{
+ struct m2mtest_ctx *ctx = priv;
+ struct m2mtest_dev *dev = ctx->dev;
+ mutex_unlock(&dev->dev_mutex);
+}
+
+
+/* device_run() - prepares and starts the device
+ *
+ * This simulates all the immediate preparations required before starting
+ * a device. This will be called by the framework when it decides to schedule
+ * a particular instance.
+ */
+static void device_run(void *priv)
+{
+ struct m2mtest_ctx *ctx = priv;
+ struct m2mtest_dev *dev = ctx->dev;
+ struct vb2_buffer *src_buf, *dst_buf;
+
+ src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx);
+ dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx);
+
+ device_process(ctx, src_buf, dst_buf);
+
+ /* Run a timer, which simulates a hardware irq */
+ schedule_irq(dev, ctx->transtime);
+}
+
+static void device_isr(unsigned long priv)
+{
+ struct m2mtest_dev *m2mtest_dev = (struct m2mtest_dev *)priv;
+ struct m2mtest_ctx *curr_ctx;
+ struct vb2_buffer *src_vb, *dst_vb;
+ unsigned long flags;
+
+ curr_ctx = v4l2_m2m_get_curr_priv(m2mtest_dev->m2m_dev);
+
+ if (NULL == curr_ctx) {
+ printk(KERN_ERR
+ "Instance released before the end of transaction\n");
+ return;
+ }
+
+ src_vb = v4l2_m2m_src_buf_remove(curr_ctx->m2m_ctx);
+ dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->m2m_ctx);
+
+ curr_ctx->num_processed++;
+
+ spin_lock_irqsave(&m2mtest_dev->irqlock, flags);
+ v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE);
+ v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE);
+ spin_unlock_irqrestore(&m2mtest_dev->irqlock, flags);
+
+ if (curr_ctx->num_processed == curr_ctx->translen
+ || curr_ctx->aborting) {
+ dprintk(curr_ctx->dev, "Finishing transaction\n");
+ curr_ctx->num_processed = 0;
+ v4l2_m2m_job_finish(m2mtest_dev->m2m_dev, curr_ctx->m2m_ctx);
+ } else {
+ device_run(curr_ctx);
+ }
+}
+
+/*
+ * video ioctls
+ */
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ strncpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver) - 1);
+ strncpy(cap->card, MEM2MEM_NAME, sizeof(cap->card) - 1);
+ strlcpy(cap->bus_info, MEM2MEM_NAME, sizeof(cap->bus_info));
++ cap->device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING;
+ cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+ return 0;
+}
+
+static int enum_fmt(struct v4l2_fmtdesc *f, u32 type)
+{
+ int i, num;
+ struct m2mtest_fmt *fmt;
+
+ num = 0;
+
+ for (i = 0; i < NUM_FORMATS; ++i) {
+ if (formats[i].types & type) {
+ /* index-th format of type type found ? */
+ if (num == f->index)
+ break;
+ /* Correct type but haven't reached our index yet,
+ * just increment per-type index */
+ ++num;
+ }
+ }
+
+ if (i < NUM_FORMATS) {
+ /* Format found */
+ fmt = &formats[i];
+ strncpy(f->description, fmt->name, sizeof(f->description) - 1);
+ f->pixelformat = fmt->fourcc;
+ return 0;
+ }
+
+ /* Format not found */
+ return -EINVAL;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ return enum_fmt(f, MEM2MEM_CAPTURE);
+}
+
+static int vidioc_enum_fmt_vid_out(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ return enum_fmt(f, MEM2MEM_OUTPUT);
+}
+
+static int vidioc_g_fmt(struct m2mtest_ctx *ctx, struct v4l2_format *f)
+{
+ struct vb2_queue *vq;
+ struct m2mtest_q_data *q_data;
+
+ vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type);
+ if (!vq)
+ return -EINVAL;
+
+ q_data = get_q_data(ctx, f->type);
+
+ f->fmt.pix.width = q_data->width;
+ f->fmt.pix.height = q_data->height;
+ f->fmt.pix.field = V4L2_FIELD_NONE;
+ f->fmt.pix.pixelformat = q_data->fmt->fourcc;
+ f->fmt.pix.bytesperline = (q_data->width * q_data->fmt->depth) >> 3;
+ f->fmt.pix.sizeimage = q_data->sizeimage;
+ f->fmt.pix.colorspace = ctx->colorspace;
+
+ return 0;
+}
+
+static int vidioc_g_fmt_vid_out(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ return vidioc_g_fmt(file2ctx(file), f);
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ return vidioc_g_fmt(file2ctx(file), f);
+}
+
+static int vidioc_try_fmt(struct v4l2_format *f, struct m2mtest_fmt *fmt)
+{
+ enum v4l2_field field;
+
+ field = f->fmt.pix.field;
+
+ if (field == V4L2_FIELD_ANY)
+ field = V4L2_FIELD_NONE;
+ else if (V4L2_FIELD_NONE != field)
+ return -EINVAL;
+
+ /* V4L2 specification suggests the driver corrects the format struct
+ * if any of the dimensions is unsupported */
+ f->fmt.pix.field = field;
+
+ if (f->fmt.pix.height < MIN_H)
+ f->fmt.pix.height = MIN_H;
+ else if (f->fmt.pix.height > MAX_H)
+ f->fmt.pix.height = MAX_H;
+
+ if (f->fmt.pix.width < MIN_W)
+ f->fmt.pix.width = MIN_W;
+ else if (f->fmt.pix.width > MAX_W)
+ f->fmt.pix.width = MAX_W;
+
+ f->fmt.pix.width &= ~DIM_ALIGN_MASK;
+ 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_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct m2mtest_fmt *fmt;
+ struct m2mtest_ctx *ctx = file2ctx(file);
+
+ fmt = find_format(f);
+ if (!fmt || !(fmt->types & MEM2MEM_CAPTURE)) {
+ v4l2_err(&ctx->dev->v4l2_dev,
+ "Fourcc format (0x%08x) invalid.\n",
+ f->fmt.pix.pixelformat);
+ return -EINVAL;
+ }
+ f->fmt.pix.colorspace = ctx->colorspace;
+
+ return vidioc_try_fmt(f, fmt);
+}
+
+static int vidioc_try_fmt_vid_out(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct m2mtest_fmt *fmt;
+ struct m2mtest_ctx *ctx = file2ctx(file);
+
+ fmt = find_format(f);
+ if (!fmt || !(fmt->types & MEM2MEM_OUTPUT)) {
+ v4l2_err(&ctx->dev->v4l2_dev,
+ "Fourcc format (0x%08x) invalid.\n",
+ f->fmt.pix.pixelformat);
+ return -EINVAL;
+ }
+ if (!f->fmt.pix.colorspace)
+ f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709;
+
+ return vidioc_try_fmt(f, fmt);
+}
+
+static int vidioc_s_fmt(struct m2mtest_ctx *ctx, struct v4l2_format *f)
+{
+ struct m2mtest_q_data *q_data;
+ struct vb2_queue *vq;
+
+ vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type);
+ if (!vq)
+ return -EINVAL;
+
+ q_data = get_q_data(ctx, f->type);
+ if (!q_data)
+ return -EINVAL;
+
+ if (vb2_is_busy(vq)) {
+ v4l2_err(&ctx->dev->v4l2_dev, "%s queue busy\n", __func__);
+ return -EBUSY;
+ }
+
+ q_data->fmt = find_format(f);
+ q_data->width = f->fmt.pix.width;
+ q_data->height = f->fmt.pix.height;
+ q_data->sizeimage = q_data->width * q_data->height
+ * q_data->fmt->depth >> 3;
+
+ dprintk(ctx->dev,
+ "Setting format for type %d, wxh: %dx%d, fmt: %d\n",
+ f->type, q_data->width, q_data->height, q_data->fmt->fourcc);
+
+ return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ int ret;
+
+ ret = vidioc_try_fmt_vid_cap(file, priv, f);
+ if (ret)
+ return ret;
+
+ return vidioc_s_fmt(file2ctx(file), f);
+}
+
+static int vidioc_s_fmt_vid_out(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct m2mtest_ctx *ctx = file2ctx(file);
+ int ret;
+
+ ret = vidioc_try_fmt_vid_out(file, priv, f);
+ if (ret)
+ return ret;
+
+ ret = vidioc_s_fmt(file2ctx(file), f);
+ if (!ret)
+ ctx->colorspace = f->fmt.pix.colorspace;
+ return ret;
+}
+
+static int vidioc_reqbufs(struct file *file, void *priv,
+ struct v4l2_requestbuffers *reqbufs)
+{
+ struct m2mtest_ctx *ctx = file2ctx(file);
+
+ return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs);
+}
+
+static int vidioc_querybuf(struct file *file, void *priv,
+ struct v4l2_buffer *buf)
+{
+ struct m2mtest_ctx *ctx = file2ctx(file);
+
+ return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf);
+}
+
+static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf)
+{
+ struct m2mtest_ctx *ctx = file2ctx(file);
+
+ return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf);
+}
+
+static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf)
+{
+ struct m2mtest_ctx *ctx = file2ctx(file);
+
+ return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf);
+}
+
+static int vidioc_streamon(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct m2mtest_ctx *ctx = file2ctx(file);
+
+ return v4l2_m2m_streamon(file, ctx->m2m_ctx, type);
+}
+
+static int vidioc_streamoff(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct m2mtest_ctx *ctx = file2ctx(file);
+
+ return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type);
+}
+
+static int m2mtest_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct m2mtest_ctx *ctx =
+ container_of(ctrl->handler, struct m2mtest_ctx, hdl);
+
+ switch (ctrl->id) {
+ case V4L2_CID_HFLIP:
+ if (ctrl->val)
+ ctx->mode |= MEM2MEM_HFLIP;
+ else
+ ctx->mode &= ~MEM2MEM_HFLIP;
+ break;
+
+ case V4L2_CID_VFLIP:
+ if (ctrl->val)
+ ctx->mode |= MEM2MEM_VFLIP;
+ else
+ ctx->mode &= ~MEM2MEM_VFLIP;
+ break;
+
+ case V4L2_CID_TRANS_TIME_MSEC:
+ ctx->transtime = ctrl->val;
+ break;
+
+ case V4L2_CID_TRANS_NUM_BUFS:
+ ctx->translen = ctrl->val;
+ break;
+
+ default:
+ v4l2_err(&ctx->dev->v4l2_dev, "Invalid control\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops m2mtest_ctrl_ops = {
+ .s_ctrl = m2mtest_s_ctrl,
+};
+
+
+static const struct v4l2_ioctl_ops m2mtest_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_enum_fmt_vid_out = vidioc_enum_fmt_vid_out,
+ .vidioc_g_fmt_vid_out = vidioc_g_fmt_vid_out,
+ .vidioc_try_fmt_vid_out = vidioc_try_fmt_vid_out,
+ .vidioc_s_fmt_vid_out = vidioc_s_fmt_vid_out,
+
+ .vidioc_reqbufs = vidioc_reqbufs,
+ .vidioc_querybuf = vidioc_querybuf,
+
+ .vidioc_qbuf = vidioc_qbuf,
+ .vidioc_dqbuf = vidioc_dqbuf,
+
+ .vidioc_streamon = vidioc_streamon,
+ .vidioc_streamoff = vidioc_streamoff,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+
+/*
+ * Queue operations
+ */
+
+static int m2mtest_queue_setup(struct vb2_queue *vq,
+ const struct v4l2_format *fmt,
+ unsigned int *nbuffers, unsigned int *nplanes,
+ unsigned int sizes[], void *alloc_ctxs[])
+{
+ struct m2mtest_ctx *ctx = vb2_get_drv_priv(vq);
+ struct m2mtest_q_data *q_data;
+ unsigned int size, count = *nbuffers;
+
+ q_data = get_q_data(ctx, vq->type);
+
+ size = q_data->width * q_data->height * q_data->fmt->depth >> 3;
+
+ while (size * count > MEM2MEM_VID_MEM_LIMIT)
+ (count)--;
+
+ *nplanes = 1;
+ *nbuffers = count;
+ sizes[0] = size;
+
+ /*
+ * videobuf2-vmalloc allocator is context-less so no need to set
+ * alloc_ctxs array.
+ */
+
+ dprintk(ctx->dev, "get %d buffer(s) of size %d each.\n", count, size);
+
+ return 0;
+}
+
+static int m2mtest_buf_prepare(struct vb2_buffer *vb)
+{
+ struct m2mtest_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+ struct m2mtest_q_data *q_data;
+
+ dprintk(ctx->dev, "type: %d\n", vb->vb2_queue->type);
+
+ q_data = get_q_data(ctx, vb->vb2_queue->type);
+
+ if (vb2_plane_size(vb, 0) < q_data->sizeimage) {
+ dprintk(ctx->dev, "%s data will not fit into plane (%lu < %lu)\n",
+ __func__, vb2_plane_size(vb, 0), (long)q_data->sizeimage);
+ return -EINVAL;
+ }
+
+ vb2_set_plane_payload(vb, 0, q_data->sizeimage);
+
+ return 0;
+}
+
+static void m2mtest_buf_queue(struct vb2_buffer *vb)
+{
+ struct m2mtest_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+ v4l2_m2m_buf_queue(ctx->m2m_ctx, vb);
+}
+
+static void m2mtest_wait_prepare(struct vb2_queue *q)
+{
+ struct m2mtest_ctx *ctx = vb2_get_drv_priv(q);
+ m2mtest_unlock(ctx);
+}
+
+static void m2mtest_wait_finish(struct vb2_queue *q)
+{
+ struct m2mtest_ctx *ctx = vb2_get_drv_priv(q);
+ m2mtest_lock(ctx);
+}
+
+static struct vb2_ops m2mtest_qops = {
+ .queue_setup = m2mtest_queue_setup,
+ .buf_prepare = m2mtest_buf_prepare,
+ .buf_queue = m2mtest_buf_queue,
+ .wait_prepare = m2mtest_wait_prepare,
+ .wait_finish = m2mtest_wait_finish,
+};
+
+static int queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq)
+{
+ struct m2mtest_ctx *ctx = priv;
+ int ret;
+
+ memset(src_vq, 0, sizeof(*src_vq));
+ src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+ src_vq->io_modes = VB2_MMAP;
+ src_vq->drv_priv = ctx;
+ src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ src_vq->ops = &m2mtest_qops;
+ src_vq->mem_ops = &vb2_vmalloc_memops;
+
+ ret = vb2_queue_init(src_vq);
+ if (ret)
+ return ret;
+
+ memset(dst_vq, 0, sizeof(*dst_vq));
+ dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ dst_vq->io_modes = VB2_MMAP;
+ dst_vq->drv_priv = ctx;
+ dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ dst_vq->ops = &m2mtest_qops;
+ dst_vq->mem_ops = &vb2_vmalloc_memops;
+
+ return vb2_queue_init(dst_vq);
+}
+
+static const struct v4l2_ctrl_config m2mtest_ctrl_trans_time_msec = {
+ .ops = &m2mtest_ctrl_ops,
+ .id = V4L2_CID_TRANS_TIME_MSEC,
+ .name = "Transaction Time (msec)",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .def = 1001,
+ .min = 1,
+ .max = 10001,
+ .step = 100,
+};
+
+static const struct v4l2_ctrl_config m2mtest_ctrl_trans_num_bufs = {
+ .ops = &m2mtest_ctrl_ops,
+ .id = V4L2_CID_TRANS_NUM_BUFS,
+ .name = "Buffers Per Transaction",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .def = 1,
+ .min = 1,
+ .max = MEM2MEM_DEF_NUM_BUFS,
+ .step = 1,
+};
+
+/*
+ * File operations
+ */
+static int m2mtest_open(struct file *file)
+{
+ struct m2mtest_dev *dev = video_drvdata(file);
+ struct m2mtest_ctx *ctx = NULL;
+ struct v4l2_ctrl_handler *hdl;
+ int rc = 0;
+
+ if (mutex_lock_interruptible(&dev->dev_mutex))
+ return -ERESTARTSYS;
+ ctx = kzalloc(sizeof *ctx, GFP_KERNEL);
+ if (!ctx) {
+ rc = -ENOMEM;
+ goto open_unlock;
+ }
+
+ v4l2_fh_init(&ctx->fh, video_devdata(file));
+ file->private_data = &ctx->fh;
+ ctx->dev = dev;
+ hdl = &ctx->hdl;
+ v4l2_ctrl_handler_init(hdl, 4);
+ v4l2_ctrl_new_std(hdl, &m2mtest_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0);
+ v4l2_ctrl_new_std(hdl, &m2mtest_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0);
+ v4l2_ctrl_new_custom(hdl, &m2mtest_ctrl_trans_time_msec, NULL);
+ v4l2_ctrl_new_custom(hdl, &m2mtest_ctrl_trans_num_bufs, NULL);
+ if (hdl->error) {
+ rc = hdl->error;
+ v4l2_ctrl_handler_free(hdl);
+ goto open_unlock;
+ }
+ ctx->fh.ctrl_handler = hdl;
+ v4l2_ctrl_handler_setup(hdl);
+
+ ctx->q_data[V4L2_M2M_SRC].fmt = &formats[0];
+ ctx->q_data[V4L2_M2M_SRC].width = 640;
+ ctx->q_data[V4L2_M2M_SRC].height = 480;
+ ctx->q_data[V4L2_M2M_SRC].sizeimage =
+ ctx->q_data[V4L2_M2M_SRC].width *
+ ctx->q_data[V4L2_M2M_SRC].height *
+ (ctx->q_data[V4L2_M2M_SRC].fmt->depth >> 3);
+ ctx->q_data[V4L2_M2M_DST] = ctx->q_data[V4L2_M2M_SRC];
+ ctx->colorspace = V4L2_COLORSPACE_REC709;
+
+ ctx->m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init);
+
+ if (IS_ERR(ctx->m2m_ctx)) {
+ rc = PTR_ERR(ctx->m2m_ctx);
+
+ v4l2_ctrl_handler_free(hdl);
+ kfree(ctx);
+ goto open_unlock;
+ }
+
+ v4l2_fh_add(&ctx->fh);
+ atomic_inc(&dev->num_inst);
+
+ dprintk(dev, "Created instance %p, m2m_ctx: %p\n", ctx, ctx->m2m_ctx);
+
+open_unlock:
+ mutex_unlock(&dev->dev_mutex);
+ return rc;
+}
+
+static int m2mtest_release(struct file *file)
+{
+ struct m2mtest_dev *dev = video_drvdata(file);
+ struct m2mtest_ctx *ctx = file2ctx(file);
+
+ dprintk(dev, "Releasing instance %p\n", ctx);
+
+ v4l2_fh_del(&ctx->fh);
+ v4l2_fh_exit(&ctx->fh);
+ v4l2_ctrl_handler_free(&ctx->hdl);
+ mutex_lock(&dev->dev_mutex);
+ v4l2_m2m_ctx_release(ctx->m2m_ctx);
+ mutex_unlock(&dev->dev_mutex);
+ kfree(ctx);
+
+ atomic_dec(&dev->num_inst);
+
+ return 0;
+}
+
+static unsigned int m2mtest_poll(struct file *file,
+ struct poll_table_struct *wait)
+{
+ struct m2mtest_ctx *ctx = file2ctx(file);
+
+ return v4l2_m2m_poll(file, ctx->m2m_ctx, wait);
+}
+
+static int m2mtest_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct m2mtest_dev *dev = video_drvdata(file);
+ struct m2mtest_ctx *ctx = file2ctx(file);
+ int res;
+
+ if (mutex_lock_interruptible(&dev->dev_mutex))
+ return -ERESTARTSYS;
+ res = v4l2_m2m_mmap(file, ctx->m2m_ctx, vma);
+ mutex_unlock(&dev->dev_mutex);
+ return res;
+}
+
+static const struct v4l2_file_operations m2mtest_fops = {
+ .owner = THIS_MODULE,
+ .open = m2mtest_open,
+ .release = m2mtest_release,
+ .poll = m2mtest_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = m2mtest_mmap,
+};
+
+static struct video_device m2mtest_videodev = {
+ .name = MEM2MEM_NAME,
+ .fops = &m2mtest_fops,
+ .ioctl_ops = &m2mtest_ioctl_ops,
+ .minor = -1,
+ .release = video_device_release,
+};
+
+static struct v4l2_m2m_ops m2m_ops = {
+ .device_run = device_run,
+ .job_ready = job_ready,
+ .job_abort = job_abort,
+ .lock = m2mtest_lock,
+ .unlock = m2mtest_unlock,
+};
+
+static int m2mtest_probe(struct platform_device *pdev)
+{
+ struct m2mtest_dev *dev;
+ struct video_device *vfd;
+ int ret;
+
+ dev = kzalloc(sizeof *dev, GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ spin_lock_init(&dev->irqlock);
+
+ ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
+ if (ret)
+ goto free_dev;
+
+ atomic_set(&dev->num_inst, 0);
+ mutex_init(&dev->dev_mutex);
+
+ vfd = video_device_alloc();
+ if (!vfd) {
+ v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n");
+ ret = -ENOMEM;
+ goto unreg_dev;
+ }
+
+ *vfd = m2mtest_videodev;
+ vfd->lock = &dev->dev_mutex;
+
+ ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0);
+ if (ret) {
+ v4l2_err(&dev->v4l2_dev, "Failed to register video device\n");
+ goto rel_vdev;
+ }
+
+ video_set_drvdata(vfd, dev);
+ snprintf(vfd->name, sizeof(vfd->name), "%s", m2mtest_videodev.name);
+ dev->vfd = vfd;
+ v4l2_info(&dev->v4l2_dev, MEM2MEM_TEST_MODULE_NAME
+ "Device registered as /dev/video%d\n", vfd->num);
+
+ setup_timer(&dev->timer, device_isr, (long)dev);
+ platform_set_drvdata(pdev, dev);
+
+ dev->m2m_dev = v4l2_m2m_init(&m2m_ops);
+ if (IS_ERR(dev->m2m_dev)) {
+ v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem device\n");
+ ret = PTR_ERR(dev->m2m_dev);
+ goto err_m2m;
+ }
+
+ return 0;
+
+ v4l2_m2m_release(dev->m2m_dev);
+err_m2m:
+ video_unregister_device(dev->vfd);
+rel_vdev:
+ video_device_release(vfd);
+unreg_dev:
+ v4l2_device_unregister(&dev->v4l2_dev);
+free_dev:
+ kfree(dev);
+
+ return ret;
+}
+
+static int m2mtest_remove(struct platform_device *pdev)
+{
+ struct m2mtest_dev *dev =
+ (struct m2mtest_dev *)platform_get_drvdata(pdev);
+
+ v4l2_info(&dev->v4l2_dev, "Removing " MEM2MEM_TEST_MODULE_NAME);
+ v4l2_m2m_release(dev->m2m_dev);
+ del_timer_sync(&dev->timer);
+ video_unregister_device(dev->vfd);
+ v4l2_device_unregister(&dev->v4l2_dev);
+ kfree(dev);
+
+ return 0;
+}
+
+static struct platform_driver m2mtest_pdrv = {
+ .probe = m2mtest_probe,
+ .remove = m2mtest_remove,
+ .driver = {
+ .name = MEM2MEM_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static void __exit m2mtest_exit(void)
+{
+ platform_driver_unregister(&m2mtest_pdrv);
+ platform_device_unregister(&m2mtest_pdev);
+}
+
+static int __init m2mtest_init(void)
+{
+ int ret;
+
+ ret = platform_device_register(&m2mtest_pdev);
+ if (ret)
+ return ret;
+
+ ret = platform_driver_register(&m2mtest_pdrv);
+ if (ret)
+ platform_device_unregister(&m2mtest_pdev);
+
+ return 0;
+}
+
+module_init(m2mtest_init);
+module_exit(m2mtest_exit);
+
--- /dev/null
- static const __devinitdata struct usb_device_id device_table[] = {
+/*
+ * Jeilin JL2005B/C/D library
+ *
+ * Copyright (C) 2011 Theodore Kilgore <kilgota@auburn.edu>
+ *
+ * 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
+ * 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
+ */
+
+#define MODULE_NAME "jl2005bcd"
+
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include "gspca.h"
+
+
+MODULE_AUTHOR("Theodore Kilgore <kilgota@auburn.edu>");
+MODULE_DESCRIPTION("JL2005B/C/D USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* Default timeouts, in ms */
+#define JL2005C_CMD_TIMEOUT 500
+#define JL2005C_DATA_TIMEOUT 1000
+
+/* Maximum transfer size to use. */
+#define JL2005C_MAX_TRANSFER 0x200
+#define FRAME_HEADER_LEN 16
+
+
+/* specific webcam descriptor */
+struct sd {
+ struct gspca_dev gspca_dev; /* !! must be the first item */
+ unsigned char firmware_id[6];
+ const struct v4l2_pix_format *cap_mode;
+ /* Driver stuff */
+ struct work_struct work_struct;
+ struct workqueue_struct *work_thread;
+ u8 frame_brightness;
+ int block_size; /* block size of camera */
+ int vga; /* 1 if vga cam, 0 if cif cam */
+};
+
+
+/* Camera has two resolution settings. What they are depends on model. */
+static const struct v4l2_pix_format cif_mode[] = {
+ {176, 144, V4L2_PIX_FMT_JL2005BCD, V4L2_FIELD_NONE,
+ .bytesperline = 176,
+ .sizeimage = 176 * 144,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .priv = 0},
+ {352, 288, V4L2_PIX_FMT_JL2005BCD, V4L2_FIELD_NONE,
+ .bytesperline = 352,
+ .sizeimage = 352 * 288,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .priv = 0},
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+ {320, 240, V4L2_PIX_FMT_JL2005BCD, V4L2_FIELD_NONE,
+ .bytesperline = 320,
+ .sizeimage = 320 * 240,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .priv = 0},
+ {640, 480, V4L2_PIX_FMT_JL2005BCD, V4L2_FIELD_NONE,
+ .bytesperline = 640,
+ .sizeimage = 640 * 480,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .priv = 0},
+};
+
+/*
+ * cam uses endpoint 0x03 to send commands, 0x84 for read commands,
+ * and 0x82 for bulk data transfer.
+ */
+
+/* All commands are two bytes only */
+static int jl2005c_write2(struct gspca_dev *gspca_dev, unsigned char *command)
+{
+ int retval;
+
+ memcpy(gspca_dev->usb_buf, command, 2);
+ retval = usb_bulk_msg(gspca_dev->dev,
+ usb_sndbulkpipe(gspca_dev->dev, 3),
+ gspca_dev->usb_buf, 2, NULL, 500);
+ if (retval < 0)
+ pr_err("command write [%02x] error %d\n",
+ gspca_dev->usb_buf[0], retval);
+ return retval;
+}
+
+/* Response to a command is one byte in usb_buf[0], only if requested. */
+static int jl2005c_read1(struct gspca_dev *gspca_dev)
+{
+ int retval;
+
+ retval = usb_bulk_msg(gspca_dev->dev,
+ usb_rcvbulkpipe(gspca_dev->dev, 0x84),
+ gspca_dev->usb_buf, 1, NULL, 500);
+ if (retval < 0)
+ pr_err("read command [0x%02x] error %d\n",
+ gspca_dev->usb_buf[0], retval);
+ return retval;
+}
+
+/* Response appears in gspca_dev->usb_buf[0] */
+static int jl2005c_read_reg(struct gspca_dev *gspca_dev, unsigned char reg)
+{
+ int retval;
+
+ static u8 instruction[2] = {0x95, 0x00};
+ /* put register to read in byte 1 */
+ instruction[1] = reg;
+ /* Send the read request */
+ retval = jl2005c_write2(gspca_dev, instruction);
+ if (retval < 0)
+ return retval;
+ retval = jl2005c_read1(gspca_dev);
+
+ return retval;
+}
+
+static int jl2005c_start_new_frame(struct gspca_dev *gspca_dev)
+{
+ int i;
+ int retval;
+ int frame_brightness = 0;
+
+ static u8 instruction[2] = {0x7f, 0x01};
+
+ retval = jl2005c_write2(gspca_dev, instruction);
+ if (retval < 0)
+ return retval;
+
+ i = 0;
+ while (i < 20 && !frame_brightness) {
+ /* If we tried 20 times, give up. */
+ retval = jl2005c_read_reg(gspca_dev, 0x7e);
+ if (retval < 0)
+ return retval;
+ frame_brightness = gspca_dev->usb_buf[0];
+ retval = jl2005c_read_reg(gspca_dev, 0x7d);
+ if (retval < 0)
+ return retval;
+ i++;
+ }
+ PDEBUG(D_FRAM, "frame_brightness is 0x%02x", gspca_dev->usb_buf[0]);
+ return retval;
+}
+
+static int jl2005c_write_reg(struct gspca_dev *gspca_dev, unsigned char reg,
+ unsigned char value)
+{
+ int retval;
+ u8 instruction[2];
+
+ instruction[0] = reg;
+ instruction[1] = value;
+
+ retval = jl2005c_write2(gspca_dev, instruction);
+ if (retval < 0)
+ return retval;
+
+ return retval;
+}
+
+static int jl2005c_get_firmware_id(struct gspca_dev *gspca_dev)
+{
+ struct sd *sd = (struct sd *)gspca_dev;
+ int i = 0;
+ int retval = -1;
+ unsigned char regs_to_read[] = {0x57, 0x02, 0x03, 0x5d, 0x5e, 0x5f};
+
+ PDEBUG(D_PROBE, "Running jl2005c_get_firmware_id");
+ /* Read the first ID byte once for warmup */
+ retval = jl2005c_read_reg(gspca_dev, regs_to_read[0]);
+ PDEBUG(D_PROBE, "response is %02x", gspca_dev->usb_buf[0]);
+ if (retval < 0)
+ return retval;
+ /* Now actually get the ID string */
+ for (i = 0; i < 6; i++) {
+ retval = jl2005c_read_reg(gspca_dev, regs_to_read[i]);
+ if (retval < 0)
+ return retval;
+ sd->firmware_id[i] = gspca_dev->usb_buf[0];
+ }
+ PDEBUG(D_PROBE, "firmware ID is %02x%02x%02x%02x%02x%02x",
+ sd->firmware_id[0],
+ sd->firmware_id[1],
+ sd->firmware_id[2],
+ sd->firmware_id[3],
+ sd->firmware_id[4],
+ sd->firmware_id[5]);
+ return 0;
+}
+
+static int jl2005c_stream_start_vga_lg
+ (struct gspca_dev *gspca_dev)
+{
+ int i;
+ int retval = -1;
+ static u8 instruction[][2] = {
+ {0x05, 0x00},
+ {0x7c, 0x00},
+ {0x7d, 0x18},
+ {0x02, 0x00},
+ {0x01, 0x00},
+ {0x04, 0x52},
+ };
+
+ for (i = 0; i < ARRAY_SIZE(instruction); i++) {
+ msleep(60);
+ retval = jl2005c_write2(gspca_dev, instruction[i]);
+ if (retval < 0)
+ return retval;
+ }
+ msleep(60);
+ return retval;
+}
+
+static int jl2005c_stream_start_vga_small(struct gspca_dev *gspca_dev)
+{
+ int i;
+ int retval = -1;
+ static u8 instruction[][2] = {
+ {0x06, 0x00},
+ {0x7c, 0x00},
+ {0x7d, 0x1a},
+ {0x02, 0x00},
+ {0x01, 0x00},
+ {0x04, 0x52},
+ };
+
+ for (i = 0; i < ARRAY_SIZE(instruction); i++) {
+ msleep(60);
+ retval = jl2005c_write2(gspca_dev, instruction[i]);
+ if (retval < 0)
+ return retval;
+ }
+ msleep(60);
+ return retval;
+}
+
+static int jl2005c_stream_start_cif_lg(struct gspca_dev *gspca_dev)
+{
+ int i;
+ int retval = -1;
+ static u8 instruction[][2] = {
+ {0x05, 0x00},
+ {0x7c, 0x00},
+ {0x7d, 0x30},
+ {0x02, 0x00},
+ {0x01, 0x00},
+ {0x04, 0x42},
+ };
+
+ for (i = 0; i < ARRAY_SIZE(instruction); i++) {
+ msleep(60);
+ retval = jl2005c_write2(gspca_dev, instruction[i]);
+ if (retval < 0)
+ return retval;
+ }
+ msleep(60);
+ return retval;
+}
+
+static int jl2005c_stream_start_cif_small(struct gspca_dev *gspca_dev)
+{
+ int i;
+ int retval = -1;
+ static u8 instruction[][2] = {
+ {0x06, 0x00},
+ {0x7c, 0x00},
+ {0x7d, 0x32},
+ {0x02, 0x00},
+ {0x01, 0x00},
+ {0x04, 0x42},
+ };
+
+ for (i = 0; i < ARRAY_SIZE(instruction); i++) {
+ msleep(60);
+ retval = jl2005c_write2(gspca_dev, instruction[i]);
+ if (retval < 0)
+ return retval;
+ }
+ msleep(60);
+ return retval;
+}
+
+
+static int jl2005c_stop(struct gspca_dev *gspca_dev)
+{
+ int retval;
+
+ retval = jl2005c_write_reg(gspca_dev, 0x07, 0x00);
+ return retval;
+}
+
+/* This function is called as a workqueue function and runs whenever the camera
+ * is streaming data. Because it is a workqueue function it is allowed to sleep
+ * so we can use synchronous USB calls. To avoid possible collisions with other
+ * threads attempting to use the camera's USB interface the gspca usb_lock is
+ * used when performing the one USB control operation inside the workqueue,
+ * which tells the camera to close the stream. In practice the only thing
+ * which needs to be protected against is the usb_set_interface call that
+ * gspca makes during stream_off. Otherwise the camera doesn't provide any
+ * controls that the user could try to change.
+ */
+static void jl2005c_dostream(struct work_struct *work)
+{
+ struct sd *dev = container_of(work, struct sd, work_struct);
+ struct gspca_dev *gspca_dev = &dev->gspca_dev;
+ int bytes_left = 0; /* bytes remaining in current frame. */
+ int data_len; /* size to use for the next read. */
+ int header_read = 0;
+ unsigned char header_sig[2] = {0x4a, 0x4c};
+ int act_len;
+ int packet_type;
+ int ret;
+ u8 *buffer;
+
+ buffer = kmalloc(JL2005C_MAX_TRANSFER, GFP_KERNEL | GFP_DMA);
+ if (!buffer) {
+ pr_err("Couldn't allocate USB buffer\n");
+ goto quit_stream;
+ }
+
+ while (gspca_dev->dev && gspca_dev->streaming) {
+#ifdef CONFIG_PM
+ if (gspca_dev->frozen)
+ break;
+#endif
+ /* Check if this is a new frame. If so, start the frame first */
+ if (!header_read) {
+ mutex_lock(&gspca_dev->usb_lock);
+ ret = jl2005c_start_new_frame(gspca_dev);
+ mutex_unlock(&gspca_dev->usb_lock);
+ if (ret < 0)
+ goto quit_stream;
+ ret = usb_bulk_msg(gspca_dev->dev,
+ usb_rcvbulkpipe(gspca_dev->dev, 0x82),
+ buffer, JL2005C_MAX_TRANSFER, &act_len,
+ JL2005C_DATA_TIMEOUT);
+ PDEBUG(D_PACK,
+ "Got %d bytes out of %d for header",
+ act_len, JL2005C_MAX_TRANSFER);
+ if (ret < 0 || act_len < JL2005C_MAX_TRANSFER)
+ goto quit_stream;
+ /* Check whether we actually got the first blodk */
+ if (memcmp(header_sig, buffer, 2) != 0) {
+ pr_err("First block is not the first block\n");
+ goto quit_stream;
+ }
+ /* total size to fetch is byte 7, times blocksize
+ * of which we already got act_len */
+ bytes_left = buffer[0x07] * dev->block_size - act_len;
+ PDEBUG(D_PACK, "bytes_left = 0x%x", bytes_left);
+ /* We keep the header. It has other information, too.*/
+ packet_type = FIRST_PACKET;
+ gspca_frame_add(gspca_dev, packet_type,
+ buffer, act_len);
+ header_read = 1;
+ }
+ while (bytes_left > 0 && gspca_dev->dev) {
+ data_len = bytes_left > JL2005C_MAX_TRANSFER ?
+ JL2005C_MAX_TRANSFER : bytes_left;
+ ret = usb_bulk_msg(gspca_dev->dev,
+ usb_rcvbulkpipe(gspca_dev->dev, 0x82),
+ buffer, data_len, &act_len,
+ JL2005C_DATA_TIMEOUT);
+ if (ret < 0 || act_len < data_len)
+ goto quit_stream;
+ PDEBUG(D_PACK,
+ "Got %d bytes out of %d for frame",
+ data_len, bytes_left);
+ bytes_left -= data_len;
+ if (bytes_left == 0) {
+ packet_type = LAST_PACKET;
+ header_read = 0;
+ } else
+ packet_type = INTER_PACKET;
+ gspca_frame_add(gspca_dev, packet_type,
+ buffer, data_len);
+ }
+ }
+quit_stream:
+ if (gspca_dev->dev) {
+ mutex_lock(&gspca_dev->usb_lock);
+ jl2005c_stop(gspca_dev);
+ mutex_unlock(&gspca_dev->usb_lock);
+ }
+ kfree(buffer);
+}
+
+
+
+
+/* This function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+ const struct usb_device_id *id)
+{
+ struct cam *cam;
+ struct sd *sd = (struct sd *) gspca_dev;
+
+ cam = &gspca_dev->cam;
+ /* We don't use the buffer gspca allocates so make it small. */
+ cam->bulk_size = 64;
+ cam->bulk = 1;
+ /* For the rest, the camera needs to be detected */
+ jl2005c_get_firmware_id(gspca_dev);
+ /* Here are some known firmware IDs
+ * First some JL2005B cameras
+ * {0x41, 0x07, 0x04, 0x2c, 0xe8, 0xf2} Sakar KidzCam
+ * {0x45, 0x02, 0x08, 0xb9, 0x00, 0xd2} No-name JL2005B
+ * JL2005C cameras
+ * {0x01, 0x0c, 0x16, 0x10, 0xf8, 0xc8} Argus DC-1512
+ * {0x12, 0x04, 0x03, 0xc0, 0x00, 0xd8} ICarly
+ * {0x86, 0x08, 0x05, 0x02, 0x00, 0xd4} Jazz
+ *
+ * Based upon this scanty evidence, we can detect a CIF camera by
+ * testing byte 0 for 0x4x.
+ */
+ if ((sd->firmware_id[0] & 0xf0) == 0x40) {
+ cam->cam_mode = cif_mode;
+ cam->nmodes = ARRAY_SIZE(cif_mode);
+ sd->block_size = 0x80;
+ } else {
+ cam->cam_mode = vga_mode;
+ cam->nmodes = ARRAY_SIZE(vga_mode);
+ sd->block_size = 0x200;
+ }
+
+ INIT_WORK(&sd->work_struct, jl2005c_dostream);
+
+ return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+ return 0;
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+
+ struct sd *sd = (struct sd *) gspca_dev;
+ sd->cap_mode = gspca_dev->cam.cam_mode;
+
+ switch (gspca_dev->width) {
+ case 640:
+ PDEBUG(D_STREAM, "Start streaming at vga resolution");
+ jl2005c_stream_start_vga_lg(gspca_dev);
+ break;
+ case 320:
+ PDEBUG(D_STREAM, "Start streaming at qvga resolution");
+ jl2005c_stream_start_vga_small(gspca_dev);
+ break;
+ case 352:
+ PDEBUG(D_STREAM, "Start streaming at cif resolution");
+ jl2005c_stream_start_cif_lg(gspca_dev);
+ break;
+ case 176:
+ PDEBUG(D_STREAM, "Start streaming at qcif resolution");
+ jl2005c_stream_start_cif_small(gspca_dev);
+ break;
+ default:
+ pr_err("Unknown resolution specified\n");
+ return -1;
+ }
+
+ /* Start the workqueue function to do the streaming */
+ sd->work_thread = create_singlethread_workqueue(MODULE_NAME);
+ queue_work(sd->work_thread, &sd->work_struct);
+
+ return 0;
+}
+
+/* called on streamoff with alt==0 and on disconnect */
+/* the usb_lock is held at entry - restore on exit */
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+ struct sd *dev = (struct sd *) gspca_dev;
+
+ /* wait for the work queue to terminate */
+ mutex_unlock(&gspca_dev->usb_lock);
+ /* This waits for sq905c_dostream to finish */
+ destroy_workqueue(dev->work_thread);
+ dev->work_thread = NULL;
+ mutex_lock(&gspca_dev->usb_lock);
+}
+
+
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+ .name = MODULE_NAME,
+ .config = sd_config,
+ .init = sd_init,
+ .start = sd_start,
+ .stop0 = sd_stop0,
+};
+
+/* -- module initialisation -- */
++static const struct usb_device_id device_table[] = {
+ {USB_DEVICE(0x0979, 0x0227)},
+ {}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+ THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+ .name = MODULE_NAME,
+ .id_table = device_table,
+ .probe = sd_probe,
+ .disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+ .suspend = gspca_suspend,
+ .resume = gspca_resume,
+ .reset_resume = gspca_resume,
+#endif
+};
+
+/* -- module insert / remove -- */
+static int __init sd_mod_init(void)
+{
+ int ret;
+
+ ret = usb_register(&sd_driver);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+static void __exit sd_mod_exit(void)
+{
+ usb_deregister(&sd_driver);
+}
+
+module_init(sd_mod_init);
+module_exit(sd_mod_exit);
--- /dev/null
- static const struct usb_device_id device_table[] __devinitconst = {
+/*
+ * SPCA506 chip based cameras function
+ * M Xhaard 15/04/2004 based on different work Mark Taylor and others
+ * and my own snoopy file on a pv-321c donate by a german compagny
+ * "Firma Frank Gmbh" from Saarbruecken
+ *
+ * V4L2 by Jean-Francois Moine <http://moinejf.free.fr>
+ *
+ * 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
+ * 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
+ */
+
+#define MODULE_NAME "spca506"
+
+#include "gspca.h"
+
+MODULE_AUTHOR("Michel Xhaard <mxhaard@users.sourceforge.net>");
+MODULE_DESCRIPTION("GSPCA/SPCA506 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* specific webcam descriptor */
+struct sd {
+ struct gspca_dev gspca_dev; /* !! must be the first item */
+
+ char norme;
+ char channel;
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+ {160, 120, V4L2_PIX_FMT_SPCA505, V4L2_FIELD_NONE,
+ .bytesperline = 160,
+ .sizeimage = 160 * 120 * 3 / 2,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .priv = 5},
+ {176, 144, V4L2_PIX_FMT_SPCA505, V4L2_FIELD_NONE,
+ .bytesperline = 176,
+ .sizeimage = 176 * 144 * 3 / 2,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .priv = 4},
+ {320, 240, V4L2_PIX_FMT_SPCA505, V4L2_FIELD_NONE,
+ .bytesperline = 320,
+ .sizeimage = 320 * 240 * 3 / 2,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .priv = 2},
+ {352, 288, V4L2_PIX_FMT_SPCA505, V4L2_FIELD_NONE,
+ .bytesperline = 352,
+ .sizeimage = 352 * 288 * 3 / 2,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .priv = 1},
+ {640, 480, V4L2_PIX_FMT_SPCA505, V4L2_FIELD_NONE,
+ .bytesperline = 640,
+ .sizeimage = 640 * 480 * 3 / 2,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .priv = 0},
+};
+
+#define SPCA50X_OFFSET_DATA 10
+
+#define SAA7113_bright 0x0a /* defaults 0x80 */
+#define SAA7113_contrast 0x0b /* defaults 0x47 */
+#define SAA7113_saturation 0x0c /* defaults 0x40 */
+#define SAA7113_hue 0x0d /* defaults 0x00 */
+#define SAA7113_I2C_BASE_WRITE 0x4a
+
+/* read 'len' bytes to gspca_dev->usb_buf */
+static void reg_r(struct gspca_dev *gspca_dev,
+ __u16 req,
+ __u16 index,
+ __u16 length)
+{
+ usb_control_msg(gspca_dev->dev,
+ usb_rcvctrlpipe(gspca_dev->dev, 0),
+ req,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0, /* value */
+ index, gspca_dev->usb_buf, length,
+ 500);
+}
+
+static void reg_w(struct usb_device *dev,
+ __u16 req,
+ __u16 value,
+ __u16 index)
+{
+ usb_control_msg(dev,
+ usb_sndctrlpipe(dev, 0),
+ req,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ value, index,
+ NULL, 0, 500);
+}
+
+static void spca506_Initi2c(struct gspca_dev *gspca_dev)
+{
+ reg_w(gspca_dev->dev, 0x07, SAA7113_I2C_BASE_WRITE, 0x0004);
+}
+
+static void spca506_WriteI2c(struct gspca_dev *gspca_dev, __u16 valeur,
+ __u16 reg)
+{
+ int retry = 60;
+
+ reg_w(gspca_dev->dev, 0x07, reg, 0x0001);
+ reg_w(gspca_dev->dev, 0x07, valeur, 0x0000);
+ while (retry--) {
+ reg_r(gspca_dev, 0x07, 0x0003, 2);
+ if ((gspca_dev->usb_buf[0] | gspca_dev->usb_buf[1]) == 0x00)
+ break;
+ }
+}
+
+static void spca506_SetNormeInput(struct gspca_dev *gspca_dev,
+ __u16 norme,
+ __u16 channel)
+{
+ struct sd *sd = (struct sd *) gspca_dev;
+/* fixme: check if channel == 0..3 and 6..9 (8 values) */
+ __u8 setbit0 = 0x00;
+ __u8 setbit1 = 0x00;
+ __u8 videomask = 0x00;
+
+ PDEBUG(D_STREAM, "** Open Set Norme **");
+ spca506_Initi2c(gspca_dev);
+ /* NTSC bit0 -> 1(525 l) PAL SECAM bit0 -> 0 (625 l) */
+ /* Composite channel bit1 -> 1 S-video bit 1 -> 0 */
+ /* and exclude SAA7113 reserved channel set default 0 otherwise */
+ if (norme & V4L2_STD_NTSC)
+ setbit0 = 0x01;
+ if (channel == 4 || channel == 5 || channel > 9)
+ channel = 0;
+ if (channel < 4)
+ setbit1 = 0x02;
+ videomask = (0x48 | setbit0 | setbit1);
+ reg_w(gspca_dev->dev, 0x08, videomask, 0x0000);
+ spca506_WriteI2c(gspca_dev, (0xc0 | (channel & 0x0F)), 0x02);
+
+ if (norme & V4L2_STD_NTSC)
+ spca506_WriteI2c(gspca_dev, 0x33, 0x0e);
+ /* Chrominance Control NTSC N */
+ else if (norme & V4L2_STD_SECAM)
+ spca506_WriteI2c(gspca_dev, 0x53, 0x0e);
+ /* Chrominance Control SECAM */
+ else
+ spca506_WriteI2c(gspca_dev, 0x03, 0x0e);
+ /* Chrominance Control PAL BGHIV */
+
+ sd->norme = norme;
+ sd->channel = channel;
+ PDEBUG(D_STREAM, "Set Video Byte to 0x%2x", videomask);
+ PDEBUG(D_STREAM, "Set Norme: %08x Channel %d", norme, channel);
+}
+
+static void spca506_GetNormeInput(struct gspca_dev *gspca_dev,
+ __u16 *norme, __u16 *channel)
+{
+ struct sd *sd = (struct sd *) gspca_dev;
+
+ /* Read the register is not so good value change so
+ we use your own copy in spca50x struct */
+ *norme = sd->norme;
+ *channel = sd->channel;
+ PDEBUG(D_STREAM, "Get Norme: %d Channel %d", *norme, *channel);
+}
+
+static void spca506_Setsize(struct gspca_dev *gspca_dev, __u16 code,
+ __u16 xmult, __u16 ymult)
+{
+ struct usb_device *dev = gspca_dev->dev;
+
+ PDEBUG(D_STREAM, "** SetSize **");
+ reg_w(dev, 0x04, (0x18 | (code & 0x07)), 0x0000);
+ /* Soft snap 0x40 Hard 0x41 */
+ reg_w(dev, 0x04, 0x41, 0x0001);
+ reg_w(dev, 0x04, 0x00, 0x0002);
+ /* reserved */
+ reg_w(dev, 0x04, 0x00, 0x0003);
+
+ /* reserved */
+ reg_w(dev, 0x04, 0x00, 0x0004);
+ /* reserved */
+ reg_w(dev, 0x04, 0x01, 0x0005);
+ /* reserced */
+ reg_w(dev, 0x04, xmult, 0x0006);
+ /* reserved */
+ reg_w(dev, 0x04, ymult, 0x0007);
+ /* compression 1 */
+ reg_w(dev, 0x04, 0x00, 0x0008);
+ /* T=64 -> 2 */
+ reg_w(dev, 0x04, 0x00, 0x0009);
+ /* threshold2D */
+ reg_w(dev, 0x04, 0x21, 0x000a);
+ /* quantization */
+ reg_w(dev, 0x04, 0x00, 0x000b);
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+ const struct usb_device_id *id)
+{
+ struct cam *cam;
+
+ cam = &gspca_dev->cam;
+ cam->cam_mode = vga_mode;
+ cam->nmodes = ARRAY_SIZE(vga_mode);
+ return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+ struct usb_device *dev = gspca_dev->dev;
+
+ reg_w(dev, 0x03, 0x00, 0x0004);
+ reg_w(dev, 0x03, 0xFF, 0x0003);
+ reg_w(dev, 0x03, 0x00, 0x0000);
+ reg_w(dev, 0x03, 0x1c, 0x0001);
+ reg_w(dev, 0x03, 0x18, 0x0001);
+ /* Init on PAL and composite input0 */
+ spca506_SetNormeInput(gspca_dev, 0, 0);
+ reg_w(dev, 0x03, 0x1c, 0x0001);
+ reg_w(dev, 0x03, 0x18, 0x0001);
+ reg_w(dev, 0x05, 0x00, 0x0000);
+ reg_w(dev, 0x05, 0xef, 0x0001);
+ reg_w(dev, 0x05, 0x00, 0x00c1);
+ reg_w(dev, 0x05, 0x00, 0x00c2);
+ reg_w(dev, 0x06, 0x18, 0x0002);
+ reg_w(dev, 0x06, 0xf5, 0x0011);
+ reg_w(dev, 0x06, 0x02, 0x0012);
+ reg_w(dev, 0x06, 0xfb, 0x0013);
+ reg_w(dev, 0x06, 0x00, 0x0014);
+ reg_w(dev, 0x06, 0xa4, 0x0051);
+ reg_w(dev, 0x06, 0x40, 0x0052);
+ reg_w(dev, 0x06, 0x71, 0x0053);
+ reg_w(dev, 0x06, 0x40, 0x0054);
+ /************************************************/
+ reg_w(dev, 0x03, 0x00, 0x0004);
+ reg_w(dev, 0x03, 0x00, 0x0003);
+ reg_w(dev, 0x03, 0x00, 0x0004);
+ reg_w(dev, 0x03, 0xFF, 0x0003);
+ reg_w(dev, 0x02, 0x00, 0x0000);
+ reg_w(dev, 0x03, 0x60, 0x0000);
+ reg_w(dev, 0x03, 0x18, 0x0001);
+ /* for a better reading mx :) */
+ /*sdca506_WriteI2c(value,register) */
+ spca506_Initi2c(gspca_dev);
+ spca506_WriteI2c(gspca_dev, 0x08, 0x01);
+ spca506_WriteI2c(gspca_dev, 0xc0, 0x02);
+ /* input composite video */
+ spca506_WriteI2c(gspca_dev, 0x33, 0x03);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x04);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x05);
+ spca506_WriteI2c(gspca_dev, 0x0d, 0x06);
+ spca506_WriteI2c(gspca_dev, 0xf0, 0x07);
+ spca506_WriteI2c(gspca_dev, 0x98, 0x08);
+ spca506_WriteI2c(gspca_dev, 0x03, 0x09);
+ spca506_WriteI2c(gspca_dev, 0x80, 0x0a);
+ spca506_WriteI2c(gspca_dev, 0x47, 0x0b);
+ spca506_WriteI2c(gspca_dev, 0x48, 0x0c);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x0d);
+ spca506_WriteI2c(gspca_dev, 0x03, 0x0e); /* Chroma Pal adjust */
+ spca506_WriteI2c(gspca_dev, 0x2a, 0x0f);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x10);
+ spca506_WriteI2c(gspca_dev, 0x0c, 0x11);
+ spca506_WriteI2c(gspca_dev, 0xb8, 0x12);
+ spca506_WriteI2c(gspca_dev, 0x01, 0x13);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x14);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x15);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x16);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x17);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x18);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x19);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x1a);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x1b);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x1c);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x1d);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x1e);
+ spca506_WriteI2c(gspca_dev, 0xa1, 0x1f);
+ spca506_WriteI2c(gspca_dev, 0x02, 0x40);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x41);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x42);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x43);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x44);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x45);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x46);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x47);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x48);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x49);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x4a);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x4b);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x4c);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x4d);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x4e);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x4f);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x50);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x51);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x52);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x53);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x54);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x55);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x56);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x57);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x58);
+ spca506_WriteI2c(gspca_dev, 0x54, 0x59);
+ spca506_WriteI2c(gspca_dev, 0x07, 0x5a);
+ spca506_WriteI2c(gspca_dev, 0x83, 0x5b);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x5c);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x5d);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x5e);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x5f);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x60);
+ spca506_WriteI2c(gspca_dev, 0x05, 0x61);
+ spca506_WriteI2c(gspca_dev, 0x9f, 0x62);
+ PDEBUG(D_STREAM, "** Close Init *");
+ return 0;
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+ struct usb_device *dev = gspca_dev->dev;
+ __u16 norme;
+ __u16 channel;
+
+ /**************************************/
+ reg_w(dev, 0x03, 0x00, 0x0004);
+ reg_w(dev, 0x03, 0x00, 0x0003);
+ reg_w(dev, 0x03, 0x00, 0x0004);
+ reg_w(dev, 0x03, 0xFF, 0x0003);
+ reg_w(dev, 0x02, 0x00, 0x0000);
+ reg_w(dev, 0x03, 0x60, 0x0000);
+ reg_w(dev, 0x03, 0x18, 0x0001);
+
+ /*sdca506_WriteI2c(value,register) */
+ spca506_Initi2c(gspca_dev);
+ spca506_WriteI2c(gspca_dev, 0x08, 0x01); /* Increment Delay */
+/* spca506_WriteI2c(gspca_dev, 0xc0, 0x02); * Analog Input Control 1 */
+ spca506_WriteI2c(gspca_dev, 0x33, 0x03);
+ /* Analog Input Control 2 */
+ spca506_WriteI2c(gspca_dev, 0x00, 0x04);
+ /* Analog Input Control 3 */
+ spca506_WriteI2c(gspca_dev, 0x00, 0x05);
+ /* Analog Input Control 4 */
+ spca506_WriteI2c(gspca_dev, 0x0d, 0x06);
+ /* Horizontal Sync Start 0xe9-0x0d */
+ spca506_WriteI2c(gspca_dev, 0xf0, 0x07);
+ /* Horizontal Sync Stop 0x0d-0xf0 */
+
+ spca506_WriteI2c(gspca_dev, 0x98, 0x08); /* Sync Control */
+/* Defaults value */
+ spca506_WriteI2c(gspca_dev, 0x03, 0x09); /* Luminance Control */
+ spca506_WriteI2c(gspca_dev, 0x80, 0x0a);
+ /* Luminance Brightness */
+ spca506_WriteI2c(gspca_dev, 0x47, 0x0b); /* Luminance Contrast */
+ spca506_WriteI2c(gspca_dev, 0x48, 0x0c);
+ /* Chrominance Saturation */
+ spca506_WriteI2c(gspca_dev, 0x00, 0x0d);
+ /* Chrominance Hue Control */
+ spca506_WriteI2c(gspca_dev, 0x2a, 0x0f);
+ /* Chrominance Gain Control */
+ /**************************************/
+ spca506_WriteI2c(gspca_dev, 0x00, 0x10);
+ /* Format/Delay Control */
+ spca506_WriteI2c(gspca_dev, 0x0c, 0x11); /* Output Control 1 */
+ spca506_WriteI2c(gspca_dev, 0xb8, 0x12); /* Output Control 2 */
+ spca506_WriteI2c(gspca_dev, 0x01, 0x13); /* Output Control 3 */
+ spca506_WriteI2c(gspca_dev, 0x00, 0x14); /* reserved */
+ spca506_WriteI2c(gspca_dev, 0x00, 0x15); /* VGATE START */
+ spca506_WriteI2c(gspca_dev, 0x00, 0x16); /* VGATE STOP */
+ spca506_WriteI2c(gspca_dev, 0x00, 0x17); /* VGATE Control (MSB) */
+ spca506_WriteI2c(gspca_dev, 0x00, 0x18);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x19);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x1a);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x1b);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x1c);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x1d);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x1e);
+ spca506_WriteI2c(gspca_dev, 0xa1, 0x1f);
+ spca506_WriteI2c(gspca_dev, 0x02, 0x40);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x41);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x42);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x43);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x44);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x45);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x46);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x47);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x48);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x49);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x4a);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x4b);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x4c);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x4d);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x4e);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x4f);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x50);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x51);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x52);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x53);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x54);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x55);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x56);
+ spca506_WriteI2c(gspca_dev, 0xff, 0x57);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x58);
+ spca506_WriteI2c(gspca_dev, 0x54, 0x59);
+ spca506_WriteI2c(gspca_dev, 0x07, 0x5a);
+ spca506_WriteI2c(gspca_dev, 0x83, 0x5b);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x5c);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x5d);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x5e);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x5f);
+ spca506_WriteI2c(gspca_dev, 0x00, 0x60);
+ spca506_WriteI2c(gspca_dev, 0x05, 0x61);
+ spca506_WriteI2c(gspca_dev, 0x9f, 0x62);
+ /**************************************/
+ reg_w(dev, 0x05, 0x00, 0x0003);
+ reg_w(dev, 0x05, 0x00, 0x0004);
+ reg_w(dev, 0x03, 0x10, 0x0001);
+ reg_w(dev, 0x03, 0x78, 0x0000);
+ switch (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv) {
+ case 0:
+ spca506_Setsize(gspca_dev, 0, 0x10, 0x10);
+ break;
+ case 1:
+ spca506_Setsize(gspca_dev, 1, 0x1a, 0x1a);
+ break;
+ case 2:
+ spca506_Setsize(gspca_dev, 2, 0x1c, 0x1c);
+ break;
+ case 4:
+ spca506_Setsize(gspca_dev, 4, 0x34, 0x34);
+ break;
+ default:
+/* case 5: */
+ spca506_Setsize(gspca_dev, 5, 0x40, 0x40);
+ break;
+ }
+
+ /* compress setting and size */
+ /* set i2c luma */
+ reg_w(dev, 0x02, 0x01, 0x0000);
+ reg_w(dev, 0x03, 0x12, 0x0000);
+ reg_r(gspca_dev, 0x04, 0x0001, 2);
+ PDEBUG(D_STREAM, "webcam started");
+ spca506_GetNormeInput(gspca_dev, &norme, &channel);
+ spca506_SetNormeInput(gspca_dev, norme, channel);
+ return 0;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+ struct usb_device *dev = gspca_dev->dev;
+
+ reg_w(dev, 0x02, 0x00, 0x0000);
+ reg_w(dev, 0x03, 0x00, 0x0004);
+ reg_w(dev, 0x03, 0x00, 0x0003);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+ u8 *data, /* isoc packet */
+ int len) /* iso packet length */
+{
+ switch (data[0]) {
+ case 0: /* start of frame */
+ gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+ data += SPCA50X_OFFSET_DATA;
+ len -= SPCA50X_OFFSET_DATA;
+ gspca_frame_add(gspca_dev, FIRST_PACKET, data, len);
+ break;
+ case 0xff: /* drop */
+/* gspca_dev->last_packet_type = DISCARD_PACKET; */
+ break;
+ default:
+ data += 1;
+ len -= 1;
+ gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+ break;
+ }
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 val)
+{
+ spca506_Initi2c(gspca_dev);
+ spca506_WriteI2c(gspca_dev, val, SAA7113_bright);
+ spca506_WriteI2c(gspca_dev, 0x01, 0x09);
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev, s32 val)
+{
+ spca506_Initi2c(gspca_dev);
+ spca506_WriteI2c(gspca_dev, val, SAA7113_contrast);
+ spca506_WriteI2c(gspca_dev, 0x01, 0x09);
+}
+
+static void setcolors(struct gspca_dev *gspca_dev, s32 val)
+{
+ spca506_Initi2c(gspca_dev);
+ spca506_WriteI2c(gspca_dev, val, SAA7113_saturation);
+ spca506_WriteI2c(gspca_dev, 0x01, 0x09);
+}
+
+static void sethue(struct gspca_dev *gspca_dev, s32 val)
+{
+ spca506_Initi2c(gspca_dev);
+ spca506_WriteI2c(gspca_dev, val, SAA7113_hue);
+ spca506_WriteI2c(gspca_dev, 0x01, 0x09);
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct gspca_dev *gspca_dev =
+ container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+
+ gspca_dev->usb_err = 0;
+
+ if (!gspca_dev->streaming)
+ return 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_BRIGHTNESS:
+ setbrightness(gspca_dev, ctrl->val);
+ break;
+ case V4L2_CID_CONTRAST:
+ setcontrast(gspca_dev, ctrl->val);
+ break;
+ case V4L2_CID_SATURATION:
+ setcolors(gspca_dev, ctrl->val);
+ break;
+ case V4L2_CID_HUE:
+ sethue(gspca_dev, ctrl->val);
+ break;
+ }
+ return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+ .s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+ struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+ gspca_dev->vdev.ctrl_handler = hdl;
+ v4l2_ctrl_handler_init(hdl, 4);
+ v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+ V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);
+ v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+ V4L2_CID_CONTRAST, 0, 255, 1, 0x47);
+ v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+ V4L2_CID_SATURATION, 0, 255, 1, 0x40);
+ v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+ V4L2_CID_HUE, 0, 255, 1, 0);
+
+ if (hdl->error) {
+ pr_err("Could not initialize controls\n");
+ return hdl->error;
+ }
+ return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+ .name = MODULE_NAME,
+ .config = sd_config,
+ .init = sd_init,
+ .init_controls = sd_init_controls,
+ .start = sd_start,
+ .stopN = sd_stopN,
+ .pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
++static const struct usb_device_id device_table[] = {
+ {USB_DEVICE(0x06e1, 0xa190)},
+/*fixme: may be IntelPCCameraPro BRIDGE_SPCA505
+ {USB_DEVICE(0x0733, 0x0430)}, */
+ {USB_DEVICE(0x0734, 0x043b)},
+ {USB_DEVICE(0x99fa, 0x8988)},
+ {}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int __devinit sd_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+ THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+ .name = MODULE_NAME,
+ .id_table = device_table,
+ .probe = sd_probe,
+ .disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+ .suspend = gspca_suspend,
+ .resume = gspca_resume,
+ .reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
--- /dev/null
- static const struct usb_device_id smsusb_id_table[] __devinitconst = {
+/****************************************************************
+
+Siano Mobile Silicon, Inc.
+MDTV receiver kernel modules.
+Copyright (C) 2005-2009, Uri Shkolnik, Anatoly Greenblat
+
+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, see <http://www.gnu.org/licenses/>.
+
+****************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+#include "smscoreapi.h"
+#include "sms-cards.h"
+#include "smsendian.h"
+
+static int sms_dbg;
+module_param_named(debug, sms_dbg, int, 0644);
+MODULE_PARM_DESC(debug, "set debug level (info=1, adv=2 (or-able))");
+
+#define USB1_BUFFER_SIZE 0x1000
+#define USB2_BUFFER_SIZE 0x4000
+
+#define MAX_BUFFERS 50
+#define MAX_URBS 10
+
+struct smsusb_device_t;
+
+struct smsusb_urb_t {
+ struct smscore_buffer_t *cb;
+ struct smsusb_device_t *dev;
+
+ struct urb urb;
+};
+
+struct smsusb_device_t {
+ struct usb_device *udev;
+ struct smscore_device_t *coredev;
+
+ struct smsusb_urb_t surbs[MAX_URBS];
+
+ int response_alignment;
+ int buffer_size;
+};
+
+static int smsusb_submit_urb(struct smsusb_device_t *dev,
+ struct smsusb_urb_t *surb);
+
+static void smsusb_onresponse(struct urb *urb)
+{
+ struct smsusb_urb_t *surb = (struct smsusb_urb_t *) urb->context;
+ struct smsusb_device_t *dev = surb->dev;
+
+ if (urb->status == -ESHUTDOWN) {
+ sms_err("error, urb status %d (-ESHUTDOWN), %d bytes",
+ urb->status, urb->actual_length);
+ return;
+ }
+
+ if ((urb->actual_length > 0) && (urb->status == 0)) {
+ struct SmsMsgHdr_ST *phdr = (struct SmsMsgHdr_ST *)surb->cb->p;
+
+ smsendian_handle_message_header(phdr);
+ if (urb->actual_length >= phdr->msgLength) {
+ surb->cb->size = phdr->msgLength;
+
+ if (dev->response_alignment &&
+ (phdr->msgFlags & MSG_HDR_FLAG_SPLIT_MSG)) {
+
+ surb->cb->offset =
+ dev->response_alignment +
+ ((phdr->msgFlags >> 8) & 3);
+
+ /* sanity check */
+ if (((int) phdr->msgLength +
+ surb->cb->offset) > urb->actual_length) {
+ sms_err("invalid response "
+ "msglen %d offset %d "
+ "size %d",
+ phdr->msgLength,
+ surb->cb->offset,
+ urb->actual_length);
+ goto exit_and_resubmit;
+ }
+
+ /* move buffer pointer and
+ * copy header to its new location */
+ memcpy((char *) phdr + surb->cb->offset,
+ phdr, sizeof(struct SmsMsgHdr_ST));
+ } else
+ surb->cb->offset = 0;
+
+ smscore_onresponse(dev->coredev, surb->cb);
+ surb->cb = NULL;
+ } else {
+ sms_err("invalid response "
+ "msglen %d actual %d",
+ phdr->msgLength, urb->actual_length);
+ }
+ } else
+ sms_err("error, urb status %d, %d bytes",
+ urb->status, urb->actual_length);
+
+
+exit_and_resubmit:
+ smsusb_submit_urb(dev, surb);
+}
+
+static int smsusb_submit_urb(struct smsusb_device_t *dev,
+ struct smsusb_urb_t *surb)
+{
+ if (!surb->cb) {
+ surb->cb = smscore_getbuffer(dev->coredev);
+ if (!surb->cb) {
+ sms_err("smscore_getbuffer(...) returned NULL");
+ return -ENOMEM;
+ }
+ }
+
+ usb_fill_bulk_urb(
+ &surb->urb,
+ dev->udev,
+ usb_rcvbulkpipe(dev->udev, 0x81),
+ surb->cb->p,
+ dev->buffer_size,
+ smsusb_onresponse,
+ surb
+ );
+ surb->urb.transfer_dma = surb->cb->phys;
+ surb->urb.transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ return usb_submit_urb(&surb->urb, GFP_ATOMIC);
+}
+
+static void smsusb_stop_streaming(struct smsusb_device_t *dev)
+{
+ int i;
+
+ for (i = 0; i < MAX_URBS; i++) {
+ usb_kill_urb(&dev->surbs[i].urb);
+
+ if (dev->surbs[i].cb) {
+ smscore_putbuffer(dev->coredev, dev->surbs[i].cb);
+ dev->surbs[i].cb = NULL;
+ }
+ }
+}
+
+static int smsusb_start_streaming(struct smsusb_device_t *dev)
+{
+ int i, rc;
+
+ for (i = 0; i < MAX_URBS; i++) {
+ rc = smsusb_submit_urb(dev, &dev->surbs[i]);
+ if (rc < 0) {
+ sms_err("smsusb_submit_urb(...) failed");
+ smsusb_stop_streaming(dev);
+ break;
+ }
+ }
+
+ return rc;
+}
+
+static int smsusb_sendrequest(void *context, void *buffer, size_t size)
+{
+ struct smsusb_device_t *dev = (struct smsusb_device_t *) context;
+ int dummy;
+
+ smsendian_handle_message_header((struct SmsMsgHdr_ST *)buffer);
+ return usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, 2),
+ buffer, size, &dummy, 1000);
+}
+
+static char *smsusb1_fw_lkup[] = {
+ "dvbt_stellar_usb.inp",
+ "dvbh_stellar_usb.inp",
+ "tdmb_stellar_usb.inp",
+ "none",
+ "dvbt_bda_stellar_usb.inp",
+};
+
+static inline char *sms_get_fw_name(int mode, int board_id)
+{
+ char **fw = sms_get_board(board_id)->fw;
+ return (fw && fw[mode]) ? fw[mode] : smsusb1_fw_lkup[mode];
+}
+
+static int smsusb1_load_firmware(struct usb_device *udev, int id, int board_id)
+{
+ const struct firmware *fw;
+ u8 *fw_buffer;
+ int rc, dummy;
+ char *fw_filename;
+
+ if (id < DEVICE_MODE_DVBT || id > DEVICE_MODE_DVBT_BDA) {
+ sms_err("invalid firmware id specified %d", id);
+ return -EINVAL;
+ }
+
+ fw_filename = sms_get_fw_name(id, board_id);
+
+ rc = request_firmware(&fw, fw_filename, &udev->dev);
+ if (rc < 0) {
+ sms_warn("failed to open \"%s\" mode %d, "
+ "trying again with default firmware", fw_filename, id);
+
+ fw_filename = smsusb1_fw_lkup[id];
+ rc = request_firmware(&fw, fw_filename, &udev->dev);
+ if (rc < 0) {
+ sms_warn("failed to open \"%s\" mode %d",
+ fw_filename, id);
+
+ return rc;
+ }
+ }
+
+ fw_buffer = kmalloc(fw->size, GFP_KERNEL);
+ if (fw_buffer) {
+ memcpy(fw_buffer, fw->data, fw->size);
+
+ rc = usb_bulk_msg(udev, usb_sndbulkpipe(udev, 2),
+ fw_buffer, fw->size, &dummy, 1000);
+
+ sms_info("sent %zd(%d) bytes, rc %d", fw->size, dummy, rc);
+
+ kfree(fw_buffer);
+ } else {
+ sms_err("failed to allocate firmware buffer");
+ rc = -ENOMEM;
+ }
+ sms_info("read FW %s, size=%zd", fw_filename, fw->size);
+
+ release_firmware(fw);
+
+ return rc;
+}
+
+static void smsusb1_detectmode(void *context, int *mode)
+{
+ char *product_string =
+ ((struct smsusb_device_t *) context)->udev->product;
+
+ *mode = DEVICE_MODE_NONE;
+
+ if (!product_string) {
+ product_string = "none";
+ sms_err("product string not found");
+ } else if (strstr(product_string, "DVBH"))
+ *mode = 1;
+ else if (strstr(product_string, "BDA"))
+ *mode = 4;
+ else if (strstr(product_string, "DVBT"))
+ *mode = 0;
+ else if (strstr(product_string, "TDMB"))
+ *mode = 2;
+
+ sms_info("%d \"%s\"", *mode, product_string);
+}
+
+static int smsusb1_setmode(void *context, int mode)
+{
+ struct SmsMsgHdr_ST Msg = { MSG_SW_RELOAD_REQ, 0, HIF_TASK,
+ sizeof(struct SmsMsgHdr_ST), 0 };
+
+ if (mode < DEVICE_MODE_DVBT || mode > DEVICE_MODE_DVBT_BDA) {
+ sms_err("invalid firmware id specified %d", mode);
+ return -EINVAL;
+ }
+
+ return smsusb_sendrequest(context, &Msg, sizeof(Msg));
+}
+
+static void smsusb_term_device(struct usb_interface *intf)
+{
+ struct smsusb_device_t *dev = usb_get_intfdata(intf);
+
+ if (dev) {
+ smsusb_stop_streaming(dev);
+
+ /* unregister from smscore */
+ if (dev->coredev)
+ smscore_unregister_device(dev->coredev);
+
+ sms_info("device %p destroyed", dev);
+ kfree(dev);
+ }
+
+ usb_set_intfdata(intf, NULL);
+}
+
+static int smsusb_init_device(struct usb_interface *intf, int board_id)
+{
+ struct smsdevice_params_t params;
+ struct smsusb_device_t *dev;
+ int i, rc;
+
+ /* create device object */
+ dev = kzalloc(sizeof(struct smsusb_device_t), GFP_KERNEL);
+ if (!dev) {
+ sms_err("kzalloc(sizeof(struct smsusb_device_t) failed");
+ return -ENOMEM;
+ }
+
+ memset(¶ms, 0, sizeof(params));
+ usb_set_intfdata(intf, dev);
+ dev->udev = interface_to_usbdev(intf);
+
+ params.device_type = sms_get_board(board_id)->type;
+
+ switch (params.device_type) {
+ case SMS_STELLAR:
+ dev->buffer_size = USB1_BUFFER_SIZE;
+
+ params.setmode_handler = smsusb1_setmode;
+ params.detectmode_handler = smsusb1_detectmode;
+ break;
+ default:
+ sms_err("Unspecified sms device type!");
+ /* fall-thru */
+ case SMS_NOVA_A0:
+ case SMS_NOVA_B0:
+ case SMS_VEGA:
+ dev->buffer_size = USB2_BUFFER_SIZE;
+ dev->response_alignment =
+ le16_to_cpu(dev->udev->ep_in[1]->desc.wMaxPacketSize) -
+ sizeof(struct SmsMsgHdr_ST);
+
+ params.flags |= SMS_DEVICE_FAMILY2;
+ break;
+ }
+
+ params.device = &dev->udev->dev;
+ params.buffer_size = dev->buffer_size;
+ params.num_buffers = MAX_BUFFERS;
+ params.sendrequest_handler = smsusb_sendrequest;
+ params.context = dev;
+ usb_make_path(dev->udev, params.devpath, sizeof(params.devpath));
+
+ /* register in smscore */
+ rc = smscore_register_device(¶ms, &dev->coredev);
+ if (rc < 0) {
+ sms_err("smscore_register_device(...) failed, rc %d", rc);
+ smsusb_term_device(intf);
+ return rc;
+ }
+
+ smscore_set_board_id(dev->coredev, board_id);
+
+ /* initialize urbs */
+ for (i = 0; i < MAX_URBS; i++) {
+ dev->surbs[i].dev = dev;
+ usb_init_urb(&dev->surbs[i].urb);
+ }
+
+ sms_info("smsusb_start_streaming(...).");
+ rc = smsusb_start_streaming(dev);
+ if (rc < 0) {
+ sms_err("smsusb_start_streaming(...) failed");
+ smsusb_term_device(intf);
+ return rc;
+ }
+
+ rc = smscore_start_device(dev->coredev);
+ if (rc < 0) {
+ sms_err("smscore_start_device(...) failed");
+ smsusb_term_device(intf);
+ return rc;
+ }
+
+ sms_info("device %p created", dev);
+
+ return rc;
+}
+
+static int __devinit smsusb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ char devpath[32];
+ int i, rc;
+
+ rc = usb_clear_halt(udev, usb_rcvbulkpipe(udev, 0x81));
+ rc = usb_clear_halt(udev, usb_rcvbulkpipe(udev, 0x02));
+
+ if (intf->num_altsetting > 0) {
+ rc = usb_set_interface(
+ udev, intf->cur_altsetting->desc.bInterfaceNumber, 0);
+ if (rc < 0) {
+ sms_err("usb_set_interface failed, rc %d", rc);
+ return rc;
+ }
+ }
+
+ sms_info("smsusb_probe %d",
+ intf->cur_altsetting->desc.bInterfaceNumber);
+ for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++)
+ sms_info("endpoint %d %02x %02x %d", i,
+ intf->cur_altsetting->endpoint[i].desc.bEndpointAddress,
+ intf->cur_altsetting->endpoint[i].desc.bmAttributes,
+ intf->cur_altsetting->endpoint[i].desc.wMaxPacketSize);
+
+ if ((udev->actconfig->desc.bNumInterfaces == 2) &&
+ (intf->cur_altsetting->desc.bInterfaceNumber == 0)) {
+ sms_err("rom interface 0 is not used");
+ return -ENODEV;
+ }
+
+ if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
+ snprintf(devpath, sizeof(devpath), "usb\\%d-%s",
+ udev->bus->busnum, udev->devpath);
+ sms_info("stellar device was found.");
+ return smsusb1_load_firmware(
+ udev, smscore_registry_getmode(devpath),
+ id->driver_info);
+ }
+
+ rc = smsusb_init_device(intf, id->driver_info);
+ sms_info("rc %d", rc);
+ sms_board_load_modules(id->driver_info);
+ return rc;
+}
+
+static void smsusb_disconnect(struct usb_interface *intf)
+{
+ smsusb_term_device(intf);
+}
+
+static int smsusb_suspend(struct usb_interface *intf, pm_message_t msg)
+{
+ struct smsusb_device_t *dev = usb_get_intfdata(intf);
+ printk(KERN_INFO "%s: Entering status %d.\n", __func__, msg.event);
+ smsusb_stop_streaming(dev);
+ return 0;
+}
+
+static int smsusb_resume(struct usb_interface *intf)
+{
+ int rc, i;
+ struct smsusb_device_t *dev = usb_get_intfdata(intf);
+ struct usb_device *udev = interface_to_usbdev(intf);
+
+ printk(KERN_INFO "%s: Entering.\n", __func__);
+ usb_clear_halt(udev, usb_rcvbulkpipe(udev, 0x81));
+ usb_clear_halt(udev, usb_rcvbulkpipe(udev, 0x02));
+
+ for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++)
+ printk(KERN_INFO "endpoint %d %02x %02x %d\n", i,
+ intf->cur_altsetting->endpoint[i].desc.bEndpointAddress,
+ intf->cur_altsetting->endpoint[i].desc.bmAttributes,
+ intf->cur_altsetting->endpoint[i].desc.wMaxPacketSize);
+
+ if (intf->num_altsetting > 0) {
+ rc = usb_set_interface(udev,
+ intf->cur_altsetting->desc.
+ bInterfaceNumber, 0);
+ if (rc < 0) {
+ printk(KERN_INFO "%s usb_set_interface failed, "
+ "rc %d\n", __func__, rc);
+ return rc;
+ }
+ }
+
+ smsusb_start_streaming(dev);
+ return 0;
+}
+
++static const struct usb_device_id smsusb_id_table[] = {
+ { USB_DEVICE(0x187f, 0x0010),
+ .driver_info = SMS1XXX_BOARD_SIANO_STELLAR },
+ { USB_DEVICE(0x187f, 0x0100),
+ .driver_info = SMS1XXX_BOARD_SIANO_STELLAR },
+ { USB_DEVICE(0x187f, 0x0200),
+ .driver_info = SMS1XXX_BOARD_SIANO_NOVA_A },
+ { USB_DEVICE(0x187f, 0x0201),
+ .driver_info = SMS1XXX_BOARD_SIANO_NOVA_B },
+ { USB_DEVICE(0x187f, 0x0300),
+ .driver_info = SMS1XXX_BOARD_SIANO_VEGA },
+ { USB_DEVICE(0x2040, 0x1700),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_CATAMOUNT },
+ { USB_DEVICE(0x2040, 0x1800),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_OKEMO_A },
+ { USB_DEVICE(0x2040, 0x1801),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_OKEMO_B },
+ { USB_DEVICE(0x2040, 0x2000),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD },
+ { USB_DEVICE(0x2040, 0x2009),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD_R2 },
+ { USB_DEVICE(0x2040, 0x200a),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD },
+ { USB_DEVICE(0x2040, 0x2010),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD },
+ { USB_DEVICE(0x2040, 0x2011),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD },
+ { USB_DEVICE(0x2040, 0x2019),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD },
+ { USB_DEVICE(0x2040, 0x5500),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+ { USB_DEVICE(0x2040, 0x5510),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+ { USB_DEVICE(0x2040, 0x5520),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+ { USB_DEVICE(0x2040, 0x5530),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+ { USB_DEVICE(0x2040, 0x5580),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+ { USB_DEVICE(0x2040, 0x5590),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+ { USB_DEVICE(0x187f, 0x0202),
+ .driver_info = SMS1XXX_BOARD_SIANO_NICE },
+ { USB_DEVICE(0x187f, 0x0301),
+ .driver_info = SMS1XXX_BOARD_SIANO_VENICE },
+ { USB_DEVICE(0x2040, 0xb900),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+ { USB_DEVICE(0x2040, 0xb910),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+ { USB_DEVICE(0x2040, 0xb980),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+ { USB_DEVICE(0x2040, 0xb990),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+ { USB_DEVICE(0x2040, 0xc000),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+ { USB_DEVICE(0x2040, 0xc010),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+ { USB_DEVICE(0x2040, 0xc080),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+ { USB_DEVICE(0x2040, 0xc090),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+ { USB_DEVICE(0x2040, 0xc0a0),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+ { USB_DEVICE(0x2040, 0xf5a0),
+ .driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+ { } /* Terminating entry */
+ };
+
+MODULE_DEVICE_TABLE(usb, smsusb_id_table);
+
+static struct usb_driver smsusb_driver = {
+ .name = "smsusb",
+ .probe = smsusb_probe,
+ .disconnect = smsusb_disconnect,
+ .id_table = smsusb_id_table,
+
+ .suspend = smsusb_suspend,
+ .resume = smsusb_resume,
+};
+
+module_usb_driver(smsusb_driver);
+
+MODULE_DESCRIPTION("Driver for the Siano SMS1xxx USB dongle");
+MODULE_AUTHOR("Siano Mobile Silicon, INC. (uris@siano-ms.com)");
+MODULE_LICENSE("GPL");
--- /dev/null
+/*
+ * uvc_queue.c -- USB Video Class driver - Buffers management
+ *
+ * Copyright (C) 2005-2010
+ * Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ * 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.
+ *
+ */
+
+#include <linux/atomic.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+#include <media/videobuf2-vmalloc.h>
+
+#include "uvcvideo.h"
+
+/* ------------------------------------------------------------------------
+ * Video buffers queue management.
+ *
+ * Video queues is initialized by uvc_queue_init(). The function performs
+ * basic initialization of the uvc_video_queue struct and never fails.
+ *
+ * Video buffers are managed by videobuf2. The driver uses a mutex to protect
+ * the videobuf2 queue operations by serializing calls to videobuf2 and a
+ * spinlock to protect the IRQ queue that holds the buffers to be processed by
+ * the driver.
+ */
+
+/* -----------------------------------------------------------------------------
+ * videobuf2 queue operations
+ */
+
+static int uvc_queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt,
+ unsigned int *nbuffers, unsigned int *nplanes,
+ unsigned int sizes[], void *alloc_ctxs[])
+{
+ struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
+ struct uvc_streaming *stream =
+ container_of(queue, struct uvc_streaming, queue);
+
+ if (*nbuffers > UVC_MAX_VIDEO_BUFFERS)
+ *nbuffers = UVC_MAX_VIDEO_BUFFERS;
+
+ *nplanes = 1;
+
+ sizes[0] = stream->ctrl.dwMaxVideoFrameSize;
+
+ return 0;
+}
+
+static int uvc_buffer_prepare(struct vb2_buffer *vb)
+{
+ struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue);
+ struct uvc_buffer *buf = container_of(vb, struct uvc_buffer, buf);
+
+ if (vb->v4l2_buf.type == V4L2_BUF_TYPE_VIDEO_OUTPUT &&
+ vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) {
+ uvc_trace(UVC_TRACE_CAPTURE, "[E] Bytes used out of bounds.\n");
+ return -EINVAL;
+ }
+
+ if (unlikely(queue->flags & UVC_QUEUE_DISCONNECTED))
+ return -ENODEV;
+
+ buf->state = UVC_BUF_STATE_QUEUED;
+ buf->error = 0;
+ buf->mem = vb2_plane_vaddr(vb, 0);
+ buf->length = vb2_plane_size(vb, 0);
+ if (vb->v4l2_buf.type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ buf->bytesused = 0;
+ else
+ buf->bytesused = vb2_get_plane_payload(vb, 0);
+
+ return 0;
+}
+
+static void uvc_buffer_queue(struct vb2_buffer *vb)
+{
+ struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue);
+ struct uvc_buffer *buf = container_of(vb, struct uvc_buffer, buf);
+ unsigned long flags;
+
+ spin_lock_irqsave(&queue->irqlock, flags);
+ if (likely(!(queue->flags & UVC_QUEUE_DISCONNECTED))) {
+ list_add_tail(&buf->queue, &queue->irqqueue);
+ } else {
+ /* If the device is disconnected return the buffer to userspace
+ * directly. The next QBUF call will fail with -ENODEV.
+ */
+ buf->state = UVC_BUF_STATE_ERROR;
+ vb2_buffer_done(&buf->buf, VB2_BUF_STATE_ERROR);
+ }
+
+ spin_unlock_irqrestore(&queue->irqlock, flags);
+}
+
+static int uvc_buffer_finish(struct vb2_buffer *vb)
+{
+ struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue);
+ struct uvc_streaming *stream =
+ container_of(queue, struct uvc_streaming, queue);
+ struct uvc_buffer *buf = container_of(vb, struct uvc_buffer, buf);
+
+ uvc_video_clock_update(stream, &vb->v4l2_buf, buf);
+ return 0;
+}
+
+static struct vb2_ops uvc_queue_qops = {
+ .queue_setup = uvc_queue_setup,
+ .buf_prepare = uvc_buffer_prepare,
+ .buf_queue = uvc_buffer_queue,
+ .buf_finish = uvc_buffer_finish,
+};
+
+void uvc_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
+ int drop_corrupted)
+{
+ queue->queue.type = type;
+ queue->queue.io_modes = VB2_MMAP | VB2_USERPTR;
+ queue->queue.drv_priv = queue;
+ queue->queue.buf_struct_size = sizeof(struct uvc_buffer);
+ queue->queue.ops = &uvc_queue_qops;
+ queue->queue.mem_ops = &vb2_vmalloc_memops;
+ vb2_queue_init(&queue->queue);
+
+ mutex_init(&queue->mutex);
+ spin_lock_init(&queue->irqlock);
+ INIT_LIST_HEAD(&queue->irqqueue);
+ queue->flags = drop_corrupted ? UVC_QUEUE_DROP_CORRUPTED : 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 queue operations
+ */
+
+int uvc_alloc_buffers(struct uvc_video_queue *queue,
+ struct v4l2_requestbuffers *rb)
+{
+ int ret;
+
+ mutex_lock(&queue->mutex);
+ ret = vb2_reqbufs(&queue->queue, rb);
+ mutex_unlock(&queue->mutex);
+
+ return ret ? ret : rb->count;
+}
+
+void uvc_free_buffers(struct uvc_video_queue *queue)
+{
+ mutex_lock(&queue->mutex);
+ vb2_queue_release(&queue->queue);
+ mutex_unlock(&queue->mutex);
+}
+
+int uvc_query_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf)
+{
+ int ret;
+
+ mutex_lock(&queue->mutex);
+ ret = vb2_querybuf(&queue->queue, buf);
+ mutex_unlock(&queue->mutex);
+
+ return ret;
+}
+
+int uvc_queue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf)
+{
+ int ret;
+
+ mutex_lock(&queue->mutex);
+ ret = vb2_qbuf(&queue->queue, buf);
+ mutex_unlock(&queue->mutex);
+
+ return ret;
+}
+
+int uvc_dequeue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf,
+ int nonblocking)
+{
+ int ret;
+
+ mutex_lock(&queue->mutex);
+ ret = vb2_dqbuf(&queue->queue, buf, nonblocking);
+ mutex_unlock(&queue->mutex);
+
+ return ret;
+}
+
+int uvc_queue_mmap(struct uvc_video_queue *queue, struct vm_area_struct *vma)
+{
+ int ret;
+
+ mutex_lock(&queue->mutex);
+ ret = vb2_mmap(&queue->queue, vma);
+ mutex_unlock(&queue->mutex);
+
+ return ret;
+}
+
+#ifndef CONFIG_MMU
+unsigned long uvc_queue_get_unmapped_area(struct uvc_video_queue *queue,
+ unsigned long pgoff)
+{
+ unsigned long ret;
+
+ mutex_lock(&queue->mutex);
+ ret = vb2_get_unmapped_area(&queue->queue, 0, 0, pgoff, 0);
+ mutex_unlock(&queue->mutex);
+ return ret;
+}
+#endif
+
+unsigned int uvc_queue_poll(struct uvc_video_queue *queue, struct file *file,
+ poll_table *wait)
+{
+ unsigned int ret;
+
+ mutex_lock(&queue->mutex);
+ ret = vb2_poll(&queue->queue, file, wait);
+ mutex_unlock(&queue->mutex);
+
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ *
+ */
+
+/*
+ * Check if buffers have been allocated.
+ */
+int uvc_queue_allocated(struct uvc_video_queue *queue)
+{
+ int allocated;
+
+ mutex_lock(&queue->mutex);
+ allocated = vb2_is_busy(&queue->queue);
+ mutex_unlock(&queue->mutex);
+
+ return allocated;
+}
+
+/*
+ * Enable or disable the video buffers queue.
+ *
+ * The queue must be enabled before starting video acquisition and must be
+ * disabled after stopping it. This ensures that the video buffers queue
+ * state can be properly initialized before buffers are accessed from the
+ * interrupt handler.
+ *
+ * Enabling the video queue returns -EBUSY if the queue is already enabled.
+ *
+ * Disabling the video queue cancels the queue and removes all buffers from
+ * the main queue.
+ *
+ * This function can't be called from interrupt context. Use
+ * uvc_queue_cancel() instead.
+ */
+int uvc_queue_enable(struct uvc_video_queue *queue, int enable)
+{
+ unsigned long flags;
+ int ret;
+
+ mutex_lock(&queue->mutex);
+ if (enable) {
+ ret = vb2_streamon(&queue->queue, queue->queue.type);
+ if (ret < 0)
+ goto done;
+
+ queue->buf_used = 0;
+ } else {
+ ret = vb2_streamoff(&queue->queue, queue->queue.type);
+ if (ret < 0)
+ goto done;
+
+ spin_lock_irqsave(&queue->irqlock, flags);
+ INIT_LIST_HEAD(&queue->irqqueue);
+ spin_unlock_irqrestore(&queue->irqlock, flags);
+ }
+
+done:
+ mutex_unlock(&queue->mutex);
+ return ret;
+}
+
+/*
+ * Cancel the video buffers queue.
+ *
+ * Cancelling the queue marks all buffers on the irq queue as erroneous,
+ * wakes them up and removes them from the queue.
+ *
+ * If the disconnect parameter is set, further calls to uvc_queue_buffer will
+ * fail with -ENODEV.
+ *
+ * This function acquires the irq spinlock and can be called from interrupt
+ * context.
+ */
+void uvc_queue_cancel(struct uvc_video_queue *queue, int disconnect)
+{
+ struct uvc_buffer *buf;
+ unsigned long flags;
+
+ spin_lock_irqsave(&queue->irqlock, flags);
+ while (!list_empty(&queue->irqqueue)) {
+ buf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
+ queue);
+ list_del(&buf->queue);
+ buf->state = UVC_BUF_STATE_ERROR;
+ vb2_buffer_done(&buf->buf, VB2_BUF_STATE_ERROR);
+ }
+ /* This must be protected by the irqlock spinlock to avoid race
+ * conditions between uvc_buffer_queue and the disconnection event that
+ * could result in an interruptible wait in uvc_dequeue_buffer. Do not
+ * blindly replace this logic by checking for the UVC_QUEUE_DISCONNECTED
+ * state outside the queue code.
+ */
+ if (disconnect)
+ queue->flags |= UVC_QUEUE_DISCONNECTED;
+ spin_unlock_irqrestore(&queue->irqlock, flags);
+}
+
+struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue,
+ struct uvc_buffer *buf)
+{
+ struct uvc_buffer *nextbuf;
+ unsigned long flags;
+
+ if ((queue->flags & UVC_QUEUE_DROP_CORRUPTED) && buf->error) {
+ buf->error = 0;
+ buf->state = UVC_BUF_STATE_QUEUED;
++ buf->bytesused = 0;
+ vb2_set_plane_payload(&buf->buf, 0, 0);
+ return buf;
+ }
+
+ spin_lock_irqsave(&queue->irqlock, flags);
+ list_del(&buf->queue);
+ if (!list_empty(&queue->irqqueue))
+ nextbuf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
+ queue);
+ else
+ nextbuf = NULL;
+ spin_unlock_irqrestore(&queue->irqlock, flags);
+
+ buf->state = buf->error ? VB2_BUF_STATE_ERROR : UVC_BUF_STATE_DONE;
+ vb2_set_plane_payload(&buf->buf, 0, buf->bytesused);
+ vb2_buffer_done(&buf->buf, VB2_BUF_STATE_DONE);
+
+ return nextbuf;
+}
--- /dev/null
- pr_cont("tuner=%u, type=%u, seek_upward=%u, wrap_around=%u, spacing=%u\n",
- p->tuner, p->type, p->seek_upward, p->wrap_around, p->spacing);
+/*
+ * Video capture interface for Linux version 2
+ *
+ * A generic framework to process V4L2 ioctl commands.
+ *
+ * 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.
+ *
+ * Authors: Alan Cox, <alan@lxorguk.ukuu.org.uk> (version 1)
+ * Mauro Carvalho Chehab <mchehab@infradead.org> (version 2)
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/version.h>
+
+#include <linux/videodev2.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-chip-ident.h>
+#include <media/videobuf2-core.h>
+
+/* Zero out the end of the struct pointed to by p. Everything after, but
+ * not including, the specified field is cleared. */
+#define CLEAR_AFTER_FIELD(p, field) \
+ memset((u8 *)(p) + offsetof(typeof(*(p)), field) + sizeof((p)->field), \
+ 0, sizeof(*(p)) - offsetof(typeof(*(p)), field) - sizeof((p)->field))
+
+struct std_descr {
+ v4l2_std_id std;
+ const char *descr;
+};
+
+static const struct std_descr standards[] = {
+ { V4L2_STD_NTSC, "NTSC" },
+ { V4L2_STD_NTSC_M, "NTSC-M" },
+ { V4L2_STD_NTSC_M_JP, "NTSC-M-JP" },
+ { V4L2_STD_NTSC_M_KR, "NTSC-M-KR" },
+ { V4L2_STD_NTSC_443, "NTSC-443" },
+ { V4L2_STD_PAL, "PAL" },
+ { V4L2_STD_PAL_BG, "PAL-BG" },
+ { V4L2_STD_PAL_B, "PAL-B" },
+ { V4L2_STD_PAL_B1, "PAL-B1" },
+ { V4L2_STD_PAL_G, "PAL-G" },
+ { V4L2_STD_PAL_H, "PAL-H" },
+ { V4L2_STD_PAL_I, "PAL-I" },
+ { V4L2_STD_PAL_DK, "PAL-DK" },
+ { V4L2_STD_PAL_D, "PAL-D" },
+ { V4L2_STD_PAL_D1, "PAL-D1" },
+ { V4L2_STD_PAL_K, "PAL-K" },
+ { V4L2_STD_PAL_M, "PAL-M" },
+ { V4L2_STD_PAL_N, "PAL-N" },
+ { V4L2_STD_PAL_Nc, "PAL-Nc" },
+ { V4L2_STD_PAL_60, "PAL-60" },
+ { V4L2_STD_SECAM, "SECAM" },
+ { V4L2_STD_SECAM_B, "SECAM-B" },
+ { V4L2_STD_SECAM_G, "SECAM-G" },
+ { V4L2_STD_SECAM_H, "SECAM-H" },
+ { V4L2_STD_SECAM_DK, "SECAM-DK" },
+ { V4L2_STD_SECAM_D, "SECAM-D" },
+ { V4L2_STD_SECAM_K, "SECAM-K" },
+ { V4L2_STD_SECAM_K1, "SECAM-K1" },
+ { V4L2_STD_SECAM_L, "SECAM-L" },
+ { V4L2_STD_SECAM_LC, "SECAM-Lc" },
+ { 0, "Unknown" }
+};
+
+/* video4linux standard ID conversion to standard name
+ */
+const char *v4l2_norm_to_name(v4l2_std_id id)
+{
+ u32 myid = id;
+ int i;
+
+ /* HACK: ppc32 architecture doesn't have __ucmpdi2 function to handle
+ 64 bit comparations. So, on that architecture, with some gcc
+ variants, compilation fails. Currently, the max value is 30bit wide.
+ */
+ BUG_ON(myid != id);
+
+ for (i = 0; standards[i].std; i++)
+ if (myid == standards[i].std)
+ break;
+ return standards[i].descr;
+}
+EXPORT_SYMBOL(v4l2_norm_to_name);
+
+/* Returns frame period for the given standard */
+void v4l2_video_std_frame_period(int id, struct v4l2_fract *frameperiod)
+{
+ if (id & V4L2_STD_525_60) {
+ frameperiod->numerator = 1001;
+ frameperiod->denominator = 30000;
+ } else {
+ frameperiod->numerator = 1;
+ frameperiod->denominator = 25;
+ }
+}
+EXPORT_SYMBOL(v4l2_video_std_frame_period);
+
+/* Fill in the fields of a v4l2_standard structure according to the
+ 'id' and 'transmission' parameters. Returns negative on error. */
+int v4l2_video_std_construct(struct v4l2_standard *vs,
+ int id, const char *name)
+{
+ vs->id = id;
+ v4l2_video_std_frame_period(id, &vs->frameperiod);
+ vs->framelines = (id & V4L2_STD_525_60) ? 525 : 625;
+ strlcpy(vs->name, name, sizeof(vs->name));
+ return 0;
+}
+EXPORT_SYMBOL(v4l2_video_std_construct);
+
+/* ----------------------------------------------------------------- */
+/* some arrays for pretty-printing debug messages of enum types */
+
+const char *v4l2_field_names[] = {
+ [V4L2_FIELD_ANY] = "any",
+ [V4L2_FIELD_NONE] = "none",
+ [V4L2_FIELD_TOP] = "top",
+ [V4L2_FIELD_BOTTOM] = "bottom",
+ [V4L2_FIELD_INTERLACED] = "interlaced",
+ [V4L2_FIELD_SEQ_TB] = "seq-tb",
+ [V4L2_FIELD_SEQ_BT] = "seq-bt",
+ [V4L2_FIELD_ALTERNATE] = "alternate",
+ [V4L2_FIELD_INTERLACED_TB] = "interlaced-tb",
+ [V4L2_FIELD_INTERLACED_BT] = "interlaced-bt",
+};
+EXPORT_SYMBOL(v4l2_field_names);
+
+const char *v4l2_type_names[] = {
+ [V4L2_BUF_TYPE_VIDEO_CAPTURE] = "vid-cap",
+ [V4L2_BUF_TYPE_VIDEO_OVERLAY] = "vid-overlay",
+ [V4L2_BUF_TYPE_VIDEO_OUTPUT] = "vid-out",
+ [V4L2_BUF_TYPE_VBI_CAPTURE] = "vbi-cap",
+ [V4L2_BUF_TYPE_VBI_OUTPUT] = "vbi-out",
+ [V4L2_BUF_TYPE_SLICED_VBI_CAPTURE] = "sliced-vbi-cap",
+ [V4L2_BUF_TYPE_SLICED_VBI_OUTPUT] = "sliced-vbi-out",
+ [V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY] = "vid-out-overlay",
+ [V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE] = "vid-cap-mplane",
+ [V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE] = "vid-out-mplane",
+};
+EXPORT_SYMBOL(v4l2_type_names);
+
+static const char *v4l2_memory_names[] = {
+ [V4L2_MEMORY_MMAP] = "mmap",
+ [V4L2_MEMORY_USERPTR] = "userptr",
+ [V4L2_MEMORY_OVERLAY] = "overlay",
+};
+
+#define prt_names(a, arr) ((((a) >= 0) && ((a) < ARRAY_SIZE(arr))) ? \
+ arr[a] : "unknown")
+
+/* ------------------------------------------------------------------ */
+/* debug help functions */
+
+static void v4l_print_querycap(const void *arg, bool write_only)
+{
+ const struct v4l2_capability *p = arg;
+
+ pr_cont("driver=%s, card=%s, bus=%s, version=0x%08x, "
+ "capabilities=0x%08x, device_caps=0x%08x\n",
+ p->driver, p->card, p->bus_info,
+ p->version, p->capabilities, p->device_caps);
+}
+
+static void v4l_print_enuminput(const void *arg, bool write_only)
+{
+ const struct v4l2_input *p = arg;
+
+ pr_cont("index=%u, name=%s, type=%u, audioset=0x%x, tuner=%u, "
+ "std=0x%08Lx, status=0x%x, capabilities=0x%x\n",
+ p->index, p->name, p->type, p->audioset, p->tuner,
+ (unsigned long long)p->std, p->status, p->capabilities);
+}
+
+static void v4l_print_enumoutput(const void *arg, bool write_only)
+{
+ const struct v4l2_output *p = arg;
+
+ pr_cont("index=%u, name=%s, type=%u, audioset=0x%x, "
+ "modulator=%u, std=0x%08Lx, capabilities=0x%x\n",
+ p->index, p->name, p->type, p->audioset, p->modulator,
+ (unsigned long long)p->std, p->capabilities);
+}
+
+static void v4l_print_audio(const void *arg, bool write_only)
+{
+ const struct v4l2_audio *p = arg;
+
+ if (write_only)
+ pr_cont("index=%u, mode=0x%x\n", p->index, p->mode);
+ else
+ pr_cont("index=%u, name=%s, capability=0x%x, mode=0x%x\n",
+ p->index, p->name, p->capability, p->mode);
+}
+
+static void v4l_print_audioout(const void *arg, bool write_only)
+{
+ const struct v4l2_audioout *p = arg;
+
+ if (write_only)
+ pr_cont("index=%u\n", p->index);
+ else
+ pr_cont("index=%u, name=%s, capability=0x%x, mode=0x%x\n",
+ p->index, p->name, p->capability, p->mode);
+}
+
+static void v4l_print_fmtdesc(const void *arg, bool write_only)
+{
+ const struct v4l2_fmtdesc *p = arg;
+
+ pr_cont("index=%u, type=%s, flags=0x%x, pixelformat=%c%c%c%c, description='%s'\n",
+ p->index, prt_names(p->type, v4l2_type_names),
+ p->flags, (p->pixelformat & 0xff),
+ (p->pixelformat >> 8) & 0xff,
+ (p->pixelformat >> 16) & 0xff,
+ (p->pixelformat >> 24) & 0xff,
+ p->description);
+}
+
+static void v4l_print_format(const void *arg, bool write_only)
+{
+ const struct v4l2_format *p = arg;
+ const struct v4l2_pix_format *pix;
+ const struct v4l2_pix_format_mplane *mp;
+ const struct v4l2_vbi_format *vbi;
+ const struct v4l2_sliced_vbi_format *sliced;
+ const struct v4l2_window *win;
+ const struct v4l2_clip *clip;
+ unsigned i;
+
+ pr_cont("type=%s", prt_names(p->type, v4l2_type_names));
+ switch (p->type) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+ pix = &p->fmt.pix;
+ pr_cont(", width=%u, height=%u, "
+ "pixelformat=%c%c%c%c, field=%s, "
+ "bytesperline=%u sizeimage=%u, colorspace=%d\n",
+ pix->width, pix->height,
+ (pix->pixelformat & 0xff),
+ (pix->pixelformat >> 8) & 0xff,
+ (pix->pixelformat >> 16) & 0xff,
+ (pix->pixelformat >> 24) & 0xff,
+ prt_names(pix->field, v4l2_field_names),
+ pix->bytesperline, pix->sizeimage,
+ pix->colorspace);
+ break;
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ mp = &p->fmt.pix_mp;
+ pr_cont(", width=%u, height=%u, "
+ "format=%c%c%c%c, field=%s, "
+ "colorspace=%d, num_planes=%u\n",
+ mp->width, mp->height,
+ (mp->pixelformat & 0xff),
+ (mp->pixelformat >> 8) & 0xff,
+ (mp->pixelformat >> 16) & 0xff,
+ (mp->pixelformat >> 24) & 0xff,
+ prt_names(mp->field, v4l2_field_names),
+ mp->colorspace, mp->num_planes);
+ for (i = 0; i < mp->num_planes; i++)
+ printk(KERN_DEBUG "plane %u: bytesperline=%u sizeimage=%u\n", i,
+ mp->plane_fmt[i].bytesperline,
+ mp->plane_fmt[i].sizeimage);
+ break;
+ case V4L2_BUF_TYPE_VIDEO_OVERLAY:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY:
+ win = &p->fmt.win;
+ pr_cont(", wxh=%dx%d, x,y=%d,%d, field=%s, "
+ "chromakey=0x%08x, bitmap=%p, "
+ "global_alpha=0x%02x\n",
+ win->w.width, win->w.height,
+ win->w.left, win->w.top,
+ prt_names(win->field, v4l2_field_names),
+ win->chromakey, win->bitmap, win->global_alpha);
+ clip = win->clips;
+ for (i = 0; i < win->clipcount; i++) {
+ printk(KERN_DEBUG "clip %u: wxh=%dx%d, x,y=%d,%d\n",
+ i, clip->c.width, clip->c.height,
+ clip->c.left, clip->c.top);
+ clip = clip->next;
+ }
+ break;
+ case V4L2_BUF_TYPE_VBI_CAPTURE:
+ case V4L2_BUF_TYPE_VBI_OUTPUT:
+ vbi = &p->fmt.vbi;
+ pr_cont(", sampling_rate=%u, offset=%u, samples_per_line=%u, "
+ "sample_format=%c%c%c%c, start=%u,%u, count=%u,%u\n",
+ vbi->sampling_rate, vbi->offset,
+ vbi->samples_per_line,
+ (vbi->sample_format & 0xff),
+ (vbi->sample_format >> 8) & 0xff,
+ (vbi->sample_format >> 16) & 0xff,
+ (vbi->sample_format >> 24) & 0xff,
+ vbi->start[0], vbi->start[1],
+ vbi->count[0], vbi->count[1]);
+ break;
+ case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
+ case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT:
+ sliced = &p->fmt.sliced;
+ pr_cont(", service_set=0x%08x, io_size=%d\n",
+ sliced->service_set, sliced->io_size);
+ for (i = 0; i < 24; i++)
+ printk(KERN_DEBUG "line[%02u]=0x%04x, 0x%04x\n", i,
+ sliced->service_lines[0][i],
+ sliced->service_lines[1][i]);
+ break;
+ case V4L2_BUF_TYPE_PRIVATE:
+ pr_cont("\n");
+ break;
+ }
+}
+
+static void v4l_print_framebuffer(const void *arg, bool write_only)
+{
+ const struct v4l2_framebuffer *p = arg;
+
+ pr_cont("capability=0x%x, flags=0x%x, base=0x%p, width=%u, "
+ "height=%u, pixelformat=%c%c%c%c, "
+ "bytesperline=%u sizeimage=%u, colorspace=%d\n",
+ p->capability, p->flags, p->base,
+ p->fmt.width, p->fmt.height,
+ (p->fmt.pixelformat & 0xff),
+ (p->fmt.pixelformat >> 8) & 0xff,
+ (p->fmt.pixelformat >> 16) & 0xff,
+ (p->fmt.pixelformat >> 24) & 0xff,
+ p->fmt.bytesperline, p->fmt.sizeimage,
+ p->fmt.colorspace);
+}
+
+static void v4l_print_buftype(const void *arg, bool write_only)
+{
+ pr_cont("type=%s\n", prt_names(*(u32 *)arg, v4l2_type_names));
+}
+
+static void v4l_print_modulator(const void *arg, bool write_only)
+{
+ const struct v4l2_modulator *p = arg;
+
+ if (write_only)
+ pr_cont("index=%u, txsubchans=0x%x", p->index, p->txsubchans);
+ else
+ pr_cont("index=%u, name=%s, capability=0x%x, "
+ "rangelow=%u, rangehigh=%u, txsubchans=0x%x\n",
+ p->index, p->name, p->capability,
+ p->rangelow, p->rangehigh, p->txsubchans);
+}
+
+static void v4l_print_tuner(const void *arg, bool write_only)
+{
+ const struct v4l2_tuner *p = arg;
+
+ if (write_only)
+ pr_cont("index=%u, audmode=%u\n", p->index, p->audmode);
+ else
+ pr_cont("index=%u, name=%s, type=%u, capability=0x%x, "
+ "rangelow=%u, rangehigh=%u, signal=%u, afc=%d, "
+ "rxsubchans=0x%x, audmode=%u\n",
+ p->index, p->name, p->type,
+ p->capability, p->rangelow,
+ p->rangehigh, p->signal, p->afc,
+ p->rxsubchans, p->audmode);
+}
+
+static void v4l_print_frequency(const void *arg, bool write_only)
+{
+ const struct v4l2_frequency *p = arg;
+
+ pr_cont("tuner=%u, type=%u, frequency=%u\n",
+ p->tuner, p->type, p->frequency);
+}
+
+static void v4l_print_standard(const void *arg, bool write_only)
+{
+ const struct v4l2_standard *p = arg;
+
+ pr_cont("index=%u, id=0x%Lx, name=%s, fps=%u/%u, "
+ "framelines=%u\n", p->index,
+ (unsigned long long)p->id, p->name,
+ p->frameperiod.numerator,
+ p->frameperiod.denominator,
+ p->framelines);
+}
+
+static void v4l_print_std(const void *arg, bool write_only)
+{
+ pr_cont("std=0x%08Lx\n", *(const long long unsigned *)arg);
+}
+
+static void v4l_print_hw_freq_seek(const void *arg, bool write_only)
+{
+ const struct v4l2_hw_freq_seek *p = arg;
+
++ pr_cont("tuner=%u, type=%u, seek_upward=%u, wrap_around=%u, spacing=%u, "
++ "rangelow=%u, rangehigh=%u\n",
++ p->tuner, p->type, p->seek_upward, p->wrap_around, p->spacing,
++ p->rangelow, p->rangehigh);
+}
+
+static void v4l_print_requestbuffers(const void *arg, bool write_only)
+{
+ const struct v4l2_requestbuffers *p = arg;
+
+ pr_cont("count=%d, type=%s, memory=%s\n",
+ p->count,
+ prt_names(p->type, v4l2_type_names),
+ prt_names(p->memory, v4l2_memory_names));
+}
+
+static void v4l_print_buffer(const void *arg, bool write_only)
+{
+ const struct v4l2_buffer *p = arg;
+ const struct v4l2_timecode *tc = &p->timecode;
+ const struct v4l2_plane *plane;
+ int i;
+
+ pr_cont("%02ld:%02d:%02d.%08ld index=%d, type=%s, "
+ "flags=0x%08x, field=%s, sequence=%d, memory=%s",
+ p->timestamp.tv_sec / 3600,
+ (int)(p->timestamp.tv_sec / 60) % 60,
+ (int)(p->timestamp.tv_sec % 60),
+ (long)p->timestamp.tv_usec,
+ p->index,
+ prt_names(p->type, v4l2_type_names),
+ p->flags, prt_names(p->field, v4l2_field_names),
+ p->sequence, prt_names(p->memory, v4l2_memory_names));
+
+ if (V4L2_TYPE_IS_MULTIPLANAR(p->type) && p->m.planes) {
+ pr_cont("\n");
+ for (i = 0; i < p->length; ++i) {
+ plane = &p->m.planes[i];
+ printk(KERN_DEBUG
+ "plane %d: bytesused=%d, data_offset=0x%08x "
+ "offset/userptr=0x%lx, length=%d\n",
+ i, plane->bytesused, plane->data_offset,
+ plane->m.userptr, plane->length);
+ }
+ } else {
+ pr_cont("bytesused=%d, offset/userptr=0x%lx, length=%d\n",
+ p->bytesused, p->m.userptr, p->length);
+ }
+
+ printk(KERN_DEBUG "timecode=%02d:%02d:%02d type=%d, "
+ "flags=0x%08x, frames=%d, userbits=0x%08x\n",
+ tc->hours, tc->minutes, tc->seconds,
+ tc->type, tc->flags, tc->frames, *(__u32 *)tc->userbits);
+}
+
+static void v4l_print_create_buffers(const void *arg, bool write_only)
+{
+ const struct v4l2_create_buffers *p = arg;
+
+ pr_cont("index=%d, count=%d, memory=%s, ",
+ p->index, p->count,
+ prt_names(p->memory, v4l2_memory_names));
+ v4l_print_format(&p->format, write_only);
+}
+
+static void v4l_print_streamparm(const void *arg, bool write_only)
+{
+ const struct v4l2_streamparm *p = arg;
+
+ pr_cont("type=%s", prt_names(p->type, v4l2_type_names));
+
+ if (p->type == V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+ p->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ const struct v4l2_captureparm *c = &p->parm.capture;
+
+ pr_cont(", capability=0x%x, capturemode=0x%x, timeperframe=%d/%d, "
+ "extendedmode=%d, readbuffers=%d\n",
+ c->capability, c->capturemode,
+ c->timeperframe.numerator, c->timeperframe.denominator,
+ c->extendedmode, c->readbuffers);
+ } else if (p->type == V4L2_BUF_TYPE_VIDEO_OUTPUT ||
+ p->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ const struct v4l2_outputparm *c = &p->parm.output;
+
+ pr_cont(", capability=0x%x, outputmode=0x%x, timeperframe=%d/%d, "
+ "extendedmode=%d, writebuffers=%d\n",
+ c->capability, c->outputmode,
+ c->timeperframe.numerator, c->timeperframe.denominator,
+ c->extendedmode, c->writebuffers);
+ }
+}
+
+static void v4l_print_queryctrl(const void *arg, bool write_only)
+{
+ const struct v4l2_queryctrl *p = arg;
+
+ pr_cont("id=0x%x, type=%d, name=%s, min/max=%d/%d, "
+ "step=%d, default=%d, flags=0x%08x\n",
+ p->id, p->type, p->name,
+ p->minimum, p->maximum,
+ p->step, p->default_value, p->flags);
+}
+
+static void v4l_print_querymenu(const void *arg, bool write_only)
+{
+ const struct v4l2_querymenu *p = arg;
+
+ pr_cont("id=0x%x, index=%d\n", p->id, p->index);
+}
+
+static void v4l_print_control(const void *arg, bool write_only)
+{
+ const struct v4l2_control *p = arg;
+
+ pr_cont("id=0x%x, value=%d\n", p->id, p->value);
+}
+
+static void v4l_print_ext_controls(const void *arg, bool write_only)
+{
+ const struct v4l2_ext_controls *p = arg;
+ int i;
+
+ pr_cont("class=0x%x, count=%d, error_idx=%d",
+ p->ctrl_class, p->count, p->error_idx);
+ for (i = 0; i < p->count; i++) {
+ if (p->controls[i].size)
+ pr_cont(", id/val=0x%x/0x%x",
+ p->controls[i].id, p->controls[i].value);
+ else
+ pr_cont(", id/size=0x%x/%u",
+ p->controls[i].id, p->controls[i].size);
+ }
+ pr_cont("\n");
+}
+
+static void v4l_print_cropcap(const void *arg, bool write_only)
+{
+ const struct v4l2_cropcap *p = arg;
+
+ pr_cont("type=%s, bounds wxh=%dx%d, x,y=%d,%d, "
+ "defrect wxh=%dx%d, x,y=%d,%d\n, "
+ "pixelaspect %d/%d\n",
+ prt_names(p->type, v4l2_type_names),
+ p->bounds.width, p->bounds.height,
+ p->bounds.left, p->bounds.top,
+ p->defrect.width, p->defrect.height,
+ p->defrect.left, p->defrect.top,
+ p->pixelaspect.numerator, p->pixelaspect.denominator);
+}
+
+static void v4l_print_crop(const void *arg, bool write_only)
+{
+ const struct v4l2_crop *p = arg;
+
+ pr_cont("type=%s, wxh=%dx%d, x,y=%d,%d\n",
+ prt_names(p->type, v4l2_type_names),
+ p->c.width, p->c.height,
+ p->c.left, p->c.top);
+}
+
+static void v4l_print_selection(const void *arg, bool write_only)
+{
+ const struct v4l2_selection *p = arg;
+
+ pr_cont("type=%s, target=%d, flags=0x%x, wxh=%dx%d, x,y=%d,%d\n",
+ prt_names(p->type, v4l2_type_names),
+ p->target, p->flags,
+ p->r.width, p->r.height, p->r.left, p->r.top);
+}
+
+static void v4l_print_jpegcompression(const void *arg, bool write_only)
+{
+ const struct v4l2_jpegcompression *p = arg;
+
+ pr_cont("quality=%d, APPn=%d, APP_len=%d, "
+ "COM_len=%d, jpeg_markers=0x%x\n",
+ p->quality, p->APPn, p->APP_len,
+ p->COM_len, p->jpeg_markers);
+}
+
+static void v4l_print_enc_idx(const void *arg, bool write_only)
+{
+ const struct v4l2_enc_idx *p = arg;
+
+ pr_cont("entries=%d, entries_cap=%d\n",
+ p->entries, p->entries_cap);
+}
+
+static void v4l_print_encoder_cmd(const void *arg, bool write_only)
+{
+ const struct v4l2_encoder_cmd *p = arg;
+
+ pr_cont("cmd=%d, flags=0x%x\n",
+ p->cmd, p->flags);
+}
+
+static void v4l_print_decoder_cmd(const void *arg, bool write_only)
+{
+ const struct v4l2_decoder_cmd *p = arg;
+
+ pr_cont("cmd=%d, flags=0x%x\n", p->cmd, p->flags);
+
+ if (p->cmd == V4L2_DEC_CMD_START)
+ pr_info("speed=%d, format=%u\n",
+ p->start.speed, p->start.format);
+ else if (p->cmd == V4L2_DEC_CMD_STOP)
+ pr_info("pts=%llu\n", p->stop.pts);
+}
+
+static void v4l_print_dbg_chip_ident(const void *arg, bool write_only)
+{
+ const struct v4l2_dbg_chip_ident *p = arg;
+
+ pr_cont("type=%u, ", p->match.type);
+ if (p->match.type == V4L2_CHIP_MATCH_I2C_DRIVER)
+ pr_cont("name=%s, ", p->match.name);
+ else
+ pr_cont("addr=%u, ", p->match.addr);
+ pr_cont("chip_ident=%u, revision=0x%x\n",
+ p->ident, p->revision);
+}
+
+static void v4l_print_dbg_register(const void *arg, bool write_only)
+{
+ const struct v4l2_dbg_register *p = arg;
+
+ pr_cont("type=%u, ", p->match.type);
+ if (p->match.type == V4L2_CHIP_MATCH_I2C_DRIVER)
+ pr_cont("name=%s, ", p->match.name);
+ else
+ pr_cont("addr=%u, ", p->match.addr);
+ pr_cont("reg=0x%llx, val=0x%llx\n",
+ p->reg, p->val);
+}
+
+static void v4l_print_dv_enum_presets(const void *arg, bool write_only)
+{
+ const struct v4l2_dv_enum_preset *p = arg;
+
+ pr_cont("index=%u, preset=%u, name=%s, width=%u, height=%u\n",
+ p->index, p->preset, p->name, p->width, p->height);
+}
+
+static void v4l_print_dv_preset(const void *arg, bool write_only)
+{
+ const struct v4l2_dv_preset *p = arg;
+
+ pr_cont("preset=%u\n", p->preset);
+}
+
+static void v4l_print_dv_timings(const void *arg, bool write_only)
+{
+ const struct v4l2_dv_timings *p = arg;
+
+ switch (p->type) {
+ case V4L2_DV_BT_656_1120:
+ pr_cont("type=bt-656/1120, interlaced=%u, "
+ "pixelclock=%llu, "
+ "width=%u, height=%u, polarities=0x%x, "
+ "hfrontporch=%u, hsync=%u, "
+ "hbackporch=%u, vfrontporch=%u, "
+ "vsync=%u, vbackporch=%u, "
+ "il_vfrontporch=%u, il_vsync=%u, "
+ "il_vbackporch=%u, standards=0x%x, flags=0x%x\n",
+ p->bt.interlaced, p->bt.pixelclock,
+ p->bt.width, p->bt.height,
+ p->bt.polarities, p->bt.hfrontporch,
+ p->bt.hsync, p->bt.hbackporch,
+ p->bt.vfrontporch, p->bt.vsync,
+ p->bt.vbackporch, p->bt.il_vfrontporch,
+ p->bt.il_vsync, p->bt.il_vbackporch,
+ p->bt.standards, p->bt.flags);
+ break;
+ default:
+ pr_cont("type=%d\n", p->type);
+ break;
+ }
+}
+
+static void v4l_print_enum_dv_timings(const void *arg, bool write_only)
+{
+ const struct v4l2_enum_dv_timings *p = arg;
+
+ pr_cont("index=%u, ", p->index);
+ v4l_print_dv_timings(&p->timings, write_only);
+}
+
+static void v4l_print_dv_timings_cap(const void *arg, bool write_only)
+{
+ const struct v4l2_dv_timings_cap *p = arg;
+
+ switch (p->type) {
+ case V4L2_DV_BT_656_1120:
+ pr_cont("type=bt-656/1120, width=%u-%u, height=%u-%u, "
+ "pixelclock=%llu-%llu, standards=0x%x, capabilities=0x%x\n",
+ p->bt.min_width, p->bt.max_width,
+ p->bt.min_height, p->bt.max_height,
+ p->bt.min_pixelclock, p->bt.max_pixelclock,
+ p->bt.standards, p->bt.capabilities);
+ break;
+ default:
+ pr_cont("type=%u\n", p->type);
+ break;
+ }
+}
+
+static void v4l_print_frmsizeenum(const void *arg, bool write_only)
+{
+ const struct v4l2_frmsizeenum *p = arg;
+
+ pr_cont("index=%u, pixelformat=%c%c%c%c, type=%u",
+ p->index,
+ (p->pixel_format & 0xff),
+ (p->pixel_format >> 8) & 0xff,
+ (p->pixel_format >> 16) & 0xff,
+ (p->pixel_format >> 24) & 0xff,
+ p->type);
+ switch (p->type) {
+ case V4L2_FRMSIZE_TYPE_DISCRETE:
+ pr_cont(" wxh=%ux%u\n",
+ p->discrete.width, p->discrete.height);
+ break;
+ case V4L2_FRMSIZE_TYPE_STEPWISE:
+ pr_cont(" min=%ux%u, max=%ux%u, step=%ux%u\n",
+ p->stepwise.min_width, p->stepwise.min_height,
+ p->stepwise.step_width, p->stepwise.step_height,
+ p->stepwise.max_width, p->stepwise.max_height);
+ break;
+ case V4L2_FRMSIZE_TYPE_CONTINUOUS:
+ /* fall through */
+ default:
+ pr_cont("\n");
+ break;
+ }
+}
+
+static void v4l_print_frmivalenum(const void *arg, bool write_only)
+{
+ const struct v4l2_frmivalenum *p = arg;
+
+ pr_cont("index=%u, pixelformat=%c%c%c%c, wxh=%ux%u, type=%u",
+ p->index,
+ (p->pixel_format & 0xff),
+ (p->pixel_format >> 8) & 0xff,
+ (p->pixel_format >> 16) & 0xff,
+ (p->pixel_format >> 24) & 0xff,
+ p->width, p->height, p->type);
+ switch (p->type) {
+ case V4L2_FRMIVAL_TYPE_DISCRETE:
+ pr_cont(" fps=%d/%d\n",
+ p->discrete.numerator,
+ p->discrete.denominator);
+ break;
+ case V4L2_FRMIVAL_TYPE_STEPWISE:
+ pr_cont(" min=%d/%d, max=%d/%d, step=%d/%d\n",
+ p->stepwise.min.numerator,
+ p->stepwise.min.denominator,
+ p->stepwise.max.numerator,
+ p->stepwise.max.denominator,
+ p->stepwise.step.numerator,
+ p->stepwise.step.denominator);
+ break;
+ case V4L2_FRMIVAL_TYPE_CONTINUOUS:
+ /* fall through */
+ default:
+ pr_cont("\n");
+ break;
+ }
+}
+
+static void v4l_print_event(const void *arg, bool write_only)
+{
+ const struct v4l2_event *p = arg;
+ const struct v4l2_event_ctrl *c;
+
+ pr_cont("type=0x%x, pending=%u, sequence=%u, id=%u, "
+ "timestamp=%lu.%9.9lu\n",
+ p->type, p->pending, p->sequence, p->id,
+ p->timestamp.tv_sec, p->timestamp.tv_nsec);
+ switch (p->type) {
+ case V4L2_EVENT_VSYNC:
+ printk(KERN_DEBUG "field=%s\n",
+ prt_names(p->u.vsync.field, v4l2_field_names));
+ break;
+ case V4L2_EVENT_CTRL:
+ c = &p->u.ctrl;
+ printk(KERN_DEBUG "changes=0x%x, type=%u, ",
+ c->changes, c->type);
+ if (c->type == V4L2_CTRL_TYPE_INTEGER64)
+ pr_cont("value64=%lld, ", c->value64);
+ else
+ pr_cont("value=%d, ", c->value);
+ pr_cont("flags=0x%x, minimum=%d, maximum=%d, step=%d,"
+ " default_value=%d\n",
+ c->flags, c->minimum, c->maximum,
+ c->step, c->default_value);
+ break;
+ case V4L2_EVENT_FRAME_SYNC:
+ pr_cont("frame_sequence=%u\n",
+ p->u.frame_sync.frame_sequence);
+ break;
+ }
+}
+
+static void v4l_print_event_subscription(const void *arg, bool write_only)
+{
+ const struct v4l2_event_subscription *p = arg;
+
+ pr_cont("type=0x%x, id=0x%x, flags=0x%x\n",
+ p->type, p->id, p->flags);
+}
+
+static void v4l_print_sliced_vbi_cap(const void *arg, bool write_only)
+{
+ const struct v4l2_sliced_vbi_cap *p = arg;
+ int i;
+
+ pr_cont("type=%s, service_set=0x%08x\n",
+ prt_names(p->type, v4l2_type_names), p->service_set);
+ for (i = 0; i < 24; i++)
+ printk(KERN_DEBUG "line[%02u]=0x%04x, 0x%04x\n", i,
+ p->service_lines[0][i],
+ p->service_lines[1][i]);
+}
+
+static void v4l_print_freq_band(const void *arg, bool write_only)
+{
+ const struct v4l2_frequency_band *p = arg;
+
+ pr_cont("tuner=%u, type=%u, index=%u, capability=0x%x, "
+ "rangelow=%u, rangehigh=%u, modulation=0x%x\n",
+ p->tuner, p->type, p->index,
+ p->capability, p->rangelow,
+ p->rangehigh, p->modulation);
+}
+
+static void v4l_print_u32(const void *arg, bool write_only)
+{
+ pr_cont("value=%u\n", *(const u32 *)arg);
+}
+
+static void v4l_print_newline(const void *arg, bool write_only)
+{
+ pr_cont("\n");
+}
+
+static void v4l_print_default(const void *arg, bool write_only)
+{
+ pr_cont("driver-specific ioctl\n");
+}
+
+static int check_ext_ctrls(struct v4l2_ext_controls *c, int allow_priv)
+{
+ __u32 i;
+
+ /* zero the reserved fields */
+ c->reserved[0] = c->reserved[1] = 0;
+ for (i = 0; i < c->count; i++)
+ c->controls[i].reserved2[0] = 0;
+
+ /* V4L2_CID_PRIVATE_BASE cannot be used as control class
+ when using extended controls.
+ Only when passed in through VIDIOC_G_CTRL and VIDIOC_S_CTRL
+ is it allowed for backwards compatibility.
+ */
+ if (!allow_priv && c->ctrl_class == V4L2_CID_PRIVATE_BASE)
+ return 0;
+ /* Check that all controls are from the same control class. */
+ for (i = 0; i < c->count; i++) {
+ if (V4L2_CTRL_ID2CLASS(c->controls[i].id) != c->ctrl_class) {
+ c->error_idx = i;
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int check_fmt(const struct v4l2_ioctl_ops *ops, enum v4l2_buf_type type)
+{
+ if (ops == NULL)
+ return -EINVAL;
+
+ switch (type) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ if (ops->vidioc_g_fmt_vid_cap ||
+ ops->vidioc_g_fmt_vid_cap_mplane)
+ return 0;
+ break;
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ if (ops->vidioc_g_fmt_vid_cap_mplane)
+ return 0;
+ break;
+ case V4L2_BUF_TYPE_VIDEO_OVERLAY:
+ if (ops->vidioc_g_fmt_vid_overlay)
+ return 0;
+ break;
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+ if (ops->vidioc_g_fmt_vid_out ||
+ ops->vidioc_g_fmt_vid_out_mplane)
+ return 0;
+ break;
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ if (ops->vidioc_g_fmt_vid_out_mplane)
+ return 0;
+ break;
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY:
+ if (ops->vidioc_g_fmt_vid_out_overlay)
+ return 0;
+ break;
+ case V4L2_BUF_TYPE_VBI_CAPTURE:
+ if (ops->vidioc_g_fmt_vbi_cap)
+ return 0;
+ break;
+ case V4L2_BUF_TYPE_VBI_OUTPUT:
+ if (ops->vidioc_g_fmt_vbi_out)
+ return 0;
+ break;
+ case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
+ if (ops->vidioc_g_fmt_sliced_vbi_cap)
+ return 0;
+ break;
+ case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT:
+ if (ops->vidioc_g_fmt_sliced_vbi_out)
+ return 0;
+ break;
+ case V4L2_BUF_TYPE_PRIVATE:
+ if (ops->vidioc_g_fmt_type_private)
+ return 0;
+ break;
+ }
+ return -EINVAL;
+}
+
+static int v4l_querycap(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_capability *cap = (struct v4l2_capability *)arg;
+
+ cap->version = LINUX_VERSION_CODE;
+ return ops->vidioc_querycap(file, fh, cap);
+}
+
+static int v4l_s_input(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ return ops->vidioc_s_input(file, fh, *(unsigned int *)arg);
+}
+
+static int v4l_s_output(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ return ops->vidioc_s_output(file, fh, *(unsigned int *)arg);
+}
+
+static int v4l_g_priority(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd;
+ u32 *p = arg;
+
+ if (ops->vidioc_g_priority)
+ return ops->vidioc_g_priority(file, fh, arg);
+ vfd = video_devdata(file);
+ *p = v4l2_prio_max(&vfd->v4l2_dev->prio);
+ return 0;
+}
+
+static int v4l_s_priority(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd;
+ struct v4l2_fh *vfh;
+ u32 *p = arg;
+
+ if (ops->vidioc_s_priority)
+ return ops->vidioc_s_priority(file, fh, *p);
+ vfd = video_devdata(file);
+ vfh = file->private_data;
+ return v4l2_prio_change(&vfd->v4l2_dev->prio, &vfh->prio, *p);
+}
+
+static int v4l_enuminput(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_input *p = arg;
+
+ /*
+ * We set the flags for CAP_PRESETS, CAP_CUSTOM_TIMINGS &
+ * CAP_STD here based on ioctl handler provided by the
+ * driver. If the driver doesn't support these
+ * for a specific input, it must override these flags.
+ */
+ if (ops->vidioc_s_std)
+ p->capabilities |= V4L2_IN_CAP_STD;
+ if (ops->vidioc_s_dv_preset)
+ p->capabilities |= V4L2_IN_CAP_PRESETS;
+ if (ops->vidioc_s_dv_timings)
+ p->capabilities |= V4L2_IN_CAP_CUSTOM_TIMINGS;
+
+ return ops->vidioc_enum_input(file, fh, p);
+}
+
+static int v4l_enumoutput(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_output *p = arg;
+
+ /*
+ * We set the flags for CAP_PRESETS, CAP_CUSTOM_TIMINGS &
+ * CAP_STD here based on ioctl handler provided by the
+ * driver. If the driver doesn't support these
+ * for a specific output, it must override these flags.
+ */
+ if (ops->vidioc_s_std)
+ p->capabilities |= V4L2_OUT_CAP_STD;
+ if (ops->vidioc_s_dv_preset)
+ p->capabilities |= V4L2_OUT_CAP_PRESETS;
+ if (ops->vidioc_s_dv_timings)
+ p->capabilities |= V4L2_OUT_CAP_CUSTOM_TIMINGS;
+
+ return ops->vidioc_enum_output(file, fh, p);
+}
+
+static int v4l_enum_fmt(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_fmtdesc *p = arg;
+
+ switch (p->type) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ if (unlikely(!ops->vidioc_enum_fmt_vid_cap))
+ break;
+ return ops->vidioc_enum_fmt_vid_cap(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ if (unlikely(!ops->vidioc_enum_fmt_vid_cap_mplane))
+ break;
+ return ops->vidioc_enum_fmt_vid_cap_mplane(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_OVERLAY:
+ if (unlikely(!ops->vidioc_enum_fmt_vid_overlay))
+ break;
+ return ops->vidioc_enum_fmt_vid_overlay(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+ if (unlikely(!ops->vidioc_enum_fmt_vid_out))
+ break;
+ return ops->vidioc_enum_fmt_vid_out(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ if (unlikely(!ops->vidioc_enum_fmt_vid_out_mplane))
+ break;
+ return ops->vidioc_enum_fmt_vid_out_mplane(file, fh, arg);
+ case V4L2_BUF_TYPE_PRIVATE:
+ if (unlikely(!ops->vidioc_enum_fmt_type_private))
+ break;
+ return ops->vidioc_enum_fmt_type_private(file, fh, arg);
+ }
+ return -EINVAL;
+}
+
+static int v4l_g_fmt(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_format *p = arg;
+
+ switch (p->type) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ if (unlikely(!ops->vidioc_g_fmt_vid_cap))
+ break;
+ return ops->vidioc_g_fmt_vid_cap(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ if (unlikely(!ops->vidioc_g_fmt_vid_cap_mplane))
+ break;
+ return ops->vidioc_g_fmt_vid_cap_mplane(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_OVERLAY:
+ if (unlikely(!ops->vidioc_g_fmt_vid_overlay))
+ break;
+ return ops->vidioc_g_fmt_vid_overlay(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+ if (unlikely(!ops->vidioc_g_fmt_vid_out))
+ break;
+ return ops->vidioc_g_fmt_vid_out(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ if (unlikely(!ops->vidioc_g_fmt_vid_out_mplane))
+ break;
+ return ops->vidioc_g_fmt_vid_out_mplane(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY:
+ if (unlikely(!ops->vidioc_g_fmt_vid_out_overlay))
+ break;
+ return ops->vidioc_g_fmt_vid_out_overlay(file, fh, arg);
+ case V4L2_BUF_TYPE_VBI_CAPTURE:
+ if (unlikely(!ops->vidioc_g_fmt_vbi_cap))
+ break;
+ return ops->vidioc_g_fmt_vbi_cap(file, fh, arg);
+ case V4L2_BUF_TYPE_VBI_OUTPUT:
+ if (unlikely(!ops->vidioc_g_fmt_vbi_out))
+ break;
+ return ops->vidioc_g_fmt_vbi_out(file, fh, arg);
+ case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
+ if (unlikely(!ops->vidioc_g_fmt_sliced_vbi_cap))
+ break;
+ return ops->vidioc_g_fmt_sliced_vbi_cap(file, fh, arg);
+ case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT:
+ if (unlikely(!ops->vidioc_g_fmt_sliced_vbi_out))
+ break;
+ return ops->vidioc_g_fmt_sliced_vbi_out(file, fh, arg);
+ case V4L2_BUF_TYPE_PRIVATE:
+ if (unlikely(!ops->vidioc_g_fmt_type_private))
+ break;
+ return ops->vidioc_g_fmt_type_private(file, fh, arg);
+ }
+ return -EINVAL;
+}
+
+static int v4l_s_fmt(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_format *p = arg;
+
+ switch (p->type) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ if (unlikely(!ops->vidioc_s_fmt_vid_cap))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.pix);
+ return ops->vidioc_s_fmt_vid_cap(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ if (unlikely(!ops->vidioc_s_fmt_vid_cap_mplane))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.pix_mp);
+ return ops->vidioc_s_fmt_vid_cap_mplane(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_OVERLAY:
+ if (unlikely(!ops->vidioc_s_fmt_vid_overlay))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.win);
+ return ops->vidioc_s_fmt_vid_overlay(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+ if (unlikely(!ops->vidioc_s_fmt_vid_out))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.pix);
+ return ops->vidioc_s_fmt_vid_out(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ if (unlikely(!ops->vidioc_s_fmt_vid_out_mplane))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.pix_mp);
+ return ops->vidioc_s_fmt_vid_out_mplane(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY:
+ if (unlikely(!ops->vidioc_s_fmt_vid_out_overlay))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.win);
+ return ops->vidioc_s_fmt_vid_out_overlay(file, fh, arg);
+ case V4L2_BUF_TYPE_VBI_CAPTURE:
+ if (unlikely(!ops->vidioc_s_fmt_vbi_cap))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.vbi);
+ return ops->vidioc_s_fmt_vbi_cap(file, fh, arg);
+ case V4L2_BUF_TYPE_VBI_OUTPUT:
+ if (unlikely(!ops->vidioc_s_fmt_vbi_out))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.vbi);
+ return ops->vidioc_s_fmt_vbi_out(file, fh, arg);
+ case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
+ if (unlikely(!ops->vidioc_s_fmt_sliced_vbi_cap))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.sliced);
+ return ops->vidioc_s_fmt_sliced_vbi_cap(file, fh, arg);
+ case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT:
+ if (unlikely(!ops->vidioc_s_fmt_sliced_vbi_out))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.sliced);
+ return ops->vidioc_s_fmt_sliced_vbi_out(file, fh, arg);
+ case V4L2_BUF_TYPE_PRIVATE:
+ if (unlikely(!ops->vidioc_s_fmt_type_private))
+ break;
+ return ops->vidioc_s_fmt_type_private(file, fh, arg);
+ }
+ return -EINVAL;
+}
+
+static int v4l_try_fmt(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_format *p = arg;
+
+ switch (p->type) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ if (unlikely(!ops->vidioc_try_fmt_vid_cap))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.pix);
+ return ops->vidioc_try_fmt_vid_cap(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ if (unlikely(!ops->vidioc_try_fmt_vid_cap_mplane))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.pix_mp);
+ return ops->vidioc_try_fmt_vid_cap_mplane(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_OVERLAY:
+ if (unlikely(!ops->vidioc_try_fmt_vid_overlay))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.win);
+ return ops->vidioc_try_fmt_vid_overlay(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+ if (unlikely(!ops->vidioc_try_fmt_vid_out))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.pix);
+ return ops->vidioc_try_fmt_vid_out(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ if (unlikely(!ops->vidioc_try_fmt_vid_out_mplane))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.pix_mp);
+ return ops->vidioc_try_fmt_vid_out_mplane(file, fh, arg);
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY:
+ if (unlikely(!ops->vidioc_try_fmt_vid_out_overlay))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.win);
+ return ops->vidioc_try_fmt_vid_out_overlay(file, fh, arg);
+ case V4L2_BUF_TYPE_VBI_CAPTURE:
+ if (unlikely(!ops->vidioc_try_fmt_vbi_cap))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.vbi);
+ return ops->vidioc_try_fmt_vbi_cap(file, fh, arg);
+ case V4L2_BUF_TYPE_VBI_OUTPUT:
+ if (unlikely(!ops->vidioc_try_fmt_vbi_out))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.vbi);
+ return ops->vidioc_try_fmt_vbi_out(file, fh, arg);
+ case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
+ if (unlikely(!ops->vidioc_try_fmt_sliced_vbi_cap))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.sliced);
+ return ops->vidioc_try_fmt_sliced_vbi_cap(file, fh, arg);
+ case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT:
+ if (unlikely(!ops->vidioc_try_fmt_sliced_vbi_out))
+ break;
+ CLEAR_AFTER_FIELD(p, fmt.sliced);
+ return ops->vidioc_try_fmt_sliced_vbi_out(file, fh, arg);
+ case V4L2_BUF_TYPE_PRIVATE:
+ if (unlikely(!ops->vidioc_try_fmt_type_private))
+ break;
+ return ops->vidioc_try_fmt_type_private(file, fh, arg);
+ }
+ return -EINVAL;
+}
+
+static int v4l_streamon(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ return ops->vidioc_streamon(file, fh, *(unsigned int *)arg);
+}
+
+static int v4l_streamoff(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ return ops->vidioc_streamoff(file, fh, *(unsigned int *)arg);
+}
+
+static int v4l_g_tuner(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ struct v4l2_tuner *p = arg;
+ int err;
+
+ p->type = (vfd->vfl_type == VFL_TYPE_RADIO) ?
+ V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
+ err = ops->vidioc_g_tuner(file, fh, p);
+ if (!err)
+ p->capability |= V4L2_TUNER_CAP_FREQ_BANDS;
+ return err;
+}
+
+static int v4l_s_tuner(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ struct v4l2_tuner *p = arg;
+
+ p->type = (vfd->vfl_type == VFL_TYPE_RADIO) ?
+ V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
+ return ops->vidioc_s_tuner(file, fh, p);
+}
+
+static int v4l_g_modulator(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_modulator *p = arg;
+ int err;
+
+ err = ops->vidioc_g_modulator(file, fh, p);
+ if (!err)
+ p->capability |= V4L2_TUNER_CAP_FREQ_BANDS;
+ return err;
+}
+
+static int v4l_g_frequency(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ struct v4l2_frequency *p = arg;
+
+ p->type = (vfd->vfl_type == VFL_TYPE_RADIO) ?
+ V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
+ return ops->vidioc_g_frequency(file, fh, p);
+}
+
+static int v4l_s_frequency(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ struct v4l2_frequency *p = arg;
+ enum v4l2_tuner_type type;
+
+ type = (vfd->vfl_type == VFL_TYPE_RADIO) ?
+ V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
+ if (p->type != type)
+ return -EINVAL;
+ return ops->vidioc_s_frequency(file, fh, p);
+}
+
+static int v4l_enumstd(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ struct v4l2_standard *p = arg;
+ v4l2_std_id id = vfd->tvnorms, curr_id = 0;
+ unsigned int index = p->index, i, j = 0;
+ const char *descr = "";
+
+ /* Return norm array in a canonical way */
+ for (i = 0; i <= index && id; i++) {
+ /* last std value in the standards array is 0, so this
+ while always ends there since (id & 0) == 0. */
+ while ((id & standards[j].std) != standards[j].std)
+ j++;
+ curr_id = standards[j].std;
+ descr = standards[j].descr;
+ j++;
+ if (curr_id == 0)
+ break;
+ if (curr_id != V4L2_STD_PAL &&
+ curr_id != V4L2_STD_SECAM &&
+ curr_id != V4L2_STD_NTSC)
+ id &= ~curr_id;
+ }
+ if (i <= index)
+ return -EINVAL;
+
+ v4l2_video_std_construct(p, curr_id, descr);
+ return 0;
+}
+
+static int v4l_g_std(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ v4l2_std_id *id = arg;
+
+ /* Calls the specific handler */
+ if (ops->vidioc_g_std)
+ return ops->vidioc_g_std(file, fh, arg);
+ if (vfd->current_norm) {
+ *id = vfd->current_norm;
+ return 0;
+ }
+ return -ENOTTY;
+}
+
+static int v4l_s_std(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ v4l2_std_id *id = arg, norm;
+ int ret;
+
+ norm = (*id) & vfd->tvnorms;
+ if (vfd->tvnorms && !norm) /* Check if std is supported */
+ return -EINVAL;
+
+ /* Calls the specific handler */
+ ret = ops->vidioc_s_std(file, fh, &norm);
+
+ /* Updates standard information */
+ if (ret >= 0)
+ vfd->current_norm = norm;
+ return ret;
+}
+
+static int v4l_querystd(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ v4l2_std_id *p = arg;
+
+ /*
+ * If nothing detected, it should return all supported
+ * standard.
+ * Drivers just need to mask the std argument, in order
+ * to remove the standards that don't apply from the mask.
+ * This means that tuners, audio and video decoders can join
+ * their efforts to improve the standards detection.
+ */
+ *p = vfd->tvnorms;
+ return ops->vidioc_querystd(file, fh, arg);
+}
+
+static int v4l_s_hw_freq_seek(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ struct v4l2_hw_freq_seek *p = arg;
+ enum v4l2_tuner_type type;
+
+ type = (vfd->vfl_type == VFL_TYPE_RADIO) ?
+ V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
+ if (p->type != type)
+ return -EINVAL;
+ return ops->vidioc_s_hw_freq_seek(file, fh, p);
+}
+
+static int v4l_reqbufs(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_requestbuffers *p = arg;
+ int ret = check_fmt(ops, p->type);
+
+ if (ret)
+ return ret;
+
+ if (p->type < V4L2_BUF_TYPE_PRIVATE)
+ CLEAR_AFTER_FIELD(p, memory);
+
+ return ops->vidioc_reqbufs(file, fh, p);
+}
+
+static int v4l_querybuf(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_buffer *p = arg;
+ int ret = check_fmt(ops, p->type);
+
+ return ret ? ret : ops->vidioc_querybuf(file, fh, p);
+}
+
+static int v4l_qbuf(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_buffer *p = arg;
+ int ret = check_fmt(ops, p->type);
+
+ return ret ? ret : ops->vidioc_qbuf(file, fh, p);
+}
+
+static int v4l_dqbuf(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_buffer *p = arg;
+ int ret = check_fmt(ops, p->type);
+
+ return ret ? ret : ops->vidioc_dqbuf(file, fh, p);
+}
+
+static int v4l_create_bufs(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_create_buffers *create = arg;
+ int ret = check_fmt(ops, create->format.type);
+
+ return ret ? ret : ops->vidioc_create_bufs(file, fh, create);
+}
+
+static int v4l_prepare_buf(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_buffer *b = arg;
+ int ret = check_fmt(ops, b->type);
+
+ return ret ? ret : ops->vidioc_prepare_buf(file, fh, b);
+}
+
+static int v4l_g_parm(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ struct v4l2_streamparm *p = arg;
+ v4l2_std_id std;
+ int ret = check_fmt(ops, p->type);
+
+ if (ret)
+ return ret;
+ if (ops->vidioc_g_parm)
+ return ops->vidioc_g_parm(file, fh, p);
+ std = vfd->current_norm;
+ if (p->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+ p->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return -EINVAL;
+ p->parm.capture.readbuffers = 2;
+ if (ops->vidioc_g_std)
+ ret = ops->vidioc_g_std(file, fh, &std);
+ if (ret == 0)
+ v4l2_video_std_frame_period(std,
+ &p->parm.capture.timeperframe);
+ return ret;
+}
+
+static int v4l_s_parm(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_streamparm *p = arg;
+ int ret = check_fmt(ops, p->type);
+
+ return ret ? ret : ops->vidioc_s_parm(file, fh, p);
+}
+
+static int v4l_queryctrl(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ struct v4l2_queryctrl *p = arg;
+ struct v4l2_fh *vfh =
+ test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags) ? fh : NULL;
+
+ if (vfh && vfh->ctrl_handler)
+ return v4l2_queryctrl(vfh->ctrl_handler, p);
+ if (vfd->ctrl_handler)
+ return v4l2_queryctrl(vfd->ctrl_handler, p);
+ if (ops->vidioc_queryctrl)
+ return ops->vidioc_queryctrl(file, fh, p);
+ return -ENOTTY;
+}
+
+static int v4l_querymenu(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ struct v4l2_querymenu *p = arg;
+ struct v4l2_fh *vfh =
+ test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags) ? fh : NULL;
+
+ if (vfh && vfh->ctrl_handler)
+ return v4l2_querymenu(vfh->ctrl_handler, p);
+ if (vfd->ctrl_handler)
+ return v4l2_querymenu(vfd->ctrl_handler, p);
+ if (ops->vidioc_querymenu)
+ return ops->vidioc_querymenu(file, fh, p);
+ return -ENOTTY;
+}
+
+static int v4l_g_ctrl(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ struct v4l2_control *p = arg;
+ struct v4l2_fh *vfh =
+ test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags) ? fh : NULL;
+ struct v4l2_ext_controls ctrls;
+ struct v4l2_ext_control ctrl;
+
+ if (vfh && vfh->ctrl_handler)
+ return v4l2_g_ctrl(vfh->ctrl_handler, p);
+ if (vfd->ctrl_handler)
+ return v4l2_g_ctrl(vfd->ctrl_handler, p);
+ if (ops->vidioc_g_ctrl)
+ return ops->vidioc_g_ctrl(file, fh, p);
+ if (ops->vidioc_g_ext_ctrls == NULL)
+ return -ENOTTY;
+
+ ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(p->id);
+ ctrls.count = 1;
+ ctrls.controls = &ctrl;
+ ctrl.id = p->id;
+ ctrl.value = p->value;
+ if (check_ext_ctrls(&ctrls, 1)) {
+ int ret = ops->vidioc_g_ext_ctrls(file, fh, &ctrls);
+
+ if (ret == 0)
+ p->value = ctrl.value;
+ return ret;
+ }
+ return -EINVAL;
+}
+
+static int v4l_s_ctrl(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ struct v4l2_control *p = arg;
+ struct v4l2_fh *vfh =
+ test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags) ? fh : NULL;
+ struct v4l2_ext_controls ctrls;
+ struct v4l2_ext_control ctrl;
+
+ if (vfh && vfh->ctrl_handler)
+ return v4l2_s_ctrl(vfh, vfh->ctrl_handler, p);
+ if (vfd->ctrl_handler)
+ return v4l2_s_ctrl(NULL, vfd->ctrl_handler, p);
+ if (ops->vidioc_s_ctrl)
+ return ops->vidioc_s_ctrl(file, fh, p);
+ if (ops->vidioc_s_ext_ctrls == NULL)
+ return -ENOTTY;
+
+ ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(p->id);
+ ctrls.count = 1;
+ ctrls.controls = &ctrl;
+ ctrl.id = p->id;
+ ctrl.value = p->value;
+ if (check_ext_ctrls(&ctrls, 1))
+ return ops->vidioc_s_ext_ctrls(file, fh, &ctrls);
+ return -EINVAL;
+}
+
+static int v4l_g_ext_ctrls(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ struct v4l2_ext_controls *p = arg;
+ struct v4l2_fh *vfh =
+ test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags) ? fh : NULL;
+
+ p->error_idx = p->count;
+ if (vfh && vfh->ctrl_handler)
+ return v4l2_g_ext_ctrls(vfh->ctrl_handler, p);
+ if (vfd->ctrl_handler)
+ return v4l2_g_ext_ctrls(vfd->ctrl_handler, p);
+ if (ops->vidioc_g_ext_ctrls == NULL)
+ return -ENOTTY;
+ return check_ext_ctrls(p, 0) ? ops->vidioc_g_ext_ctrls(file, fh, p) :
+ -EINVAL;
+}
+
+static int v4l_s_ext_ctrls(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ struct v4l2_ext_controls *p = arg;
+ struct v4l2_fh *vfh =
+ test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags) ? fh : NULL;
+
+ p->error_idx = p->count;
+ if (vfh && vfh->ctrl_handler)
+ return v4l2_s_ext_ctrls(vfh, vfh->ctrl_handler, p);
+ if (vfd->ctrl_handler)
+ return v4l2_s_ext_ctrls(NULL, vfd->ctrl_handler, p);
+ if (ops->vidioc_s_ext_ctrls == NULL)
+ return -ENOTTY;
+ return check_ext_ctrls(p, 0) ? ops->vidioc_s_ext_ctrls(file, fh, p) :
+ -EINVAL;
+}
+
+static int v4l_try_ext_ctrls(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ struct v4l2_ext_controls *p = arg;
+ struct v4l2_fh *vfh =
+ test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags) ? fh : NULL;
+
+ p->error_idx = p->count;
+ if (vfh && vfh->ctrl_handler)
+ return v4l2_try_ext_ctrls(vfh->ctrl_handler, p);
+ if (vfd->ctrl_handler)
+ return v4l2_try_ext_ctrls(vfd->ctrl_handler, p);
+ if (ops->vidioc_try_ext_ctrls == NULL)
+ return -ENOTTY;
+ return check_ext_ctrls(p, 0) ? ops->vidioc_try_ext_ctrls(file, fh, p) :
+ -EINVAL;
+}
+
+static int v4l_g_crop(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_crop *p = arg;
+ struct v4l2_selection s = {
+ .type = p->type,
+ };
+ int ret;
+
+ if (ops->vidioc_g_crop)
+ return ops->vidioc_g_crop(file, fh, p);
+ /* simulate capture crop using selection api */
+
+ /* crop means compose for output devices */
+ if (V4L2_TYPE_IS_OUTPUT(p->type))
+ s.target = V4L2_SEL_TGT_COMPOSE_ACTIVE;
+ else
+ s.target = V4L2_SEL_TGT_CROP_ACTIVE;
+
+ ret = ops->vidioc_g_selection(file, fh, &s);
+
+ /* copying results to old structure on success */
+ if (!ret)
+ p->c = s.r;
+ return ret;
+}
+
+static int v4l_s_crop(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_crop *p = arg;
+ struct v4l2_selection s = {
+ .type = p->type,
+ .r = p->c,
+ };
+
+ if (ops->vidioc_s_crop)
+ return ops->vidioc_s_crop(file, fh, p);
+ /* simulate capture crop using selection api */
+
+ /* crop means compose for output devices */
+ if (V4L2_TYPE_IS_OUTPUT(p->type))
+ s.target = V4L2_SEL_TGT_COMPOSE_ACTIVE;
+ else
+ s.target = V4L2_SEL_TGT_CROP_ACTIVE;
+
+ return ops->vidioc_s_selection(file, fh, &s);
+}
+
+static int v4l_cropcap(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_cropcap *p = arg;
+ struct v4l2_selection s = { .type = p->type };
+ int ret;
+
+ if (ops->vidioc_cropcap)
+ return ops->vidioc_cropcap(file, fh, p);
+
+ /* obtaining bounds */
+ if (V4L2_TYPE_IS_OUTPUT(p->type))
+ s.target = V4L2_SEL_TGT_COMPOSE_BOUNDS;
+ else
+ s.target = V4L2_SEL_TGT_CROP_BOUNDS;
+
+ ret = ops->vidioc_g_selection(file, fh, &s);
+ if (ret)
+ return ret;
+ p->bounds = s.r;
+
+ /* obtaining defrect */
+ if (V4L2_TYPE_IS_OUTPUT(p->type))
+ s.target = V4L2_SEL_TGT_COMPOSE_DEFAULT;
+ else
+ s.target = V4L2_SEL_TGT_CROP_DEFAULT;
+
+ ret = ops->vidioc_g_selection(file, fh, &s);
+ if (ret)
+ return ret;
+ p->defrect = s.r;
+
+ /* setting trivial pixelaspect */
+ p->pixelaspect.numerator = 1;
+ p->pixelaspect.denominator = 1;
+ return 0;
+}
+
+static int v4l_log_status(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ int ret;
+
+ if (vfd->v4l2_dev)
+ pr_info("%s: ================= START STATUS =================\n",
+ vfd->v4l2_dev->name);
+ ret = ops->vidioc_log_status(file, fh);
+ if (vfd->v4l2_dev)
+ pr_info("%s: ================== END STATUS ==================\n",
+ vfd->v4l2_dev->name);
+ return ret;
+}
+
+static int v4l_dbg_g_register(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ struct v4l2_dbg_register *p = arg;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ return ops->vidioc_g_register(file, fh, p);
+#else
+ return -ENOTTY;
+#endif
+}
+
+static int v4l_dbg_s_register(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ struct v4l2_dbg_register *p = arg;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ return ops->vidioc_s_register(file, fh, p);
+#else
+ return -ENOTTY;
+#endif
+}
+
+static int v4l_dbg_g_chip_ident(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_dbg_chip_ident *p = arg;
+
+ p->ident = V4L2_IDENT_NONE;
+ p->revision = 0;
+ return ops->vidioc_g_chip_ident(file, fh, p);
+}
+
+static int v4l_dqevent(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ return v4l2_event_dequeue(fh, arg, file->f_flags & O_NONBLOCK);
+}
+
+static int v4l_subscribe_event(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ return ops->vidioc_subscribe_event(fh, arg);
+}
+
+static int v4l_unsubscribe_event(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ return ops->vidioc_unsubscribe_event(fh, arg);
+}
+
+static int v4l_g_sliced_vbi_cap(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct v4l2_sliced_vbi_cap *p = arg;
+
+ /* Clear up to type, everything after type is zeroed already */
+ memset(p, 0, offsetof(struct v4l2_sliced_vbi_cap, type));
+
+ return ops->vidioc_g_sliced_vbi_cap(file, fh, p);
+}
+
+static int v4l_enum_freq_bands(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ struct v4l2_frequency_band *p = arg;
+ enum v4l2_tuner_type type;
+ int err;
+
+ type = (vfd->vfl_type == VFL_TYPE_RADIO) ?
+ V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
+
+ if (type != p->type)
+ return -EINVAL;
+ if (ops->vidioc_enum_freq_bands)
+ return ops->vidioc_enum_freq_bands(file, fh, p);
+ if (ops->vidioc_g_tuner) {
+ struct v4l2_tuner t = {
+ .index = p->tuner,
+ .type = type,
+ };
+
++ if (p->index)
++ return -EINVAL;
+ err = ops->vidioc_g_tuner(file, fh, &t);
+ if (err)
+ return err;
+ p->capability = t.capability | V4L2_TUNER_CAP_FREQ_BANDS;
+ p->rangelow = t.rangelow;
+ p->rangehigh = t.rangehigh;
+ p->modulation = (type == V4L2_TUNER_RADIO) ?
+ V4L2_BAND_MODULATION_FM : V4L2_BAND_MODULATION_VSB;
+ return 0;
+ }
+ if (ops->vidioc_g_modulator) {
+ struct v4l2_modulator m = {
+ .index = p->tuner,
+ };
+
+ if (type != V4L2_TUNER_RADIO)
+ return -EINVAL;
++ if (p->index)
++ return -EINVAL;
+ err = ops->vidioc_g_modulator(file, fh, &m);
+ if (err)
+ return err;
+ p->capability = m.capability | V4L2_TUNER_CAP_FREQ_BANDS;
+ p->rangelow = m.rangelow;
+ p->rangehigh = m.rangehigh;
+ p->modulation = (type == V4L2_TUNER_RADIO) ?
+ V4L2_BAND_MODULATION_FM : V4L2_BAND_MODULATION_VSB;
+ return 0;
+ }
+ return -ENOTTY;
+}
+
+struct v4l2_ioctl_info {
+ unsigned int ioctl;
+ u32 flags;
+ const char * const name;
+ union {
+ u32 offset;
+ int (*func)(const struct v4l2_ioctl_ops *ops,
+ struct file *file, void *fh, void *p);
+ } u;
+ void (*debug)(const void *arg, bool write_only);
+};
+
+/* This control needs a priority check */
+#define INFO_FL_PRIO (1 << 0)
+/* This control can be valid if the filehandle passes a control handler. */
+#define INFO_FL_CTRL (1 << 1)
+/* This is a standard ioctl, no need for special code */
+#define INFO_FL_STD (1 << 2)
+/* This is ioctl has its own function */
+#define INFO_FL_FUNC (1 << 3)
+/* Queuing ioctl */
+#define INFO_FL_QUEUE (1 << 4)
+/* Zero struct from after the field to the end */
+#define INFO_FL_CLEAR(v4l2_struct, field) \
+ ((offsetof(struct v4l2_struct, field) + \
+ sizeof(((struct v4l2_struct *)0)->field)) << 16)
+#define INFO_FL_CLEAR_MASK (_IOC_SIZEMASK << 16)
+
+#define IOCTL_INFO_STD(_ioctl, _vidioc, _debug, _flags) \
+ [_IOC_NR(_ioctl)] = { \
+ .ioctl = _ioctl, \
+ .flags = _flags | INFO_FL_STD, \
+ .name = #_ioctl, \
+ .u.offset = offsetof(struct v4l2_ioctl_ops, _vidioc), \
+ .debug = _debug, \
+ }
+
+#define IOCTL_INFO_FNC(_ioctl, _func, _debug, _flags) \
+ [_IOC_NR(_ioctl)] = { \
+ .ioctl = _ioctl, \
+ .flags = _flags | INFO_FL_FUNC, \
+ .name = #_ioctl, \
+ .u.func = _func, \
+ .debug = _debug, \
+ }
+
+static struct v4l2_ioctl_info v4l2_ioctls[] = {
+ IOCTL_INFO_FNC(VIDIOC_QUERYCAP, v4l_querycap, v4l_print_querycap, 0),
+ IOCTL_INFO_FNC(VIDIOC_ENUM_FMT, v4l_enum_fmt, v4l_print_fmtdesc, INFO_FL_CLEAR(v4l2_fmtdesc, type)),
+ IOCTL_INFO_FNC(VIDIOC_G_FMT, v4l_g_fmt, v4l_print_format, INFO_FL_CLEAR(v4l2_format, type)),
+ IOCTL_INFO_FNC(VIDIOC_S_FMT, v4l_s_fmt, v4l_print_format, INFO_FL_PRIO),
+ IOCTL_INFO_FNC(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE),
+ IOCTL_INFO_FNC(VIDIOC_QUERYBUF, v4l_querybuf, v4l_print_buffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_buffer, length)),
+ IOCTL_INFO_STD(VIDIOC_G_FBUF, vidioc_g_fbuf, v4l_print_framebuffer, 0),
+ IOCTL_INFO_STD(VIDIOC_S_FBUF, vidioc_s_fbuf, v4l_print_framebuffer, INFO_FL_PRIO),
+ IOCTL_INFO_STD(VIDIOC_OVERLAY, vidioc_overlay, v4l_print_u32, INFO_FL_PRIO),
+ IOCTL_INFO_FNC(VIDIOC_QBUF, v4l_qbuf, v4l_print_buffer, INFO_FL_QUEUE),
+ IOCTL_INFO_FNC(VIDIOC_DQBUF, v4l_dqbuf, v4l_print_buffer, INFO_FL_QUEUE),
+ IOCTL_INFO_FNC(VIDIOC_STREAMON, v4l_streamon, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE),
+ IOCTL_INFO_FNC(VIDIOC_STREAMOFF, v4l_streamoff, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE),
+ IOCTL_INFO_FNC(VIDIOC_G_PARM, v4l_g_parm, v4l_print_streamparm, INFO_FL_CLEAR(v4l2_streamparm, type)),
+ IOCTL_INFO_FNC(VIDIOC_S_PARM, v4l_s_parm, v4l_print_streamparm, INFO_FL_PRIO),
+ IOCTL_INFO_FNC(VIDIOC_G_STD, v4l_g_std, v4l_print_std, 0),
+ IOCTL_INFO_FNC(VIDIOC_S_STD, v4l_s_std, v4l_print_std, INFO_FL_PRIO),
+ IOCTL_INFO_FNC(VIDIOC_ENUMSTD, v4l_enumstd, v4l_print_standard, INFO_FL_CLEAR(v4l2_standard, index)),
+ IOCTL_INFO_FNC(VIDIOC_ENUMINPUT, v4l_enuminput, v4l_print_enuminput, INFO_FL_CLEAR(v4l2_input, index)),
+ IOCTL_INFO_FNC(VIDIOC_G_CTRL, v4l_g_ctrl, v4l_print_control, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_control, id)),
+ IOCTL_INFO_FNC(VIDIOC_S_CTRL, v4l_s_ctrl, v4l_print_control, INFO_FL_PRIO | INFO_FL_CTRL),
+ IOCTL_INFO_FNC(VIDIOC_G_TUNER, v4l_g_tuner, v4l_print_tuner, INFO_FL_CLEAR(v4l2_tuner, index)),
+ IOCTL_INFO_FNC(VIDIOC_S_TUNER, v4l_s_tuner, v4l_print_tuner, INFO_FL_PRIO),
+ IOCTL_INFO_STD(VIDIOC_G_AUDIO, vidioc_g_audio, v4l_print_audio, 0),
+ IOCTL_INFO_STD(VIDIOC_S_AUDIO, vidioc_s_audio, v4l_print_audio, INFO_FL_PRIO),
+ IOCTL_INFO_FNC(VIDIOC_QUERYCTRL, v4l_queryctrl, v4l_print_queryctrl, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_queryctrl, id)),
+ IOCTL_INFO_FNC(VIDIOC_QUERYMENU, v4l_querymenu, v4l_print_querymenu, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_querymenu, index)),
+ IOCTL_INFO_STD(VIDIOC_G_INPUT, vidioc_g_input, v4l_print_u32, 0),
+ IOCTL_INFO_FNC(VIDIOC_S_INPUT, v4l_s_input, v4l_print_u32, INFO_FL_PRIO),
+ IOCTL_INFO_STD(VIDIOC_G_OUTPUT, vidioc_g_output, v4l_print_u32, 0),
+ IOCTL_INFO_FNC(VIDIOC_S_OUTPUT, v4l_s_output, v4l_print_u32, INFO_FL_PRIO),
+ IOCTL_INFO_FNC(VIDIOC_ENUMOUTPUT, v4l_enumoutput, v4l_print_enumoutput, INFO_FL_CLEAR(v4l2_output, index)),
+ IOCTL_INFO_STD(VIDIOC_G_AUDOUT, vidioc_g_audout, v4l_print_audioout, 0),
+ IOCTL_INFO_STD(VIDIOC_S_AUDOUT, vidioc_s_audout, v4l_print_audioout, INFO_FL_PRIO),
+ IOCTL_INFO_FNC(VIDIOC_G_MODULATOR, v4l_g_modulator, v4l_print_modulator, INFO_FL_CLEAR(v4l2_modulator, index)),
+ IOCTL_INFO_STD(VIDIOC_S_MODULATOR, vidioc_s_modulator, v4l_print_modulator, INFO_FL_PRIO),
+ IOCTL_INFO_FNC(VIDIOC_G_FREQUENCY, v4l_g_frequency, v4l_print_frequency, INFO_FL_CLEAR(v4l2_frequency, tuner)),
+ IOCTL_INFO_FNC(VIDIOC_S_FREQUENCY, v4l_s_frequency, v4l_print_frequency, INFO_FL_PRIO),
+ IOCTL_INFO_FNC(VIDIOC_CROPCAP, v4l_cropcap, v4l_print_cropcap, INFO_FL_CLEAR(v4l2_cropcap, type)),
+ IOCTL_INFO_FNC(VIDIOC_G_CROP, v4l_g_crop, v4l_print_crop, INFO_FL_CLEAR(v4l2_crop, type)),
+ IOCTL_INFO_FNC(VIDIOC_S_CROP, v4l_s_crop, v4l_print_crop, INFO_FL_PRIO),
+ IOCTL_INFO_STD(VIDIOC_G_SELECTION, vidioc_g_selection, v4l_print_selection, 0),
+ IOCTL_INFO_STD(VIDIOC_S_SELECTION, vidioc_s_selection, v4l_print_selection, INFO_FL_PRIO),
+ IOCTL_INFO_STD(VIDIOC_G_JPEGCOMP, vidioc_g_jpegcomp, v4l_print_jpegcompression, 0),
+ IOCTL_INFO_STD(VIDIOC_S_JPEGCOMP, vidioc_s_jpegcomp, v4l_print_jpegcompression, INFO_FL_PRIO),
+ IOCTL_INFO_FNC(VIDIOC_QUERYSTD, v4l_querystd, v4l_print_std, 0),
+ IOCTL_INFO_FNC(VIDIOC_TRY_FMT, v4l_try_fmt, v4l_print_format, 0),
+ IOCTL_INFO_STD(VIDIOC_ENUMAUDIO, vidioc_enumaudio, v4l_print_audio, INFO_FL_CLEAR(v4l2_audio, index)),
+ IOCTL_INFO_STD(VIDIOC_ENUMAUDOUT, vidioc_enumaudout, v4l_print_audioout, INFO_FL_CLEAR(v4l2_audioout, index)),
+ IOCTL_INFO_FNC(VIDIOC_G_PRIORITY, v4l_g_priority, v4l_print_u32, 0),
+ IOCTL_INFO_FNC(VIDIOC_S_PRIORITY, v4l_s_priority, v4l_print_u32, INFO_FL_PRIO),
+ IOCTL_INFO_FNC(VIDIOC_G_SLICED_VBI_CAP, v4l_g_sliced_vbi_cap, v4l_print_sliced_vbi_cap, INFO_FL_CLEAR(v4l2_sliced_vbi_cap, type)),
+ IOCTL_INFO_FNC(VIDIOC_LOG_STATUS, v4l_log_status, v4l_print_newline, 0),
+ IOCTL_INFO_FNC(VIDIOC_G_EXT_CTRLS, v4l_g_ext_ctrls, v4l_print_ext_controls, INFO_FL_CTRL),
+ IOCTL_INFO_FNC(VIDIOC_S_EXT_CTRLS, v4l_s_ext_ctrls, v4l_print_ext_controls, INFO_FL_PRIO | INFO_FL_CTRL),
+ IOCTL_INFO_FNC(VIDIOC_TRY_EXT_CTRLS, v4l_try_ext_ctrls, v4l_print_ext_controls, INFO_FL_CTRL),
+ IOCTL_INFO_STD(VIDIOC_ENUM_FRAMESIZES, vidioc_enum_framesizes, v4l_print_frmsizeenum, INFO_FL_CLEAR(v4l2_frmsizeenum, pixel_format)),
+ IOCTL_INFO_STD(VIDIOC_ENUM_FRAMEINTERVALS, vidioc_enum_frameintervals, v4l_print_frmivalenum, INFO_FL_CLEAR(v4l2_frmivalenum, height)),
+ IOCTL_INFO_STD(VIDIOC_G_ENC_INDEX, vidioc_g_enc_index, v4l_print_enc_idx, 0),
+ IOCTL_INFO_STD(VIDIOC_ENCODER_CMD, vidioc_encoder_cmd, v4l_print_encoder_cmd, INFO_FL_PRIO | INFO_FL_CLEAR(v4l2_encoder_cmd, flags)),
+ IOCTL_INFO_STD(VIDIOC_TRY_ENCODER_CMD, vidioc_try_encoder_cmd, v4l_print_encoder_cmd, INFO_FL_CLEAR(v4l2_encoder_cmd, flags)),
+ IOCTL_INFO_STD(VIDIOC_DECODER_CMD, vidioc_decoder_cmd, v4l_print_decoder_cmd, INFO_FL_PRIO),
+ IOCTL_INFO_STD(VIDIOC_TRY_DECODER_CMD, vidioc_try_decoder_cmd, v4l_print_decoder_cmd, 0),
+ IOCTL_INFO_FNC(VIDIOC_DBG_S_REGISTER, v4l_dbg_s_register, v4l_print_dbg_register, 0),
+ IOCTL_INFO_FNC(VIDIOC_DBG_G_REGISTER, v4l_dbg_g_register, v4l_print_dbg_register, 0),
+ IOCTL_INFO_FNC(VIDIOC_DBG_G_CHIP_IDENT, v4l_dbg_g_chip_ident, v4l_print_dbg_chip_ident, 0),
+ IOCTL_INFO_FNC(VIDIOC_S_HW_FREQ_SEEK, v4l_s_hw_freq_seek, v4l_print_hw_freq_seek, INFO_FL_PRIO),
+ IOCTL_INFO_STD(VIDIOC_ENUM_DV_PRESETS, vidioc_enum_dv_presets, v4l_print_dv_enum_presets, 0),
+ IOCTL_INFO_STD(VIDIOC_S_DV_PRESET, vidioc_s_dv_preset, v4l_print_dv_preset, INFO_FL_PRIO),
+ IOCTL_INFO_STD(VIDIOC_G_DV_PRESET, vidioc_g_dv_preset, v4l_print_dv_preset, 0),
+ IOCTL_INFO_STD(VIDIOC_QUERY_DV_PRESET, vidioc_query_dv_preset, v4l_print_dv_preset, 0),
+ IOCTL_INFO_STD(VIDIOC_S_DV_TIMINGS, vidioc_s_dv_timings, v4l_print_dv_timings, INFO_FL_PRIO),
+ IOCTL_INFO_STD(VIDIOC_G_DV_TIMINGS, vidioc_g_dv_timings, v4l_print_dv_timings, 0),
+ IOCTL_INFO_FNC(VIDIOC_DQEVENT, v4l_dqevent, v4l_print_event, 0),
+ IOCTL_INFO_FNC(VIDIOC_SUBSCRIBE_EVENT, v4l_subscribe_event, v4l_print_event_subscription, 0),
+ IOCTL_INFO_FNC(VIDIOC_UNSUBSCRIBE_EVENT, v4l_unsubscribe_event, v4l_print_event_subscription, 0),
+ IOCTL_INFO_FNC(VIDIOC_CREATE_BUFS, v4l_create_bufs, v4l_print_create_buffers, INFO_FL_PRIO | INFO_FL_QUEUE),
+ IOCTL_INFO_FNC(VIDIOC_PREPARE_BUF, v4l_prepare_buf, v4l_print_buffer, INFO_FL_QUEUE),
+ IOCTL_INFO_STD(VIDIOC_ENUM_DV_TIMINGS, vidioc_enum_dv_timings, v4l_print_enum_dv_timings, 0),
+ IOCTL_INFO_STD(VIDIOC_QUERY_DV_TIMINGS, vidioc_query_dv_timings, v4l_print_dv_timings, 0),
+ IOCTL_INFO_STD(VIDIOC_DV_TIMINGS_CAP, vidioc_dv_timings_cap, v4l_print_dv_timings_cap, INFO_FL_CLEAR(v4l2_dv_timings_cap, type)),
+ IOCTL_INFO_FNC(VIDIOC_ENUM_FREQ_BANDS, v4l_enum_freq_bands, v4l_print_freq_band, 0),
+};
+#define V4L2_IOCTLS ARRAY_SIZE(v4l2_ioctls)
+
+bool v4l2_is_known_ioctl(unsigned int cmd)
+{
+ if (_IOC_NR(cmd) >= V4L2_IOCTLS)
+ return false;
+ return v4l2_ioctls[_IOC_NR(cmd)].ioctl == cmd;
+}
+
+struct mutex *v4l2_ioctl_get_lock(struct video_device *vdev, unsigned cmd)
+{
+ if (_IOC_NR(cmd) >= V4L2_IOCTLS)
+ return vdev->lock;
+ if (test_bit(_IOC_NR(cmd), vdev->disable_locking))
+ return NULL;
+ if (vdev->queue && vdev->queue->lock &&
+ (v4l2_ioctls[_IOC_NR(cmd)].flags & INFO_FL_QUEUE))
+ return vdev->queue->lock;
+ return vdev->lock;
+}
+
+/* Common ioctl debug function. This function can be used by
+ external ioctl messages as well as internal V4L ioctl */
+void v4l_printk_ioctl(const char *prefix, unsigned int cmd)
+{
+ const char *dir, *type;
+
+ if (prefix)
+ printk(KERN_DEBUG "%s: ", prefix);
+
+ switch (_IOC_TYPE(cmd)) {
+ case 'd':
+ type = "v4l2_int";
+ break;
+ case 'V':
+ if (_IOC_NR(cmd) >= V4L2_IOCTLS) {
+ type = "v4l2";
+ break;
+ }
+ pr_cont("%s", v4l2_ioctls[_IOC_NR(cmd)].name);
+ return;
+ default:
+ type = "unknown";
+ break;
+ }
+
+ switch (_IOC_DIR(cmd)) {
+ case _IOC_NONE: dir = "--"; break;
+ case _IOC_READ: dir = "r-"; break;
+ case _IOC_WRITE: dir = "-w"; break;
+ case _IOC_READ | _IOC_WRITE: dir = "rw"; break;
+ default: dir = "*ERR*"; break;
+ }
+ pr_cont("%s ioctl '%c', dir=%s, #%d (0x%08x)",
+ type, _IOC_TYPE(cmd), dir, _IOC_NR(cmd), cmd);
+}
+EXPORT_SYMBOL(v4l_printk_ioctl);
+
+static long __video_do_ioctl(struct file *file,
+ unsigned int cmd, void *arg)
+{
+ struct video_device *vfd = video_devdata(file);
+ const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
+ bool write_only = false;
+ struct v4l2_ioctl_info default_info;
+ const struct v4l2_ioctl_info *info;
+ void *fh = file->private_data;
+ struct v4l2_fh *vfh = NULL;
+ int use_fh_prio = 0;
+ int debug = vfd->debug;
+ long ret = -ENOTTY;
+
+ if (ops == NULL) {
+ pr_warn("%s: has no ioctl_ops.\n",
+ video_device_node_name(vfd));
+ return ret;
+ }
+
+ if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags)) {
+ vfh = file->private_data;
+ use_fh_prio = test_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
+ }
+
+ if (v4l2_is_known_ioctl(cmd)) {
+ info = &v4l2_ioctls[_IOC_NR(cmd)];
+
+ if (!test_bit(_IOC_NR(cmd), vfd->valid_ioctls) &&
+ !((info->flags & INFO_FL_CTRL) && vfh && vfh->ctrl_handler))
+ goto done;
+
+ if (use_fh_prio && (info->flags & INFO_FL_PRIO)) {
+ ret = v4l2_prio_check(vfd->prio, vfh->prio);
+ if (ret)
+ goto done;
+ }
+ } else {
+ default_info.ioctl = cmd;
+ default_info.flags = 0;
+ default_info.debug = v4l_print_default;
+ info = &default_info;
+ }
+
+ write_only = _IOC_DIR(cmd) == _IOC_WRITE;
+ if (write_only && debug > V4L2_DEBUG_IOCTL) {
+ v4l_printk_ioctl(video_device_node_name(vfd), cmd);
+ pr_cont(": ");
+ info->debug(arg, write_only);
+ }
+ if (info->flags & INFO_FL_STD) {
+ typedef int (*vidioc_op)(struct file *file, void *fh, void *p);
+ const void *p = vfd->ioctl_ops;
+ const vidioc_op *vidioc = p + info->u.offset;
+
+ ret = (*vidioc)(file, fh, arg);
+ } else if (info->flags & INFO_FL_FUNC) {
+ ret = info->u.func(ops, file, fh, arg);
+ } else if (!ops->vidioc_default) {
+ ret = -ENOTTY;
+ } else {
+ ret = ops->vidioc_default(file, fh,
+ use_fh_prio ? v4l2_prio_check(vfd->prio, vfh->prio) >= 0 : 0,
+ cmd, arg);
+ }
+
+done:
+ if (debug) {
+ if (write_only && debug > V4L2_DEBUG_IOCTL) {
+ if (ret < 0)
+ printk(KERN_DEBUG "%s: error %ld\n",
+ video_device_node_name(vfd), ret);
+ return ret;
+ }
+ v4l_printk_ioctl(video_device_node_name(vfd), cmd);
+ if (ret < 0)
+ pr_cont(": error %ld\n", ret);
+ else if (debug == V4L2_DEBUG_IOCTL)
+ pr_cont("\n");
+ else if (_IOC_DIR(cmd) == _IOC_NONE)
+ info->debug(arg, write_only);
+ else {
+ pr_cont(": ");
+ info->debug(arg, write_only);
+ }
+ }
+
+ return ret;
+}
+
+static int check_array_args(unsigned int cmd, void *parg, size_t *array_size,
+ void * __user *user_ptr, void ***kernel_ptr)
+{
+ int ret = 0;
+
+ switch (cmd) {
+ case VIDIOC_QUERYBUF:
+ case VIDIOC_QBUF:
+ case VIDIOC_DQBUF: {
+ struct v4l2_buffer *buf = parg;
+
+ if (V4L2_TYPE_IS_MULTIPLANAR(buf->type) && buf->length > 0) {
+ if (buf->length > VIDEO_MAX_PLANES) {
+ ret = -EINVAL;
+ break;
+ }
+ *user_ptr = (void __user *)buf->m.planes;
+ *kernel_ptr = (void *)&buf->m.planes;
+ *array_size = sizeof(struct v4l2_plane) * buf->length;
+ ret = 1;
+ }
+ break;
+ }
+
+ case VIDIOC_S_EXT_CTRLS:
+ case VIDIOC_G_EXT_CTRLS:
+ case VIDIOC_TRY_EXT_CTRLS: {
+ struct v4l2_ext_controls *ctrls = parg;
+
+ if (ctrls->count != 0) {
+ if (ctrls->count > V4L2_CID_MAX_CTRLS) {
+ ret = -EINVAL;
+ break;
+ }
+ *user_ptr = (void __user *)ctrls->controls;
+ *kernel_ptr = (void *)&ctrls->controls;
+ *array_size = sizeof(struct v4l2_ext_control)
+ * ctrls->count;
+ ret = 1;
+ }
+ break;
+ }
+ }
+
+ return ret;
+}
+
+long
+video_usercopy(struct file *file, unsigned int cmd, unsigned long arg,
+ v4l2_kioctl func)
+{
+ char sbuf[128];
+ void *mbuf = NULL;
+ void *parg = (void *)arg;
+ long err = -EINVAL;
+ bool has_array_args;
+ size_t array_size = 0;
+ void __user *user_ptr = NULL;
+ void **kernel_ptr = NULL;
+
+ /* Copy arguments into temp kernel buffer */
+ if (_IOC_DIR(cmd) != _IOC_NONE) {
+ if (_IOC_SIZE(cmd) <= sizeof(sbuf)) {
+ parg = sbuf;
+ } else {
+ /* too big to allocate from stack */
+ mbuf = kmalloc(_IOC_SIZE(cmd), GFP_KERNEL);
+ if (NULL == mbuf)
+ return -ENOMEM;
+ parg = mbuf;
+ }
+
+ err = -EFAULT;
+ if (_IOC_DIR(cmd) & _IOC_WRITE) {
+ unsigned int n = _IOC_SIZE(cmd);
+
+ /*
+ * In some cases, only a few fields are used as input,
+ * i.e. when the app sets "index" and then the driver
+ * fills in the rest of the structure for the thing
+ * with that index. We only need to copy up the first
+ * non-input field.
+ */
+ if (v4l2_is_known_ioctl(cmd)) {
+ u32 flags = v4l2_ioctls[_IOC_NR(cmd)].flags;
+ if (flags & INFO_FL_CLEAR_MASK)
+ n = (flags & INFO_FL_CLEAR_MASK) >> 16;
+ }
+
+ if (copy_from_user(parg, (void __user *)arg, n))
+ goto out;
+
+ /* zero out anything we don't copy from userspace */
+ if (n < _IOC_SIZE(cmd))
+ memset((u8 *)parg + n, 0, _IOC_SIZE(cmd) - n);
+ } else {
+ /* read-only ioctl */
+ memset(parg, 0, _IOC_SIZE(cmd));
+ }
+ }
+
+ err = check_array_args(cmd, parg, &array_size, &user_ptr, &kernel_ptr);
+ if (err < 0)
+ goto out;
+ has_array_args = err;
+
+ if (has_array_args) {
+ /*
+ * When adding new types of array args, make sure that the
+ * parent argument to ioctl (which contains the pointer to the
+ * array) fits into sbuf (so that mbuf will still remain
+ * unused up to here).
+ */
+ mbuf = kmalloc(array_size, GFP_KERNEL);
+ err = -ENOMEM;
+ if (NULL == mbuf)
+ goto out_array_args;
+ err = -EFAULT;
+ if (copy_from_user(mbuf, user_ptr, array_size))
+ goto out_array_args;
+ *kernel_ptr = mbuf;
+ }
+
+ /* Handles IOCTL */
+ err = func(file, cmd, parg);
+ if (err == -ENOIOCTLCMD)
+ err = -ENOTTY;
+
+ if (has_array_args) {
+ *kernel_ptr = user_ptr;
+ if (copy_to_user(user_ptr, mbuf, array_size))
+ err = -EFAULT;
+ goto out_array_args;
+ }
+ /* VIDIOC_QUERY_DV_TIMINGS can return an error, but still have valid
+ results that must be returned. */
+ if (err < 0 && cmd != VIDIOC_QUERY_DV_TIMINGS)
+ goto out;
+
+out_array_args:
+ /* Copy results into user buffer */
+ switch (_IOC_DIR(cmd)) {
+ case _IOC_READ:
+ case (_IOC_WRITE | _IOC_READ):
+ if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd)))
+ err = -EFAULT;
+ break;
+ }
+
+out:
+ kfree(mbuf);
+ return err;
+}
+EXPORT_SYMBOL(video_usercopy);
+
+long video_ioctl2(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(file, cmd, arg, __video_do_ioctl);
+}
+EXPORT_SYMBOL(video_ioctl2);