blob: 6ecb504a79f99c4b87cf0ed99eb0a8849db5d878 [file] [log] [blame]
/*
* Copyright (C) 2015 Industrial Research Institute for Automation
* and Measurements PIAP
*
* Written by Krzysztof Ha?asa.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License
* as published by the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <media/v4l2-common.h>
#include <media/v4l2-event.h>
#include "tw686x-kh.h"
#include "tw686x-kh-regs.h"
#define MAX_SG_ENTRY_SIZE (/* 8192 - 128 */ 4096)
#define MAX_SG_DESC_COUNT 256 /* PAL 704x576 needs up to 198 4-KB pages */
static const struct tw686x_format formats[] = {
{
.name = "4:2:2 packed, UYVY", /* aka Y422 */
.fourcc = V4L2_PIX_FMT_UYVY,
.mode = 0,
.depth = 16,
}, {
#if 0
.name = "4:2:0 packed, YUV",
.mode = 1, /* non-standard */
.depth = 12,
}, {
.name = "4:1:1 packed, YUV",
.mode = 2, /* non-standard */
.depth = 12,
}, {
#endif
.name = "4:1:1 packed, YUV",
.fourcc = V4L2_PIX_FMT_Y41P,
.mode = 3,
.depth = 12,
}, {
.name = "15 bpp RGB",
.fourcc = V4L2_PIX_FMT_RGB555,
.mode = 4,
.depth = 16,
}, {
.name = "16 bpp RGB",
.fourcc = V4L2_PIX_FMT_RGB565,
.mode = 5,
.depth = 16,
}, {
.name = "4:2:2 packed, YUYV",
.fourcc = V4L2_PIX_FMT_YUYV,
.mode = 6,
.depth = 16,
}
/* mode 7 is "reserved" */
};
static const v4l2_std_id video_standards[7] = {
V4L2_STD_NTSC,
V4L2_STD_PAL,
V4L2_STD_SECAM,
V4L2_STD_NTSC_443,
V4L2_STD_PAL_M,
V4L2_STD_PAL_N,
V4L2_STD_PAL_60,
};
static const struct tw686x_format *format_by_fourcc(unsigned int fourcc)
{
unsigned int cnt;
for (cnt = 0; cnt < ARRAY_SIZE(formats); cnt++)
if (formats[cnt].fourcc == fourcc)
return &formats[cnt];
return NULL;
}
static void tw686x_get_format(struct tw686x_video_channel *vc,
struct v4l2_format *f)
{
const struct tw686x_format *format;
unsigned int width, height, height_div = 1;
format = format_by_fourcc(f->fmt.pix.pixelformat);
if (!format) {
format = &formats[0];
f->fmt.pix.pixelformat = format->fourcc;
}
width = 704;
if (f->fmt.pix.width < width * 3 / 4 /* halfway */)
width /= 2;
height = (vc->video_standard & V4L2_STD_625_50) ? 576 : 480;
if (f->fmt.pix.height < height * 3 / 4 /* halfway */)
height_div = 2;
switch (f->fmt.pix.field) {
case V4L2_FIELD_TOP:
case V4L2_FIELD_BOTTOM:
height_div = 2;
break;
case V4L2_FIELD_SEQ_BT:
if (height_div > 1)
f->fmt.pix.field = V4L2_FIELD_BOTTOM;
break;
default:
if (height_div > 1)
f->fmt.pix.field = V4L2_FIELD_TOP;
else
f->fmt.pix.field = V4L2_FIELD_SEQ_TB;
}
height /= height_div;
f->fmt.pix.width = width;
f->fmt.pix.height = height;
f->fmt.pix.bytesperline = f->fmt.pix.width * format->depth / 8;
f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
}
/* video queue operations */
static int tw686x_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
unsigned int *nplanes, unsigned int sizes[],
void *alloc_ctxs[])
{
struct tw686x_video_channel *vc = vb2_get_drv_priv(vq);
unsigned int size = vc->width * vc->height * vc->format->depth / 8;
alloc_ctxs[0] = vc->alloc_ctx;
if (*nbuffers < 2)
*nbuffers = 2;
if (*nplanes)
return sizes[0] < size ? -EINVAL : 0;
sizes[0] = size;
*nplanes = 1; /* packed formats only */
return 0;
}
static void tw686x_buf_queue(struct vb2_buffer *vb)
{
struct tw686x_video_channel *vc = vb2_get_drv_priv(vb->vb2_queue);
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct tw686x_vb2_buf *buf;
buf = container_of(vbuf, struct tw686x_vb2_buf, vb);
spin_lock(&vc->qlock);
list_add_tail(&buf->list, &vc->vidq_queued);
spin_unlock(&vc->qlock);
}
static void setup_descs(struct tw686x_video_channel *vc, unsigned int n)
{
loop:
while (!list_empty(&vc->vidq_queued)) {
struct vdma_desc *descs = vc->sg_descs[n];
struct tw686x_vb2_buf *buf;
struct sg_table *vbuf;
struct scatterlist *sg;
unsigned int buf_len, count = 0;
int i;
buf = list_first_entry(&vc->vidq_queued, struct tw686x_vb2_buf,
list);
list_del(&buf->list);
buf_len = vc->width * vc->height * vc->format->depth / 8;
if (vb2_plane_size(&buf->vb.vb2_buf, 0) < buf_len) {
pr_err("Video buffer size too small\n");
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
goto loop; /* try another */
}
vbuf = vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0);
for_each_sg(vbuf->sgl, sg, vbuf->nents, i) {
dma_addr_t phys = sg_dma_address(sg);
unsigned int len = sg_dma_len(sg);
while (len && buf_len) {
unsigned int entry_len = min_t(unsigned int, len,
MAX_SG_ENTRY_SIZE);
entry_len = min(entry_len, buf_len);
if (count == MAX_SG_DESC_COUNT) {
pr_err("Video buffer size too fragmented\n");
vb2_buffer_done(&buf->vb.vb2_buf,
VB2_BUF_STATE_ERROR);
goto loop;
}
descs[count].phys = cpu_to_le32(phys);
descs[count++].flags_length =
cpu_to_le32(0x40000000 /* available */ |
entry_len);
phys += entry_len;
len -= entry_len;
buf_len -= entry_len;
}
if (!buf_len)
break;
}
/* clear the remaining entries */
while (count < MAX_SG_DESC_COUNT) {
descs[count].phys = 0;
descs[count++].flags_length = 0; /* unavailable */
}
buf->vb.vb2_buf.state = VB2_BUF_STATE_ACTIVE;
vc->curr_bufs[n] = buf;
return;
}
vc->curr_bufs[n] = NULL;
}
/* On TW6864 and TW6868, all channels share the pair of video DMA SG tables,
with 10-bit start_idx and end_idx determining start and end of frame buffer
for particular channel.
TW6868 with all its 8 channels would be problematic (only 127 SG entries per
channel) but we support only 4 channels on this chip anyway (the first
4 channels are driven with internal video decoder, the other 4 would require
an external TW286x part).
On TW6865 and TW6869, each channel has its own DMA SG table, with indexes
starting with 0. Both chips have complete sets of internal video decoders
(respectively 4 or 8-channel).
All chips have separate SG tables for two video frames. */
static void setup_dma_cfg(struct tw686x_video_channel *vc)
{
unsigned int field_width = 704;
unsigned int field_height = (vc->video_standard & V4L2_STD_625_50) ?
288 : 240;
unsigned int start_idx = is_second_gen(vc->dev) ? 0 :
vc->ch * MAX_SG_DESC_COUNT;
unsigned int end_idx = start_idx + MAX_SG_DESC_COUNT - 1;
u32 dma_cfg = (0 << 30) /* input selection */ |
(1 << 29) /* field2 dropped (if any) */ |
((vc->height < 300) << 28) /* field dropping */ |
(1 << 27) /* master */ |
(0 << 25) /* master channel (for slave only) */ |
(0 << 24) /* (no) vertical (line) decimation */ |
((vc->width < 400) << 23) /* horizontal decimation */ |
(vc->format->mode << 20) /* output video format */ |
(end_idx << 10) /* DMA end index */ |
start_idx /* DMA start index */;
u32 reg;
reg_write(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch], dma_cfg);
reg_write(vc->dev, VIDEO_SIZE[vc->ch], (1 << 31) | (field_height << 16)
| field_width);
reg = reg_read(vc->dev, VIDEO_CONTROL1);
if (vc->video_standard & V4L2_STD_625_50)
reg |= 1 << (vc->ch + 13);
else
reg &= ~(1 << (vc->ch + 13));
reg_write(vc->dev, VIDEO_CONTROL1, reg);
}
static int tw686x_start_streaming(struct vb2_queue *vq, unsigned int count)
{
struct tw686x_video_channel *vc = vb2_get_drv_priv(vq);
struct tw686x_dev *dev = vc->dev;
u32 dma_ch_mask;
unsigned int n;
setup_dma_cfg(vc);
/* queue video buffers if available */
spin_lock(&vc->qlock);
for (n = 0; n < 2; n++)
setup_descs(vc, n);
spin_unlock(&vc->qlock);
dev->video_active |= 1 << vc->ch;
vc->seq = 0;
dma_ch_mask = reg_read(dev, DMA_CHANNEL_ENABLE) | (1 << vc->ch);
reg_write(dev, DMA_CHANNEL_ENABLE, dma_ch_mask);
reg_write(dev, DMA_CMD, (1 << 31) | dma_ch_mask);
return 0;
}
static void tw686x_stop_streaming(struct vb2_queue *vq)
{
struct tw686x_video_channel *vc = vb2_get_drv_priv(vq);
struct tw686x_dev *dev = vc->dev;
u32 dma_ch_mask = reg_read(dev, DMA_CHANNEL_ENABLE);
u32 dma_cmd = reg_read(dev, DMA_CMD);
unsigned int n;
dma_ch_mask &= ~(1 << vc->ch);
reg_write(dev, DMA_CHANNEL_ENABLE, dma_ch_mask);
dev->video_active &= ~(1 << vc->ch);
dma_cmd &= ~(1 << vc->ch);
reg_write(dev, DMA_CMD, dma_cmd);
if (!dev->video_active) {
reg_write(dev, DMA_CMD, 0);
reg_write(dev, DMA_CHANNEL_ENABLE, 0);
}
spin_lock(&vc->qlock);
while (!list_empty(&vc->vidq_queued)) {
struct tw686x_vb2_buf *buf;
buf = list_entry(vc->vidq_queued.next, struct tw686x_vb2_buf,
list);
list_del(&buf->list);
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
}
for (n = 0; n < 2; n++)
if (vc->curr_bufs[n])
vb2_buffer_done(&vc->curr_bufs[n]->vb.vb2_buf,
VB2_BUF_STATE_ERROR);
spin_unlock(&vc->qlock);
}
static struct vb2_ops tw686x_video_qops = {
.queue_setup = tw686x_queue_setup,
.buf_queue = tw686x_buf_queue,
.start_streaming = tw686x_start_streaming,
.stop_streaming = tw686x_stop_streaming,
.wait_prepare = vb2_ops_wait_prepare,
.wait_finish = vb2_ops_wait_finish,
};
static int tw686x_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct tw686x_video_channel *vc;
struct tw686x_dev *dev;
unsigned int ch;
vc = container_of(ctrl->handler, struct tw686x_video_channel,
ctrl_handler);
dev = vc->dev;
ch = vc->ch;
switch (ctrl->id) {
case V4L2_CID_BRIGHTNESS:
reg_write(dev, BRIGHT[ch], ctrl->val & 0xFF);
return 0;
case V4L2_CID_CONTRAST:
reg_write(dev, CONTRAST[ch], ctrl->val);
return 0;
case V4L2_CID_SATURATION:
reg_write(dev, SAT_U[ch], ctrl->val);
reg_write(dev, SAT_V[ch], ctrl->val);
return 0;
case V4L2_CID_HUE:
reg_write(dev, HUE[ch], ctrl->val & 0xFF);
return 0;
}
return -EINVAL;
}
static const struct v4l2_ctrl_ops ctrl_ops = {
.s_ctrl = tw686x_s_ctrl,
};
static int tw686x_g_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct tw686x_video_channel *vc = video_drvdata(file);
f->fmt.pix.width = vc->width;
f->fmt.pix.height = vc->height;
f->fmt.pix.field = vc->field;
f->fmt.pix.pixelformat = vc->format->fourcc;
f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
f->fmt.pix.bytesperline = f->fmt.pix.width * vc->format->depth / 8;
f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
return 0;
}
static int tw686x_try_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
tw686x_get_format(video_drvdata(file), f);
return 0;
}
static int tw686x_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct tw686x_video_channel *vc = video_drvdata(file);
tw686x_get_format(vc, f);
vc->format = format_by_fourcc(f->fmt.pix.pixelformat);
vc->field = f->fmt.pix.field;
vc->width = f->fmt.pix.width;
vc->height = f->fmt.pix.height;
return 0;
}
static int tw686x_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
struct tw686x_video_channel *vc = video_drvdata(file);
struct tw686x_dev *dev = vc->dev;
strcpy(cap->driver, "tw686x-kh");
strcpy(cap->card, dev->name);
sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci_dev));
cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
static int tw686x_s_std(struct file *file, void *priv, v4l2_std_id id)
{
struct tw686x_video_channel *vc = video_drvdata(file);
unsigned int cnt;
u32 sdt = 0; /* default */
for (cnt = 0; cnt < ARRAY_SIZE(video_standards); cnt++)
if (id & video_standards[cnt]) {
sdt = cnt;
break;
}
reg_write(vc->dev, SDT[vc->ch], sdt);
vc->video_standard = video_standards[sdt];
return 0;
}
static int tw686x_g_std(struct file *file, void *priv, v4l2_std_id *id)
{
struct tw686x_video_channel *vc = video_drvdata(file);
*id = vc->video_standard;
return 0;
}
static int tw686x_enum_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
if (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 tw686x_g_parm(struct file *file, void *priv,
struct v4l2_streamparm *sp)
{
struct tw686x_video_channel *vc = video_drvdata(file);
if (sp->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
memset(&sp->parm.capture, 0, sizeof(sp->parm.capture));
sp->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
v4l2_video_std_frame_period(vc->video_standard,
&sp->parm.capture.timeperframe);
return 0;
}
static int tw686x_enum_input(struct file *file, void *priv,
struct v4l2_input *inp)
{
/* the chip has internal multiplexer, support can be added
if the actual hw uses it */
if (inp->index)
return -EINVAL;
snprintf(inp->name, sizeof(inp->name), "Composite");
inp->type = V4L2_INPUT_TYPE_CAMERA;
inp->std = V4L2_STD_ALL;
inp->capabilities = V4L2_IN_CAP_STD;
return 0;
}
static int tw686x_g_input(struct file *file, void *priv, unsigned int *v)
{
*v = 0;
return 0;
}
static int tw686x_s_input(struct file *file, void *priv, unsigned int v)
{
if (v)
return -EINVAL;
return 0;
}
static const struct v4l2_file_operations tw686x_video_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.unlocked_ioctl = video_ioctl2,
.release = vb2_fop_release,
.poll = vb2_fop_poll,
.read = vb2_fop_read,
.mmap = vb2_fop_mmap,
};
static const struct v4l2_ioctl_ops tw686x_video_ioctl_ops = {
.vidioc_querycap = tw686x_querycap,
.vidioc_enum_fmt_vid_cap = tw686x_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = tw686x_g_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = tw686x_s_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = tw686x_try_fmt_vid_cap,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
.vidioc_g_std = tw686x_g_std,
.vidioc_s_std = tw686x_s_std,
.vidioc_g_parm = tw686x_g_parm,
.vidioc_enum_input = tw686x_enum_input,
.vidioc_g_input = tw686x_g_input,
.vidioc_s_input = tw686x_s_input,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
static int video_thread(void *arg)
{
struct tw686x_dev *dev = arg;
DECLARE_WAITQUEUE(wait, current);
set_freezable();
add_wait_queue(&dev->video_thread_wait, &wait);
while (1) {
long timeout = schedule_timeout_interruptible(HZ);
unsigned int ch;
if (timeout == -ERESTARTSYS || kthread_should_stop())
break;
for (ch = 0; ch < max_channels(dev); ch++) {
struct tw686x_video_channel *vc;
unsigned long flags;
u32 request, n, stat = VB2_BUF_STATE_DONE;
vc = &dev->video_channels[ch];
if (!(dev->video_active & (1 << ch)))
continue;
spin_lock_irq(&dev->irq_lock);
request = dev->dma_requests & (0x01000001 << ch);
if (request)
dev->dma_requests &= ~request;
spin_unlock_irq(&dev->irq_lock);
if (!request)
continue;
request >>= ch;
/* handle channel events */
if ((request & 0x01000000) |
(reg_read(dev, VIDEO_FIFO_STATUS) & (0x01010001 << ch)) |
(reg_read(dev, VIDEO_PARSER_STATUS) & (0x00000101 << ch))) {
/* DMA Errors - reset channel */
u32 reg;
spin_lock_irqsave(&dev->irq_lock, flags);
reg = reg_read(dev, DMA_CMD);
/* Reset DMA channel */
reg_write(dev, DMA_CMD, reg & ~(1 << ch));
reg_write(dev, DMA_CMD, reg);
spin_unlock_irqrestore(&dev->irq_lock, flags);
stat = VB2_BUF_STATE_ERROR;
}
/* handle video stream */
mutex_lock(&vc->vb_mutex);
spin_lock(&vc->qlock);
n = !!(reg_read(dev, PB_STATUS) & (1 << ch));
if (vc->curr_bufs[n]) {
struct vb2_v4l2_buffer *vb;
vb = &vc->curr_bufs[n]->vb;
vb->vb2_buf.timestamp = ktime_get_ns();
vb->field = vc->field;
if (V4L2_FIELD_HAS_BOTH(vc->field))
vb->sequence = vc->seq++;
else
vb->sequence = (vc->seq++) / 2;
vb2_set_plane_payload(&vb->vb2_buf, 0,
vc->width * vc->height * vc->format->depth / 8);
vb2_buffer_done(&vb->vb2_buf, stat);
}
setup_descs(vc, n);
spin_unlock(&vc->qlock);
mutex_unlock(&vc->vb_mutex);
}
try_to_freeze();
}
remove_wait_queue(&dev->video_thread_wait, &wait);
return 0;
}
int tw686x_kh_video_irq(struct tw686x_dev *dev)
{
unsigned long flags, handled = 0;
u32 requests;
spin_lock_irqsave(&dev->irq_lock, flags);
requests = dev->dma_requests;
spin_unlock_irqrestore(&dev->irq_lock, flags);
if (requests & dev->video_active) {
wake_up_interruptible_all(&dev->video_thread_wait);
handled = 1;
}
return handled;
}
void tw686x_kh_video_free(struct tw686x_dev *dev)
{
unsigned int ch, n;
if (dev->video_thread)
kthread_stop(dev->video_thread);
for (ch = 0; ch < max_channels(dev); ch++) {
struct tw686x_video_channel *vc = &dev->video_channels[ch];
v4l2_ctrl_handler_free(&vc->ctrl_handler);
if (vc->device)
video_unregister_device(vc->device);
vb2_dma_sg_cleanup_ctx(vc->alloc_ctx);
for (n = 0; n < 2; n++) {
struct dma_desc *descs = &vc->sg_tables[n];
if (descs->virt)
pci_free_consistent(dev->pci_dev, descs->size,
descs->virt, descs->phys);
}
}
v4l2_device_unregister(&dev->v4l2_dev);
}
#define SG_TABLE_SIZE (MAX_SG_DESC_COUNT * sizeof(struct vdma_desc))
int tw686x_kh_video_init(struct tw686x_dev *dev)
{
unsigned int ch, n;
int err;
init_waitqueue_head(&dev->video_thread_wait);
err = v4l2_device_register(&dev->pci_dev->dev, &dev->v4l2_dev);
if (err)
return err;
reg_write(dev, VIDEO_CONTROL1, 0); /* NTSC, disable scaler */
reg_write(dev, PHASE_REF, 0x00001518); /* Scatter-gather DMA mode */
/* setup required SG table sizes */
for (n = 0; n < 2; n++)
if (is_second_gen(dev)) {
/* TW 6865, TW6869 - each channel needs a pair of
descriptor tables */
for (ch = 0; ch < max_channels(dev); ch++)
dev->video_channels[ch].sg_tables[n].size =
SG_TABLE_SIZE;
} else
/* TW 6864, TW6868 - we need to allocate a pair of
descriptor tables, common for all channels.
Each table will be bigger than 4 KB. */
dev->video_channels[0].sg_tables[n].size =
max_channels(dev) * SG_TABLE_SIZE;
/* allocate SG tables and initialize video channels */
for (ch = 0; ch < max_channels(dev); ch++) {
struct tw686x_video_channel *vc = &dev->video_channels[ch];
struct video_device *vdev;
mutex_init(&vc->vb_mutex);
spin_lock_init(&vc->qlock);
INIT_LIST_HEAD(&vc->vidq_queued);
vc->dev = dev;
vc->ch = ch;
/* default settings: NTSC */
vc->format = &formats[0];
vc->video_standard = V4L2_STD_NTSC;
reg_write(vc->dev, SDT[vc->ch], 0);
vc->field = V4L2_FIELD_SEQ_BT;
vc->width = 704;
vc->height = 480;
for (n = 0; n < 2; n++) {
void *cpu;
if (vc->sg_tables[n].size) {
unsigned int reg = n ? DMA_PAGE_TABLE1_ADDR[ch] :
DMA_PAGE_TABLE0_ADDR[ch];
cpu = pci_alloc_consistent(dev->pci_dev,
vc->sg_tables[n].size,
&vc->sg_tables[n].phys);
if (!cpu) {
pr_err("Error allocating video DMA scatter-gather tables\n");
err = -ENOMEM;
goto error;
}
vc->sg_tables[n].virt = cpu;
reg_write(dev, reg, vc->sg_tables[n].phys);
} else
cpu = dev->video_channels[0].sg_tables[n].virt +
ch * SG_TABLE_SIZE;
vc->sg_descs[n] = cpu;
}
reg_write(dev, VCTRL1[0], 0x24);
reg_write(dev, LOOP[0], 0xA5);
if (max_channels(dev) > 4) {
reg_write(dev, VCTRL1[1], 0x24);
reg_write(dev, LOOP[1], 0xA5);
}
reg_write(dev, VIDEO_FIELD_CTRL[ch], 0);
reg_write(dev, VDELAY_LO[ch], 0x14);
vdev = video_device_alloc();
if (!vdev) {
pr_warn("Unable to allocate video device\n");
err = -ENOMEM;
goto error;
}
vc->alloc_ctx = vb2_dma_sg_init_ctx(&dev->pci_dev->dev);
if (IS_ERR(vc->alloc_ctx)) {
pr_warn("Unable to initialize DMA scatter-gather context\n");
err = PTR_ERR(vc->alloc_ctx);
goto error;
}
vc->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vc->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
vc->vidq.drv_priv = vc;
vc->vidq.buf_struct_size = sizeof(struct tw686x_vb2_buf);
vc->vidq.ops = &tw686x_video_qops;
vc->vidq.mem_ops = &vb2_dma_sg_memops;
vc->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
vc->vidq.min_buffers_needed = 2;
vc->vidq.lock = &vc->vb_mutex;
vc->vidq.gfp_flags = GFP_DMA32;
err = vb2_queue_init(&vc->vidq);
if (err)
goto error;
strcpy(vdev->name, "TW686x-video");
snprintf(vdev->name, sizeof(vdev->name), "%s video", dev->name);
vdev->fops = &tw686x_video_fops;
vdev->ioctl_ops = &tw686x_video_ioctl_ops;
vdev->release = video_device_release;
vdev->v4l2_dev = &dev->v4l2_dev;
vdev->queue = &vc->vidq;
vdev->tvnorms = V4L2_STD_ALL;
vdev->minor = -1;
vdev->lock = &vc->vb_mutex;
dev->video_channels[ch].device = vdev;
video_set_drvdata(vdev, vc);
err = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
if (err < 0)
goto error;
v4l2_ctrl_handler_init(&vc->ctrl_handler,
4 /* number of controls */);
vdev->ctrl_handler = &vc->ctrl_handler;
v4l2_ctrl_new_std(&vc->ctrl_handler, &ctrl_ops,
V4L2_CID_BRIGHTNESS, -128, 127, 1, 0);
v4l2_ctrl_new_std(&vc->ctrl_handler, &ctrl_ops,
V4L2_CID_CONTRAST, 0, 255, 1, 64);
v4l2_ctrl_new_std(&vc->ctrl_handler, &ctrl_ops,
V4L2_CID_SATURATION, 0, 255, 1, 128);
v4l2_ctrl_new_std(&vc->ctrl_handler, &ctrl_ops, V4L2_CID_HUE,
-124, 127, 1, 0);
err = vc->ctrl_handler.error;
if (err)
goto error;
v4l2_ctrl_handler_setup(&vc->ctrl_handler);
}
dev->video_thread = kthread_run(video_thread, dev, "tw686x_video");
if (IS_ERR(dev->video_thread)) {
err = PTR_ERR(dev->video_thread);
goto error;
}
return 0;
error:
tw686x_kh_video_free(dev);
return err;
}