diff options
Diffstat (limited to 'drivers/staging/media/tw686x-kh/tw686x-kh-video.c')
-rw-r--r-- | drivers/staging/media/tw686x-kh/tw686x-kh-video.c | 821 |
1 files changed, 821 insertions, 0 deletions
diff --git a/drivers/staging/media/tw686x-kh/tw686x-kh-video.c b/drivers/staging/media/tw686x-kh/tw686x-kh-video.c new file mode 100644 index 000000000000..6ecb504a79f9 --- /dev/null +++ b/drivers/staging/media/tw686x-kh/tw686x-kh-video.c @@ -0,0 +1,821 @@ +/* + * 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; +} |