Commit 0ff59f31 authored by Krzysztof Hałasa's avatar Krzysztof Hałasa Committed by Mauro Carvalho Chehab

[media] TW686x frame grabber driver

A driver for Intersil/Techwell TW686x-based PCIe frame grabbers.

[hans.verkuil@cisco.com: renamed staging tw686x to tw686x-kh to prevent naming conflicts]
[hans.verkuil@cisco.com: don't build tw686x-kh if tw686x is already selected to prevent conflicts]
[mchehab@osg.samsung.com: use "unsigned int" instead of just "unsigned"  and add some whitespaces to make checkpatch happier]
Signed-off-by: default avatarKrzysztof Halasa <khalasa@piap.pl>
Signed-off-by: default avatarHans Verkuil <hans.verkuil@cisco.com>
Tested-by: default avatarHans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@osg.samsung.com>
parent 5ed470fe
...@@ -37,6 +37,8 @@ source "drivers/staging/media/omap4iss/Kconfig" ...@@ -37,6 +37,8 @@ source "drivers/staging/media/omap4iss/Kconfig"
source "drivers/staging/media/timb/Kconfig" source "drivers/staging/media/timb/Kconfig"
source "drivers/staging/media/tw686x-kh/Kconfig"
# Keep LIRC at the end, as it has sub-menus # Keep LIRC at the end, as it has sub-menus
source "drivers/staging/media/lirc/Kconfig" source "drivers/staging/media/lirc/Kconfig"
......
...@@ -8,3 +8,4 @@ obj-$(CONFIG_VIDEO_OMAP1) += omap1/ ...@@ -8,3 +8,4 @@ obj-$(CONFIG_VIDEO_OMAP1) += omap1/
obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/ obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/
obj-$(CONFIG_DVB_MN88472) += mn88472/ obj-$(CONFIG_DVB_MN88472) += mn88472/
obj-$(CONFIG_VIDEO_TIMBERDALE) += timb/ obj-$(CONFIG_VIDEO_TIMBERDALE) += timb/
obj-$(CONFIG_VIDEO_TW686X_KH) += tw686x-kh/
config VIDEO_TW686X_KH
tristate "Intersil/Techwell TW686x Video For Linux"
depends on VIDEO_DEV && PCI && VIDEO_V4L2
depends on !(VIDEO_TW686X=y || VIDEO_TW686X=m) || COMPILE_TEST
select VIDEOBUF2_DMA_SG
help
Support for Intersil/Techwell TW686x-based frame grabber cards.
Currently supported chips:
- TW6864 (4 video channels),
- TW6865 (4 video channels, not tested, second generation chip),
- TW6868 (8 video channels but only 4 first channels using
built-in video decoder are supported, not tested),
- TW6869 (8 video channels, second generation chip).
To compile this driver as a module, choose M here: the module
will be named tw686x-kh.
tw686x-kh-objs := tw686x-kh-core.o tw686x-kh-video.o
obj-$(CONFIG_VIDEO_TW686X_KH) += tw686x-kh.o
TODO: implement V4L2_FIELD_INTERLACED* mode(s).
Please Cc: patches to Krzysztof Halasa <khalasa@piap.pl>.
/*
* 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/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include "tw686x-kh.h"
#include "tw686x-kh-regs.h"
static irqreturn_t tw686x_irq(int irq, void *dev_id)
{
struct tw686x_dev *dev = (struct tw686x_dev *)dev_id;
u32 int_status = reg_read(dev, INT_STATUS); /* cleared on read */
unsigned long flags;
unsigned int handled = 0;
if (int_status) {
spin_lock_irqsave(&dev->irq_lock, flags);
dev->dma_requests |= int_status;
spin_unlock_irqrestore(&dev->irq_lock, flags);
if (int_status & 0xFF0000FF)
handled = tw686x_video_irq(dev);
}
return IRQ_RETVAL(handled);
}
static int tw686x_probe(struct pci_dev *pci_dev,
const struct pci_device_id *pci_id)
{
struct tw686x_dev *dev;
int err;
dev = devm_kzalloc(&pci_dev->dev, sizeof(*dev) +
(pci_id->driver_data & TYPE_MAX_CHANNELS) *
sizeof(dev->video_channels[0]), GFP_KERNEL);
if (!dev)
return -ENOMEM;
sprintf(dev->name, "TW%04X", pci_dev->device);
dev->type = pci_id->driver_data;
pr_info("%s: PCI %s, IRQ %d, MMIO 0x%lx\n", dev->name,
pci_name(pci_dev), pci_dev->irq,
(unsigned long)pci_resource_start(pci_dev, 0));
dev->pci_dev = pci_dev;
if (pcim_enable_device(pci_dev))
return -EIO;
pci_set_master(pci_dev);
if (pci_set_dma_mask(pci_dev, DMA_BIT_MASK(32))) {
pr_err("%s: 32-bit PCI DMA not supported\n", dev->name);
return -EIO;
}
err = pci_request_regions(pci_dev, dev->name);
if (err < 0) {
pr_err("%s: Unable to get MMIO region\n", dev->name);
return err;
}
dev->mmio = pci_ioremap_bar(pci_dev, 0);
if (!dev->mmio) {
pr_err("%s: Unable to remap MMIO region\n", dev->name);
return -EIO;
}
reg_write(dev, SYS_SOFT_RST, 0x0F); /* Reset all subsystems */
mdelay(1);
reg_write(dev, SRST[0], 0x3F);
if (max_channels(dev) > 4)
reg_write(dev, SRST[1], 0x3F);
reg_write(dev, DMA_CMD, 0);
reg_write(dev, DMA_CHANNEL_ENABLE, 0);
reg_write(dev, DMA_CHANNEL_TIMEOUT, 0x3EFF0FF0);
reg_write(dev, DMA_TIMER_INTERVAL, 0x38000);
reg_write(dev, DMA_CONFIG, 0xFFFFFF04);
spin_lock_init(&dev->irq_lock);
err = devm_request_irq(&pci_dev->dev, pci_dev->irq, tw686x_irq,
IRQF_SHARED, dev->name, dev);
if (err < 0) {
pr_err("%s: Unable to get IRQ\n", dev->name);
return err;
}
err = tw686x_video_init(dev);
if (err)
return err;
pci_set_drvdata(pci_dev, dev);
return 0;
}
static void tw686x_remove(struct pci_dev *pci_dev)
{
struct tw686x_dev *dev = pci_get_drvdata(pci_dev);
tw686x_video_free(dev);
}
/* driver_data is number of A/V channels */
static const struct pci_device_id tw686x_pci_tbl[] = {
{PCI_DEVICE(0x1797, 0x6864), .driver_data = 4},
/* not tested */
{PCI_DEVICE(0x1797, 0x6865), .driver_data = 4 | TYPE_SECOND_GEN},
/* TW6868 supports 8 A/V channels with an external TW2865 chip -
not supported by the driver */
{PCI_DEVICE(0x1797, 0x6868), .driver_data = 4}, /* not tested */
{PCI_DEVICE(0x1797, 0x6869), .driver_data = 8 | TYPE_SECOND_GEN},
{}
};
static struct pci_driver tw686x_pci_driver = {
.name = "tw686x-kh",
.id_table = tw686x_pci_tbl,
.probe = tw686x_probe,
.remove = tw686x_remove,
};
MODULE_DESCRIPTION("Driver for video frame grabber cards based on Intersil/Techwell TW686[4589]");
MODULE_AUTHOR("Krzysztof Halasa");
MODULE_LICENSE("GPL v2");
MODULE_DEVICE_TABLE(pci, tw686x_pci_tbl);
module_pci_driver(tw686x_pci_driver);
/* DMA controller registers */
#define REG8_1(a0) ((const u16[8]) {a0, a0 + 1, a0 + 2, a0 + 3, \
a0 + 4, a0 + 5, a0 + 6, a0 + 7})
#define REG8_2(a0) ((const u16[8]) {a0, a0 + 2, a0 + 4, a0 + 6, \
a0 + 8, a0 + 0xA, a0 + 0xC, a0 + 0xE})
#define REG8_8(a0) ((const u16[8]) {a0, a0 + 8, a0 + 0x10, a0 + 0x18, \
a0 + 0x20, a0 + 0x28, a0 + 0x30, a0 + 0x38})
#define INT_STATUS 0x00
#define PB_STATUS 0x01
#define DMA_CMD 0x02
#define VIDEO_FIFO_STATUS 0x03
#define VIDEO_CHANNEL_ID 0x04
#define VIDEO_PARSER_STATUS 0x05
#define SYS_SOFT_RST 0x06
#define DMA_PAGE_TABLE0_ADDR ((const u16[8]) {0x08, 0xD0, 0xD2, 0xD4, \
0xD6, 0xD8, 0xDA, 0xDC})
#define DMA_PAGE_TABLE1_ADDR ((const u16[8]) {0x09, 0xD1, 0xD3, 0xD5, \
0xD7, 0xD9, 0xDB, 0xDD})
#define DMA_CHANNEL_ENABLE 0x0A
#define DMA_CONFIG 0x0B
#define DMA_TIMER_INTERVAL 0x0C
#define DMA_CHANNEL_TIMEOUT 0x0D
#define VDMA_CHANNEL_CONFIG REG8_1(0x10)
#define ADMA_P_ADDR REG8_2(0x18)
#define ADMA_B_ADDR REG8_2(0x19)
#define DMA10_P_ADDR 0x28 /* ??? */
#define DMA10_B_ADDR 0x29
#define VIDEO_CONTROL1 0x2A
#define VIDEO_CONTROL2 0x2B
#define AUDIO_CONTROL1 0x2C
#define AUDIO_CONTROL2 0x2D
#define PHASE_REF 0x2E
#define GPIO_REG 0x2F
#define INTL_HBAR_CTRL REG8_1(0x30)
#define AUDIO_CONTROL3 0x38
#define VIDEO_FIELD_CTRL REG8_1(0x39)
#define HSCALER_CTRL REG8_1(0x42)
#define VIDEO_SIZE REG8_1(0x4A)
#define VIDEO_SIZE_F2 REG8_1(0x52)
#define MD_CONF REG8_1(0x60)
#define MD_INIT REG8_1(0x68)
#define MD_MAP0 REG8_1(0x70)
#define VDMA_P_ADDR REG8_8(0x80) /* not used in DMA SG mode */
#define VDMA_WHP REG8_8(0x81)
#define VDMA_B_ADDR REG8_8(0x82)
#define VDMA_F2_P_ADDR REG8_8(0x84)
#define VDMA_F2_WHP REG8_8(0x85)
#define VDMA_F2_B_ADDR REG8_8(0x86)
#define EP_REG_ADDR 0xFE
#define EP_REG_DATA 0xFF
/* Video decoder registers */
#define VDREG8(a0) ((const u16[8]) { \
a0 + 0x000, a0 + 0x010, a0 + 0x020, a0 + 0x030, \
a0 + 0x100, a0 + 0x110, a0 + 0x120, a0 + 0x130})
#define VIDSTAT VDREG8(0x100)
#define BRIGHT VDREG8(0x101)
#define CONTRAST VDREG8(0x102)
#define SHARPNESS VDREG8(0x103)
#define SAT_U VDREG8(0x104)
#define SAT_V VDREG8(0x105)
#define HUE VDREG8(0x106)
#define CROP_HI VDREG8(0x107)
#define VDELAY_LO VDREG8(0x108)
#define VACTIVE_LO VDREG8(0x109)
#define HDELAY_LO VDREG8(0x10A)
#define HACTIVE_LO VDREG8(0x10B)
#define MVSN VDREG8(0x10C)
#define STATUS2 VDREG8(0x10C)
#define SDT VDREG8(0x10E)
#define SDT_EN VDREG8(0x10F)
#define VSCALE_LO VDREG8(0x144)
#define SCALE_HI VDREG8(0x145)
#define HSCALE_LO VDREG8(0x146)
#define F2CROP_HI VDREG8(0x147)
#define F2VDELAY_LO VDREG8(0x148)
#define F2VACTIVE_LO VDREG8(0x149)
#define F2HDELAY_LO VDREG8(0x14A)
#define F2HACTIVE_LO VDREG8(0x14B)
#define F2VSCALE_LO VDREG8(0x14C)
#define F2SCALE_HI VDREG8(0x14D)
#define F2HSCALE_LO VDREG8(0x14E)
#define F2CNT VDREG8(0x14F)
#define VDREG2(a0) ((const u16[2]) {a0, a0 + 0x100})
#define SRST VDREG2(0x180)
#define ACNTL VDREG2(0x181)
#define ACNTL2 VDREG2(0x182)
#define CNTRL1 VDREG2(0x183)
#define CKHY VDREG2(0x184)
#define SHCOR VDREG2(0x185)
#define CORING VDREG2(0x186)
#define CLMPG VDREG2(0x187)
#define IAGC VDREG2(0x188)
#define VCTRL1 VDREG2(0x18F)
#define MISC1 VDREG2(0x194)
#define LOOP VDREG2(0x195)
#define MISC2 VDREG2(0x196)
#define CLMD VDREG2(0x197)
#define AIGAIN ((const u16[8]) {0x1D0, 0x1D1, 0x1D2, 0x1D3, \
0x2D0, 0x2D1, 0x2D2, 0x2D3})
/*
* 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;
}
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,
};
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_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 (dev->dma_requests & dev->video_active) {
wake_up_interruptible_all(&dev->video_thread_wait);
handled = 1;
}
return handled;
}
void tw686x_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_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;
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_video_free(dev);
return err;
}
/*
* 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/delay.h>
#include <linux/freezer.h>
#include <linux/interrupt.h>
#include <linux/kthread.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <media/videobuf2-dma-sg.h>
#include <linux/videodev2.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#define TYPE_MAX_CHANNELS 0x0F
#define TYPE_SECOND_GEN 0x10
struct tw686x_format {
char *name;
unsigned int fourcc;
unsigned int depth;
unsigned int mode;
};
struct dma_desc {
dma_addr_t phys;
void *virt;
unsigned int size;
};
struct vdma_desc {
__le32 flags_length; /* 3 MSBits for flags, 13 LSBits for length */
__le32 phys;
};
struct tw686x_vb2_buf {
struct vb2_v4l2_buffer vb;
struct list_head list;
};
struct tw686x_video_channel {
struct tw686x_dev *dev;
struct vb2_queue vidq;
struct list_head vidq_queued;
struct video_device *device;
struct dma_desc sg_tables[2];
struct tw686x_vb2_buf *curr_bufs[2];
void *alloc_ctx;
struct vdma_desc *sg_descs[2];
struct v4l2_ctrl_handler ctrl_handler;
const struct tw686x_format *format;
struct mutex vb_mutex;
spinlock_t qlock;
v4l2_std_id video_standard;
unsigned int width, height;
enum v4l2_field field; /* supported TOP, BOTTOM, SEQ_TB and SEQ_BT */
unsigned int seq; /* video field or frame counter */
unsigned int ch;
};
/* global device status */
struct tw686x_dev {
spinlock_t irq_lock;
struct v4l2_device v4l2_dev;
struct snd_card *card; /* sound card */
unsigned int video_active; /* active video channel mask */
char name[32];
unsigned int type;
struct pci_dev *pci_dev;
__u32 __iomem *mmio;
struct task_struct *video_thread;
wait_queue_head_t video_thread_wait;
u32 dma_requests;
struct tw686x_video_channel video_channels[0];
};
static inline uint32_t reg_read(struct tw686x_dev *dev, unsigned int reg)
{
return readl(dev->mmio + reg);
}
static inline void reg_write(struct tw686x_dev *dev, unsigned int reg,
uint32_t value)
{
writel(value, dev->mmio + reg);
}
static inline unsigned int max_channels(struct tw686x_dev *dev)
{
return dev->type & TYPE_MAX_CHANNELS; /* 4 or 8 channels */
}
static inline unsigned int is_second_gen(struct tw686x_dev *dev)
{
/* each channel has its own DMA SG table */
return dev->type & TYPE_SECOND_GEN;
}
int tw686x_video_irq(struct tw686x_dev *dev);
int tw686x_video_init(struct tw686x_dev *dev);
void tw686x_video_free(struct tw686x_dev *dev);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment