mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-21 08:19:28 +00:00
![Hans Verkuil](/assets/img/avatar_default.png)
Instead of filling in the struct v4l2_capability device_caps field, fill in the struct video_device device_caps field. That way the V4L2 core knows what the capabilities of the video device are. But this only really works if all drivers use this, so convert all pci drivers in this patch. Tested with cx88-blackbird and ivtv PVR-350. Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
779 lines
21 KiB
C
779 lines
21 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Driver for the Conexant CX25821 PCIe bridge
|
|
*
|
|
* Copyright (C) 2009 Conexant Systems Inc.
|
|
* Authors <shu.lin@conexant.com>, <hiep.huynh@conexant.com>
|
|
* Based on Steven Toth <stoth@linuxtv.org> cx25821 driver
|
|
* Parts adapted/taken from Eduardo Moscoso Rubino
|
|
* Copyright (C) 2009 Eduardo Moscoso Rubino <moscoso@TopoLogica.com>
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include "cx25821-video.h"
|
|
|
|
MODULE_DESCRIPTION("v4l2 driver module for cx25821 based TV cards");
|
|
MODULE_AUTHOR("Hiep Huynh <hiep.huynh@conexant.com>");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
static unsigned int video_nr[] = {[0 ... (CX25821_MAXBOARDS - 1)] = UNSET };
|
|
|
|
module_param_array(video_nr, int, NULL, 0444);
|
|
|
|
MODULE_PARM_DESC(video_nr, "video device numbers");
|
|
|
|
static unsigned int video_debug = VIDEO_DEBUG;
|
|
module_param(video_debug, int, 0644);
|
|
MODULE_PARM_DESC(video_debug, "enable debug messages [video]");
|
|
|
|
static unsigned int irq_debug;
|
|
module_param(irq_debug, int, 0644);
|
|
MODULE_PARM_DESC(irq_debug, "enable debug messages [IRQ handler]");
|
|
|
|
#define FORMAT_FLAGS_PACKED 0x01
|
|
|
|
static const struct cx25821_fmt formats[] = {
|
|
{
|
|
.name = "4:1:1, packed, Y41P",
|
|
.fourcc = V4L2_PIX_FMT_Y41P,
|
|
.depth = 12,
|
|
.flags = FORMAT_FLAGS_PACKED,
|
|
}, {
|
|
.name = "4:2:2, packed, YUYV",
|
|
.fourcc = V4L2_PIX_FMT_YUYV,
|
|
.depth = 16,
|
|
.flags = FORMAT_FLAGS_PACKED,
|
|
},
|
|
};
|
|
|
|
static const struct cx25821_fmt *cx25821_format_by_fourcc(unsigned int fourcc)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(formats); i++)
|
|
if (formats[i].fourcc == fourcc)
|
|
return formats + i;
|
|
return NULL;
|
|
}
|
|
|
|
int cx25821_start_video_dma(struct cx25821_dev *dev,
|
|
struct cx25821_dmaqueue *q,
|
|
struct cx25821_buffer *buf,
|
|
const struct sram_channel *channel)
|
|
{
|
|
int tmp = 0;
|
|
|
|
/* setup fifo + format */
|
|
cx25821_sram_channel_setup(dev, channel, buf->bpl, buf->risc.dma);
|
|
|
|
/* reset counter */
|
|
cx_write(channel->gpcnt_ctl, 3);
|
|
|
|
/* enable irq */
|
|
cx_set(PCI_INT_MSK, cx_read(PCI_INT_MSK) | (1 << channel->i));
|
|
cx_set(channel->int_msk, 0x11);
|
|
|
|
/* start dma */
|
|
cx_write(channel->dma_ctl, 0x11); /* FIFO and RISC enable */
|
|
|
|
/* make sure upstream setting if any is reversed */
|
|
tmp = cx_read(VID_CH_MODE_SEL);
|
|
cx_write(VID_CH_MODE_SEL, tmp & 0xFFFFFE00);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cx25821_video_irq(struct cx25821_dev *dev, int chan_num, u32 status)
|
|
{
|
|
int handled = 0;
|
|
u32 mask;
|
|
const struct sram_channel *channel = dev->channels[chan_num].sram_channels;
|
|
|
|
mask = cx_read(channel->int_msk);
|
|
if (0 == (status & mask))
|
|
return handled;
|
|
|
|
cx_write(channel->int_stat, status);
|
|
|
|
/* risc op code error */
|
|
if (status & (1 << 16)) {
|
|
pr_warn("%s, %s: video risc op code error\n",
|
|
dev->name, channel->name);
|
|
cx_clear(channel->dma_ctl, 0x11);
|
|
cx25821_sram_channel_dump(dev, channel);
|
|
}
|
|
|
|
/* risc1 y */
|
|
if (status & FLD_VID_DST_RISC1) {
|
|
struct cx25821_dmaqueue *dmaq =
|
|
&dev->channels[channel->i].dma_vidq;
|
|
struct cx25821_buffer *buf;
|
|
|
|
spin_lock(&dev->slock);
|
|
if (!list_empty(&dmaq->active)) {
|
|
buf = list_entry(dmaq->active.next,
|
|
struct cx25821_buffer, queue);
|
|
|
|
buf->vb.vb2_buf.timestamp = ktime_get_ns();
|
|
buf->vb.sequence = dmaq->count++;
|
|
list_del(&buf->queue);
|
|
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
|
|
}
|
|
spin_unlock(&dev->slock);
|
|
handled++;
|
|
}
|
|
return handled;
|
|
}
|
|
|
|
static int cx25821_queue_setup(struct vb2_queue *q,
|
|
unsigned int *num_buffers, unsigned int *num_planes,
|
|
unsigned int sizes[], struct device *alloc_devs[])
|
|
{
|
|
struct cx25821_channel *chan = q->drv_priv;
|
|
unsigned size = (chan->fmt->depth * chan->width * chan->height) >> 3;
|
|
|
|
if (*num_planes)
|
|
return sizes[0] < size ? -EINVAL : 0;
|
|
|
|
*num_planes = 1;
|
|
sizes[0] = size;
|
|
return 0;
|
|
}
|
|
|
|
static int cx25821_buffer_prepare(struct vb2_buffer *vb)
|
|
{
|
|
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
|
|
struct cx25821_channel *chan = vb->vb2_queue->drv_priv;
|
|
struct cx25821_dev *dev = chan->dev;
|
|
struct cx25821_buffer *buf =
|
|
container_of(vbuf, struct cx25821_buffer, vb);
|
|
struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, 0);
|
|
u32 line0_offset;
|
|
int bpl_local = LINE_SIZE_D1;
|
|
int ret;
|
|
|
|
if (chan->pixel_formats == PIXEL_FRMT_411)
|
|
buf->bpl = (chan->fmt->depth * chan->width) >> 3;
|
|
else
|
|
buf->bpl = (chan->fmt->depth >> 3) * chan->width;
|
|
|
|
if (vb2_plane_size(vb, 0) < chan->height * buf->bpl)
|
|
return -EINVAL;
|
|
vb2_set_plane_payload(vb, 0, chan->height * buf->bpl);
|
|
buf->vb.field = chan->field;
|
|
|
|
if (chan->pixel_formats == PIXEL_FRMT_411) {
|
|
bpl_local = buf->bpl;
|
|
} else {
|
|
bpl_local = buf->bpl; /* Default */
|
|
|
|
if (chan->use_cif_resolution) {
|
|
if (dev->tvnorm & V4L2_STD_625_50)
|
|
bpl_local = 352 << 1;
|
|
else
|
|
bpl_local = chan->cif_width << 1;
|
|
}
|
|
}
|
|
|
|
switch (chan->field) {
|
|
case V4L2_FIELD_TOP:
|
|
ret = cx25821_risc_buffer(dev->pci, &buf->risc,
|
|
sgt->sgl, 0, UNSET,
|
|
buf->bpl, 0, chan->height);
|
|
break;
|
|
case V4L2_FIELD_BOTTOM:
|
|
ret = cx25821_risc_buffer(dev->pci, &buf->risc,
|
|
sgt->sgl, UNSET, 0,
|
|
buf->bpl, 0, chan->height);
|
|
break;
|
|
case V4L2_FIELD_INTERLACED:
|
|
/* All other formats are top field first */
|
|
line0_offset = 0;
|
|
dprintk(1, "top field first\n");
|
|
|
|
ret = cx25821_risc_buffer(dev->pci, &buf->risc,
|
|
sgt->sgl, line0_offset,
|
|
bpl_local, bpl_local, bpl_local,
|
|
chan->height >> 1);
|
|
break;
|
|
case V4L2_FIELD_SEQ_TB:
|
|
ret = cx25821_risc_buffer(dev->pci, &buf->risc,
|
|
sgt->sgl,
|
|
0, buf->bpl * (chan->height >> 1),
|
|
buf->bpl, 0, chan->height >> 1);
|
|
break;
|
|
case V4L2_FIELD_SEQ_BT:
|
|
ret = cx25821_risc_buffer(dev->pci, &buf->risc,
|
|
sgt->sgl,
|
|
buf->bpl * (chan->height >> 1), 0,
|
|
buf->bpl, 0, chan->height >> 1);
|
|
break;
|
|
default:
|
|
WARN_ON(1);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
dprintk(2, "[%p/%d] buffer_prep - %dx%d %dbpp \"%s\" - dma=0x%08lx\n",
|
|
buf, buf->vb.vb2_buf.index, chan->width, chan->height,
|
|
chan->fmt->depth, chan->fmt->name,
|
|
(unsigned long)buf->risc.dma);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void cx25821_buffer_finish(struct vb2_buffer *vb)
|
|
{
|
|
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
|
|
struct cx25821_buffer *buf =
|
|
container_of(vbuf, struct cx25821_buffer, vb);
|
|
struct cx25821_channel *chan = vb->vb2_queue->drv_priv;
|
|
struct cx25821_dev *dev = chan->dev;
|
|
|
|
cx25821_free_buffer(dev, buf);
|
|
}
|
|
|
|
static void cx25821_buffer_queue(struct vb2_buffer *vb)
|
|
{
|
|
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
|
|
struct cx25821_buffer *buf =
|
|
container_of(vbuf, struct cx25821_buffer, vb);
|
|
struct cx25821_channel *chan = vb->vb2_queue->drv_priv;
|
|
struct cx25821_dev *dev = chan->dev;
|
|
struct cx25821_buffer *prev;
|
|
struct cx25821_dmaqueue *q = &dev->channels[chan->id].dma_vidq;
|
|
|
|
buf->risc.cpu[1] = cpu_to_le32(buf->risc.dma + 12);
|
|
buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_CNT_INC);
|
|
buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma + 12);
|
|
buf->risc.jmp[2] = cpu_to_le32(0); /* bits 63-32 */
|
|
|
|
if (list_empty(&q->active)) {
|
|
list_add_tail(&buf->queue, &q->active);
|
|
} else {
|
|
buf->risc.cpu[0] |= cpu_to_le32(RISC_IRQ1);
|
|
prev = list_entry(q->active.prev, struct cx25821_buffer,
|
|
queue);
|
|
list_add_tail(&buf->queue, &q->active);
|
|
prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma);
|
|
}
|
|
}
|
|
|
|
static int cx25821_start_streaming(struct vb2_queue *q, unsigned int count)
|
|
{
|
|
struct cx25821_channel *chan = q->drv_priv;
|
|
struct cx25821_dev *dev = chan->dev;
|
|
struct cx25821_dmaqueue *dmaq = &dev->channels[chan->id].dma_vidq;
|
|
struct cx25821_buffer *buf = list_entry(dmaq->active.next,
|
|
struct cx25821_buffer, queue);
|
|
|
|
dmaq->count = 0;
|
|
cx25821_start_video_dma(dev, dmaq, buf, chan->sram_channels);
|
|
return 0;
|
|
}
|
|
|
|
static void cx25821_stop_streaming(struct vb2_queue *q)
|
|
{
|
|
struct cx25821_channel *chan = q->drv_priv;
|
|
struct cx25821_dev *dev = chan->dev;
|
|
struct cx25821_dmaqueue *dmaq = &dev->channels[chan->id].dma_vidq;
|
|
unsigned long flags;
|
|
|
|
cx_write(chan->sram_channels->dma_ctl, 0); /* FIFO and RISC disable */
|
|
spin_lock_irqsave(&dev->slock, flags);
|
|
while (!list_empty(&dmaq->active)) {
|
|
struct cx25821_buffer *buf = list_entry(dmaq->active.next,
|
|
struct cx25821_buffer, queue);
|
|
|
|
list_del(&buf->queue);
|
|
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
|
|
}
|
|
spin_unlock_irqrestore(&dev->slock, flags);
|
|
}
|
|
|
|
static const struct vb2_ops cx25821_video_qops = {
|
|
.queue_setup = cx25821_queue_setup,
|
|
.buf_prepare = cx25821_buffer_prepare,
|
|
.buf_finish = cx25821_buffer_finish,
|
|
.buf_queue = cx25821_buffer_queue,
|
|
.wait_prepare = vb2_ops_wait_prepare,
|
|
.wait_finish = vb2_ops_wait_finish,
|
|
.start_streaming = cx25821_start_streaming,
|
|
.stop_streaming = cx25821_stop_streaming,
|
|
};
|
|
|
|
/* VIDEO IOCTLS */
|
|
|
|
static int cx25821_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
|
|
struct v4l2_fmtdesc *f)
|
|
{
|
|
if (unlikely(f->index >= ARRAY_SIZE(formats)))
|
|
return -EINVAL;
|
|
|
|
strscpy(f->description, formats[f->index].name, sizeof(f->description));
|
|
f->pixelformat = formats[f->index].fourcc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cx25821_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct cx25821_channel *chan = video_drvdata(file);
|
|
|
|
f->fmt.pix.width = chan->width;
|
|
f->fmt.pix.height = chan->height;
|
|
f->fmt.pix.field = chan->field;
|
|
f->fmt.pix.pixelformat = chan->fmt->fourcc;
|
|
f->fmt.pix.bytesperline = (chan->width * chan->fmt->depth) >> 3;
|
|
f->fmt.pix.sizeimage = chan->height * f->fmt.pix.bytesperline;
|
|
f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cx25821_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct cx25821_channel *chan = video_drvdata(file);
|
|
struct cx25821_dev *dev = chan->dev;
|
|
const struct cx25821_fmt *fmt;
|
|
enum v4l2_field field = f->fmt.pix.field;
|
|
unsigned int maxh;
|
|
unsigned w;
|
|
|
|
fmt = cx25821_format_by_fourcc(f->fmt.pix.pixelformat);
|
|
if (NULL == fmt)
|
|
return -EINVAL;
|
|
maxh = (dev->tvnorm & V4L2_STD_625_50) ? 576 : 480;
|
|
|
|
w = f->fmt.pix.width;
|
|
if (field != V4L2_FIELD_BOTTOM)
|
|
field = V4L2_FIELD_TOP;
|
|
if (w < 352) {
|
|
w = 176;
|
|
f->fmt.pix.height = maxh / 4;
|
|
} else if (w < 720) {
|
|
w = 352;
|
|
f->fmt.pix.height = maxh / 2;
|
|
} else {
|
|
w = 720;
|
|
f->fmt.pix.height = maxh;
|
|
field = V4L2_FIELD_INTERLACED;
|
|
}
|
|
f->fmt.pix.field = field;
|
|
f->fmt.pix.width = w;
|
|
f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3;
|
|
f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
|
|
f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct cx25821_channel *chan = video_drvdata(file);
|
|
struct cx25821_dev *dev = chan->dev;
|
|
int pix_format = PIXEL_FRMT_422;
|
|
int err;
|
|
|
|
err = cx25821_vidioc_try_fmt_vid_cap(file, priv, f);
|
|
|
|
if (0 != err)
|
|
return err;
|
|
|
|
chan->fmt = cx25821_format_by_fourcc(f->fmt.pix.pixelformat);
|
|
chan->field = f->fmt.pix.field;
|
|
chan->width = f->fmt.pix.width;
|
|
chan->height = f->fmt.pix.height;
|
|
|
|
if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_Y41P)
|
|
pix_format = PIXEL_FRMT_411;
|
|
else
|
|
pix_format = PIXEL_FRMT_422;
|
|
|
|
cx25821_set_pixel_format(dev, SRAM_CH00, pix_format);
|
|
|
|
/* check if cif resolution */
|
|
if (chan->width == 320 || chan->width == 352)
|
|
chan->use_cif_resolution = 1;
|
|
else
|
|
chan->use_cif_resolution = 0;
|
|
|
|
chan->cif_width = chan->width;
|
|
medusa_set_resolution(dev, chan->width, SRAM_CH00);
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_log_status(struct file *file, void *priv)
|
|
{
|
|
struct cx25821_channel *chan = video_drvdata(file);
|
|
struct cx25821_dev *dev = chan->dev;
|
|
const struct sram_channel *sram_ch = chan->sram_channels;
|
|
u32 tmp = 0;
|
|
|
|
tmp = cx_read(sram_ch->dma_ctl);
|
|
pr_info("Video input 0 is %s\n",
|
|
(tmp & 0x11) ? "streaming" : "stopped");
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int cx25821_vidioc_querycap(struct file *file, void *priv,
|
|
struct v4l2_capability *cap)
|
|
{
|
|
struct cx25821_channel *chan = video_drvdata(file);
|
|
struct cx25821_dev *dev = chan->dev;
|
|
|
|
strscpy(cap->driver, "cx25821", sizeof(cap->driver));
|
|
strscpy(cap->card, cx25821_boards[dev->board].name, sizeof(cap->card));
|
|
sprintf(cap->bus_info, "PCIe:%s", pci_name(dev->pci));
|
|
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT |
|
|
V4L2_CAP_READWRITE | V4L2_CAP_STREAMING |
|
|
V4L2_CAP_DEVICE_CAPS;
|
|
return 0;
|
|
}
|
|
|
|
static int cx25821_vidioc_g_std(struct file *file, void *priv, v4l2_std_id *tvnorms)
|
|
{
|
|
struct cx25821_channel *chan = video_drvdata(file);
|
|
|
|
*tvnorms = chan->dev->tvnorm;
|
|
return 0;
|
|
}
|
|
|
|
static int cx25821_vidioc_s_std(struct file *file, void *priv,
|
|
v4l2_std_id tvnorms)
|
|
{
|
|
struct cx25821_channel *chan = video_drvdata(file);
|
|
struct cx25821_dev *dev = chan->dev;
|
|
|
|
if (dev->tvnorm == tvnorms)
|
|
return 0;
|
|
|
|
dev->tvnorm = tvnorms;
|
|
chan->width = 720;
|
|
chan->height = (dev->tvnorm & V4L2_STD_625_50) ? 576 : 480;
|
|
|
|
medusa_set_videostandard(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cx25821_vidioc_enum_input(struct file *file, void *priv,
|
|
struct v4l2_input *i)
|
|
{
|
|
if (i->index)
|
|
return -EINVAL;
|
|
|
|
i->type = V4L2_INPUT_TYPE_CAMERA;
|
|
i->std = CX25821_NORMS;
|
|
strscpy(i->name, "Composite", sizeof(i->name));
|
|
return 0;
|
|
}
|
|
|
|
static int cx25821_vidioc_g_input(struct file *file, void *priv, unsigned int *i)
|
|
{
|
|
*i = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int cx25821_vidioc_s_input(struct file *file, void *priv, unsigned int i)
|
|
{
|
|
return i ? -EINVAL : 0;
|
|
}
|
|
|
|
static int cx25821_s_ctrl(struct v4l2_ctrl *ctrl)
|
|
{
|
|
struct cx25821_channel *chan =
|
|
container_of(ctrl->handler, struct cx25821_channel, hdl);
|
|
struct cx25821_dev *dev = chan->dev;
|
|
|
|
switch (ctrl->id) {
|
|
case V4L2_CID_BRIGHTNESS:
|
|
medusa_set_brightness(dev, ctrl->val, chan->id);
|
|
break;
|
|
case V4L2_CID_HUE:
|
|
medusa_set_hue(dev, ctrl->val, chan->id);
|
|
break;
|
|
case V4L2_CID_CONTRAST:
|
|
medusa_set_contrast(dev, ctrl->val, chan->id);
|
|
break;
|
|
case V4L2_CID_SATURATION:
|
|
medusa_set_saturation(dev, ctrl->val, chan->id);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int cx25821_vidioc_enum_output(struct file *file, void *priv,
|
|
struct v4l2_output *o)
|
|
{
|
|
if (o->index)
|
|
return -EINVAL;
|
|
|
|
o->type = V4L2_INPUT_TYPE_CAMERA;
|
|
o->std = CX25821_NORMS;
|
|
strscpy(o->name, "Composite", sizeof(o->name));
|
|
return 0;
|
|
}
|
|
|
|
static int cx25821_vidioc_g_output(struct file *file, void *priv, unsigned int *o)
|
|
{
|
|
*o = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int cx25821_vidioc_s_output(struct file *file, void *priv, unsigned int o)
|
|
{
|
|
return o ? -EINVAL : 0;
|
|
}
|
|
|
|
static int cx25821_vidioc_try_fmt_vid_out(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct cx25821_channel *chan = video_drvdata(file);
|
|
struct cx25821_dev *dev = chan->dev;
|
|
const struct cx25821_fmt *fmt;
|
|
|
|
fmt = cx25821_format_by_fourcc(f->fmt.pix.pixelformat);
|
|
if (NULL == fmt)
|
|
return -EINVAL;
|
|
f->fmt.pix.width = 720;
|
|
f->fmt.pix.height = (dev->tvnorm & V4L2_STD_625_50) ? 576 : 480;
|
|
f->fmt.pix.field = V4L2_FIELD_INTERLACED;
|
|
f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3;
|
|
f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
|
|
f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_s_fmt_vid_out(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct cx25821_channel *chan = video_drvdata(file);
|
|
int err;
|
|
|
|
err = cx25821_vidioc_try_fmt_vid_out(file, priv, f);
|
|
|
|
if (0 != err)
|
|
return err;
|
|
|
|
chan->fmt = cx25821_format_by_fourcc(f->fmt.pix.pixelformat);
|
|
chan->field = f->fmt.pix.field;
|
|
chan->width = f->fmt.pix.width;
|
|
chan->height = f->fmt.pix.height;
|
|
if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_Y41P)
|
|
chan->pixel_formats = PIXEL_FRMT_411;
|
|
else
|
|
chan->pixel_formats = PIXEL_FRMT_422;
|
|
return 0;
|
|
}
|
|
|
|
static const struct v4l2_ctrl_ops cx25821_ctrl_ops = {
|
|
.s_ctrl = cx25821_s_ctrl,
|
|
};
|
|
|
|
static const struct v4l2_file_operations video_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = v4l2_fh_open,
|
|
.release = vb2_fop_release,
|
|
.read = vb2_fop_read,
|
|
.poll = vb2_fop_poll,
|
|
.unlocked_ioctl = video_ioctl2,
|
|
.mmap = vb2_fop_mmap,
|
|
};
|
|
|
|
static const struct v4l2_ioctl_ops video_ioctl_ops = {
|
|
.vidioc_querycap = cx25821_vidioc_querycap,
|
|
.vidioc_enum_fmt_vid_cap = cx25821_vidioc_enum_fmt_vid_cap,
|
|
.vidioc_g_fmt_vid_cap = cx25821_vidioc_g_fmt_vid_cap,
|
|
.vidioc_try_fmt_vid_cap = cx25821_vidioc_try_fmt_vid_cap,
|
|
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
|
|
.vidioc_reqbufs = vb2_ioctl_reqbufs,
|
|
.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
|
|
.vidioc_create_bufs = vb2_ioctl_create_bufs,
|
|
.vidioc_querybuf = vb2_ioctl_querybuf,
|
|
.vidioc_qbuf = vb2_ioctl_qbuf,
|
|
.vidioc_dqbuf = vb2_ioctl_dqbuf,
|
|
.vidioc_streamon = vb2_ioctl_streamon,
|
|
.vidioc_streamoff = vb2_ioctl_streamoff,
|
|
.vidioc_g_std = cx25821_vidioc_g_std,
|
|
.vidioc_s_std = cx25821_vidioc_s_std,
|
|
.vidioc_enum_input = cx25821_vidioc_enum_input,
|
|
.vidioc_g_input = cx25821_vidioc_g_input,
|
|
.vidioc_s_input = cx25821_vidioc_s_input,
|
|
.vidioc_log_status = vidioc_log_status,
|
|
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
|
|
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
|
|
};
|
|
|
|
static const struct video_device cx25821_video_device = {
|
|
.name = "cx25821-video",
|
|
.fops = &video_fops,
|
|
.release = video_device_release_empty,
|
|
.minor = -1,
|
|
.ioctl_ops = &video_ioctl_ops,
|
|
.tvnorms = CX25821_NORMS,
|
|
.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE |
|
|
V4L2_CAP_STREAMING,
|
|
};
|
|
|
|
static const struct v4l2_file_operations video_out_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = v4l2_fh_open,
|
|
.release = vb2_fop_release,
|
|
.write = vb2_fop_write,
|
|
.poll = vb2_fop_poll,
|
|
.unlocked_ioctl = video_ioctl2,
|
|
.mmap = vb2_fop_mmap,
|
|
};
|
|
|
|
static const struct v4l2_ioctl_ops video_out_ioctl_ops = {
|
|
.vidioc_querycap = cx25821_vidioc_querycap,
|
|
.vidioc_enum_fmt_vid_out = cx25821_vidioc_enum_fmt_vid_cap,
|
|
.vidioc_g_fmt_vid_out = cx25821_vidioc_g_fmt_vid_cap,
|
|
.vidioc_try_fmt_vid_out = cx25821_vidioc_try_fmt_vid_out,
|
|
.vidioc_s_fmt_vid_out = vidioc_s_fmt_vid_out,
|
|
.vidioc_g_std = cx25821_vidioc_g_std,
|
|
.vidioc_s_std = cx25821_vidioc_s_std,
|
|
.vidioc_enum_output = cx25821_vidioc_enum_output,
|
|
.vidioc_g_output = cx25821_vidioc_g_output,
|
|
.vidioc_s_output = cx25821_vidioc_s_output,
|
|
.vidioc_log_status = vidioc_log_status,
|
|
};
|
|
|
|
static const struct video_device cx25821_video_out_device = {
|
|
.name = "cx25821-video",
|
|
.fops = &video_out_fops,
|
|
.release = video_device_release_empty,
|
|
.minor = -1,
|
|
.ioctl_ops = &video_out_ioctl_ops,
|
|
.tvnorms = CX25821_NORMS,
|
|
.device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_READWRITE,
|
|
};
|
|
|
|
void cx25821_video_unregister(struct cx25821_dev *dev, int chan_num)
|
|
{
|
|
cx_clear(PCI_INT_MSK, 1);
|
|
|
|
if (video_is_registered(&dev->channels[chan_num].vdev)) {
|
|
video_unregister_device(&dev->channels[chan_num].vdev);
|
|
v4l2_ctrl_handler_free(&dev->channels[chan_num].hdl);
|
|
}
|
|
}
|
|
|
|
int cx25821_video_register(struct cx25821_dev *dev)
|
|
{
|
|
int err;
|
|
int i;
|
|
|
|
/* initial device configuration */
|
|
dev->tvnorm = V4L2_STD_NTSC_M;
|
|
|
|
spin_lock_init(&dev->slock);
|
|
|
|
for (i = 0; i < MAX_VID_CAP_CHANNEL_NUM - 1; ++i) {
|
|
struct cx25821_channel *chan = &dev->channels[i];
|
|
struct video_device *vdev = &chan->vdev;
|
|
struct v4l2_ctrl_handler *hdl = &chan->hdl;
|
|
struct vb2_queue *q;
|
|
bool is_output = i > SRAM_CH08;
|
|
|
|
if (i == SRAM_CH08) /* audio channel */
|
|
continue;
|
|
|
|
if (!is_output) {
|
|
v4l2_ctrl_handler_init(hdl, 4);
|
|
v4l2_ctrl_new_std(hdl, &cx25821_ctrl_ops,
|
|
V4L2_CID_BRIGHTNESS, 0, 10000, 1, 6200);
|
|
v4l2_ctrl_new_std(hdl, &cx25821_ctrl_ops,
|
|
V4L2_CID_CONTRAST, 0, 10000, 1, 5000);
|
|
v4l2_ctrl_new_std(hdl, &cx25821_ctrl_ops,
|
|
V4L2_CID_SATURATION, 0, 10000, 1, 5000);
|
|
v4l2_ctrl_new_std(hdl, &cx25821_ctrl_ops,
|
|
V4L2_CID_HUE, 0, 10000, 1, 5000);
|
|
if (hdl->error) {
|
|
err = hdl->error;
|
|
goto fail_unreg;
|
|
}
|
|
err = v4l2_ctrl_handler_setup(hdl);
|
|
if (err)
|
|
goto fail_unreg;
|
|
} else {
|
|
chan->out = &dev->vid_out_data[i - SRAM_CH09];
|
|
chan->out->chan = chan;
|
|
}
|
|
|
|
chan->sram_channels = &cx25821_sram_channels[i];
|
|
chan->width = 720;
|
|
chan->field = V4L2_FIELD_INTERLACED;
|
|
if (dev->tvnorm & V4L2_STD_625_50)
|
|
chan->height = 576;
|
|
else
|
|
chan->height = 480;
|
|
|
|
if (chan->pixel_formats == PIXEL_FRMT_411)
|
|
chan->fmt = cx25821_format_by_fourcc(V4L2_PIX_FMT_Y41P);
|
|
else
|
|
chan->fmt = cx25821_format_by_fourcc(V4L2_PIX_FMT_YUYV);
|
|
|
|
cx_write(chan->sram_channels->int_stat, 0xffffffff);
|
|
|
|
INIT_LIST_HEAD(&chan->dma_vidq.active);
|
|
|
|
q = &chan->vidq;
|
|
|
|
q->type = is_output ? V4L2_BUF_TYPE_VIDEO_OUTPUT :
|
|
V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
|
|
q->io_modes |= is_output ? VB2_WRITE : VB2_READ;
|
|
q->gfp_flags = GFP_DMA32;
|
|
q->min_buffers_needed = 2;
|
|
q->drv_priv = chan;
|
|
q->buf_struct_size = sizeof(struct cx25821_buffer);
|
|
q->ops = &cx25821_video_qops;
|
|
q->mem_ops = &vb2_dma_sg_memops;
|
|
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
|
|
q->lock = &dev->lock;
|
|
q->dev = &dev->pci->dev;
|
|
|
|
if (!is_output) {
|
|
err = vb2_queue_init(q);
|
|
if (err < 0)
|
|
goto fail_unreg;
|
|
}
|
|
|
|
/* register v4l devices */
|
|
*vdev = is_output ? cx25821_video_out_device : cx25821_video_device;
|
|
vdev->v4l2_dev = &dev->v4l2_dev;
|
|
if (!is_output)
|
|
vdev->ctrl_handler = hdl;
|
|
else
|
|
vdev->vfl_dir = VFL_DIR_TX;
|
|
vdev->lock = &dev->lock;
|
|
vdev->queue = q;
|
|
snprintf(vdev->name, sizeof(vdev->name), "%s #%d", dev->name, i);
|
|
video_set_drvdata(vdev, chan);
|
|
|
|
err = video_register_device(vdev, VFL_TYPE_GRABBER,
|
|
video_nr[dev->nr]);
|
|
|
|
if (err < 0)
|
|
goto fail_unreg;
|
|
}
|
|
|
|
/* set PCI interrupt */
|
|
cx_set(PCI_INT_MSK, 0xff);
|
|
|
|
return 0;
|
|
|
|
fail_unreg:
|
|
while (i >= 0)
|
|
cx25821_video_unregister(dev, i--);
|
|
return err;
|
|
}
|