mirror of
https://github.com/torvalds/linux.git
synced 2024-12-26 21:02:19 +00:00
bcb63314e2
Drop the FSF's postal address from the source code files that typically contain mostly the license text. Of the 628 removed instances, 578 are outdated. The patch has been created with the following command without manual edits: git grep -l "675 Mass Ave\|59 Temple Place\|51 Franklin St" -- \ drivers/media/ include/media|while read i; do i=$i perl -e ' open(F,"< $ENV{i}"); $a=join("", <F>); $a =~ s/[ \t]*\*\n.*You should.*\n.*along with.*\n.*(\n.*USA.*$)?\n//m && $a =~ s/(^.*)Or, (point your browser to) /$1To obtain the license, $2\n$1/m; close(F); open(F, "> $ENV{i}"); print F $a; close(F);'; done Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
793 lines
21 KiB
C
793 lines
21 KiB
C
/*
|
|
* 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>
|
|
*
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#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;
|
|
|
|
strlcpy(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;
|
|
const u32 cap_input = V4L2_CAP_VIDEO_CAPTURE |
|
|
V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
|
|
const u32 cap_output = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_READWRITE;
|
|
|
|
strcpy(cap->driver, "cx25821");
|
|
strlcpy(cap->card, cx25821_boards[dev->board].name, sizeof(cap->card));
|
|
sprintf(cap->bus_info, "PCIe:%s", pci_name(dev->pci));
|
|
if (chan->id >= VID_CHANNEL_NUM)
|
|
cap->device_caps = cap_output;
|
|
else
|
|
cap->device_caps = cap_input;
|
|
cap->capabilities = cap_input | cap_output | 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;
|
|
strcpy(i->name, "Composite");
|
|
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;
|
|
strcpy(o->name, "Composite");
|
|
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,
|
|
};
|
|
|
|
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,
|
|
};
|
|
|
|
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;
|
|
}
|