linux-media.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/4] tw686x drivers
@ 2016-03-21 11:41 Hans Verkuil
  2016-03-21 11:41 ` [PATCH 1/4] TW686x frame grabber driver Hans Verkuil
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Hans Verkuil @ 2016-03-21 11:41 UTC (permalink / raw)
  To: linux-media; +Cc: Krzysztof Halasa, Ezequiel Garcia

From: Hans Verkuil <hans.verkuil@cisco.com>

Even though the two tw686x drivers have been posted before I thought I'd
post them again before I make a pull request due to the fact that I had
to make a few changes to the staging driver and because of the unusual
circumstances.

Krzysztof, I renamed the driver (and sources) to tw686x-kh to prevent
conflicts with the name of Ezequiel's driver.

I also prevent it from being built if VIDEO_TW686X is already selected
so we don't install two drivers for the same hardware.

I also added two patches: the first adds the GFP_DMA32 flag to ensure
the DMA buffers are in 32 bit memory (you probably never tested it on
a 64 bit system). The second mentions adds audio support to the TODO list.

For those who haven't paid attention:

We've ended up with two drivers: one only supports V4L2_FIELD_SEQ_BT,
FIELD_TOP and FIELD_BOTTOM interlaced modes and no audio, the other only
supports FIELD_INTERLACED by way of a memcpy and has audio support.

Part of the reason is weird hardware design that has different DMA modes
depending on the field settings. Krzysztof didn't need FIELD_INTERLACED,
so he never implemented that, and unfortunately when Ezequiel took his
code as starting point he only implemented the FIELD_INTERLACED format.
The memcpy was needed due to unstable DMA when using the DMA mode that
can do FIELD_INTERLACED. It is not known at this time if that unstable
behavior is specific to the hardware Ezequiel is using or if it is
inherent to the tw686x.

In the ideal world both feature sets should be merged into one driver.

But for now I decided to add Ezequiel's driver to the mainline and
Krzysztof's driver to staging. The reason for moving Ezequiel's driver
to mainline is that application support for FIELD_INTERLACED is standard,
whereas FIELD_TOP/BOTTOM/SEQ_BT is pretty rare. In addition, Ezequiel's
driver has audio support.

My hope is that someone will merge the feature sets and we can get rid
of one of the two drivers.

I have tested both drivers with this card:

http://www.nanzoom.com/product/nz-2108E.shtml

(available on ebay)

If there are no comments, then I'll make a pull request, probably next
week.

Regards,

	Hans

Ezequiel Garcia (1):
  media: Support Intersil/Techwell TW686x-based video capture cards

Hans Verkuil (2):
  tw686x-kh: specify that the DMA is 32 bits
  tw686x-kh: add audio support to the TODO list

Krzysztof Hałasa (1):
  TW686x frame grabber driver

 MAINTAINERS                                       |   8 +
 drivers/media/pci/Kconfig                         |   1 +
 drivers/media/pci/Makefile                        |   1 +
 drivers/media/pci/tw686x/Kconfig                  |  18 +
 drivers/media/pci/tw686x/Makefile                 |   3 +
 drivers/media/pci/tw686x/tw686x-audio.c           | 386 +++++++++
 drivers/media/pci/tw686x/tw686x-core.c            | 415 ++++++++++
 drivers/media/pci/tw686x/tw686x-regs.h            | 122 +++
 drivers/media/pci/tw686x/tw686x-video.c           | 927 ++++++++++++++++++++++
 drivers/media/pci/tw686x/tw686x.h                 | 158 ++++
 drivers/staging/media/Kconfig                     |   2 +
 drivers/staging/media/Makefile                    |   1 +
 drivers/staging/media/tw686x-kh/Kconfig           |  17 +
 drivers/staging/media/tw686x-kh/Makefile          |   3 +
 drivers/staging/media/tw686x-kh/TODO              |   6 +
 drivers/staging/media/tw686x-kh/tw686x-kh-core.c  | 140 ++++
 drivers/staging/media/tw686x-kh/tw686x-kh-regs.h  | 103 +++
 drivers/staging/media/tw686x-kh/tw686x-kh-video.c | 821 +++++++++++++++++++
 drivers/staging/media/tw686x-kh/tw686x-kh.h       | 118 +++
 19 files changed, 3250 insertions(+)
 create mode 100644 drivers/media/pci/tw686x/Kconfig
 create mode 100644 drivers/media/pci/tw686x/Makefile
 create mode 100644 drivers/media/pci/tw686x/tw686x-audio.c
 create mode 100644 drivers/media/pci/tw686x/tw686x-core.c
 create mode 100644 drivers/media/pci/tw686x/tw686x-regs.h
 create mode 100644 drivers/media/pci/tw686x/tw686x-video.c
 create mode 100644 drivers/media/pci/tw686x/tw686x.h
 create mode 100644 drivers/staging/media/tw686x-kh/Kconfig
 create mode 100644 drivers/staging/media/tw686x-kh/Makefile
 create mode 100644 drivers/staging/media/tw686x-kh/TODO
 create mode 100644 drivers/staging/media/tw686x-kh/tw686x-kh-core.c
 create mode 100644 drivers/staging/media/tw686x-kh/tw686x-kh-regs.h
 create mode 100644 drivers/staging/media/tw686x-kh/tw686x-kh-video.c
 create mode 100644 drivers/staging/media/tw686x-kh/tw686x-kh.h

-- 
2.7.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/4] TW686x frame grabber driver
  2016-03-21 11:41 [PATCH 0/4] tw686x drivers Hans Verkuil
@ 2016-03-21 11:41 ` Hans Verkuil
  2016-03-21 11:41 ` [PATCH 2/4] media: Support Intersil/Techwell TW686x-based video capture cards Hans Verkuil
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 6+ messages in thread
From: Hans Verkuil @ 2016-03-21 11:41 UTC (permalink / raw)
  To: linux-media; +Cc: Krzysztof Halasa, Ezequiel Garcia, Hans Verkuil

From: Krzysztof Hałasa <khalasa@piap.pl>

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

Signed-off-by: Krzysztof Halasa <khalasa@piap.pl>
Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Tested-by: Hans Verkuil <hans.verkuil@cisco.com>
[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]
---
 drivers/staging/media/Kconfig                     |   2 +
 drivers/staging/media/Makefile                    |   1 +
 drivers/staging/media/tw686x-kh/Kconfig           |  17 +
 drivers/staging/media/tw686x-kh/Makefile          |   3 +
 drivers/staging/media/tw686x-kh/TODO              |   3 +
 drivers/staging/media/tw686x-kh/tw686x-kh-core.c  | 140 ++++
 drivers/staging/media/tw686x-kh/tw686x-kh-regs.h  | 103 +++
 drivers/staging/media/tw686x-kh/tw686x-kh-video.c | 820 ++++++++++++++++++++++
 drivers/staging/media/tw686x-kh/tw686x-kh.h       | 118 ++++
 9 files changed, 1207 insertions(+)
 create mode 100644 drivers/staging/media/tw686x-kh/Kconfig
 create mode 100644 drivers/staging/media/tw686x-kh/Makefile
 create mode 100644 drivers/staging/media/tw686x-kh/TODO
 create mode 100644 drivers/staging/media/tw686x-kh/tw686x-kh-core.c
 create mode 100644 drivers/staging/media/tw686x-kh/tw686x-kh-regs.h
 create mode 100644 drivers/staging/media/tw686x-kh/tw686x-kh-video.c
 create mode 100644 drivers/staging/media/tw686x-kh/tw686x-kh.h

diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig
index 0078b6a..de7e9f5 100644
--- a/drivers/staging/media/Kconfig
+++ b/drivers/staging/media/Kconfig
@@ -37,6 +37,8 @@ source "drivers/staging/media/omap4iss/Kconfig"
 
 source "drivers/staging/media/timb/Kconfig"
 
+source "drivers/staging/media/tw686x-kh/Kconfig"
+
 # Keep LIRC at the end, as it has sub-menus
 source "drivers/staging/media/lirc/Kconfig"
 
diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile
index 9149588..60a35b3 100644
--- a/drivers/staging/media/Makefile
+++ b/drivers/staging/media/Makefile
@@ -8,3 +8,4 @@ obj-$(CONFIG_VIDEO_OMAP1)	+= omap1/
 obj-$(CONFIG_VIDEO_OMAP4)	+= omap4iss/
 obj-$(CONFIG_DVB_MN88472)       += mn88472/
 obj-$(CONFIG_VIDEO_TIMBERDALE)  += timb/
+obj-$(CONFIG_VIDEO_TW686X_KH)	+= tw686x-kh/
diff --git a/drivers/staging/media/tw686x-kh/Kconfig b/drivers/staging/media/tw686x-kh/Kconfig
new file mode 100644
index 0000000..6264d30
--- /dev/null
+++ b/drivers/staging/media/tw686x-kh/Kconfig
@@ -0,0 +1,17 @@
+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.
diff --git a/drivers/staging/media/tw686x-kh/Makefile b/drivers/staging/media/tw686x-kh/Makefile
new file mode 100644
index 0000000..2a36a38
--- /dev/null
+++ b/drivers/staging/media/tw686x-kh/Makefile
@@ -0,0 +1,3 @@
+tw686x-kh-objs := tw686x-kh-core.o tw686x-kh-video.o
+
+obj-$(CONFIG_VIDEO_TW686X_KH) += tw686x-kh.o
diff --git a/drivers/staging/media/tw686x-kh/TODO b/drivers/staging/media/tw686x-kh/TODO
new file mode 100644
index 0000000..f226e80
--- /dev/null
+++ b/drivers/staging/media/tw686x-kh/TODO
@@ -0,0 +1,3 @@
+TODO: implement V4L2_FIELD_INTERLACED* mode(s).
+
+Please Cc: patches to Krzysztof Halasa <khalasa@piap.pl>.
diff --git a/drivers/staging/media/tw686x-kh/tw686x-kh-core.c b/drivers/staging/media/tw686x-kh/tw686x-kh-core.c
new file mode 100644
index 0000000..62cccd1
--- /dev/null
+++ b/drivers/staging/media/tw686x-kh/tw686x-kh-core.c
@@ -0,0 +1,140 @@
+/*
+ * 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 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);
diff --git a/drivers/staging/media/tw686x-kh/tw686x-kh-regs.h b/drivers/staging/media/tw686x-kh/tw686x-kh-regs.h
new file mode 100644
index 0000000..33b492b
--- /dev/null
+++ b/drivers/staging/media/tw686x-kh/tw686x-kh-regs.h
@@ -0,0 +1,103 @@
+/* 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})
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 0000000..fb7a784
--- /dev/null
+++ b/drivers/staging/media/tw686x-kh/tw686x-kh-video.c
@@ -0,0 +1,820 @@
+/*
+ * 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 fourcc)
+{
+	unsigned 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 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 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 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 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 len = sg_dma_len(sg);
+
+			while (len && buf_len) {
+				unsigned entry_len = min_t(unsigned, 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 field_width = 704;
+	unsigned field_height = (vc->video_standard & V4L2_STD_625_50) ?
+		288 : 240;
+	unsigned start_idx = is_second_gen(vc->dev) ? 0 :
+		vc->ch * MAX_SG_DESC_COUNT;
+	unsigned 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 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 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 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 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 *v)
+{
+	*v = 0;
+	return 0;
+}
+
+static int tw686x_s_input(struct file *file, void *priv, unsigned 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 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 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 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 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;
+}
diff --git a/drivers/staging/media/tw686x-kh/tw686x-kh.h b/drivers/staging/media/tw686x-kh/tw686x-kh.h
new file mode 100644
index 0000000..8a16e6e
--- /dev/null
+++ b/drivers/staging/media/tw686x-kh/tw686x-kh.h
@@ -0,0 +1,118 @@
+/*
+ * 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 fourcc;
+	unsigned depth;
+	unsigned mode;
+};
+
+struct dma_desc {
+	dma_addr_t phys;
+	void *virt;
+	unsigned 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 width, height;
+	enum v4l2_field field; /* supported TOP, BOTTOM, SEQ_TB and SEQ_BT */
+	unsigned seq;	       /* video field or frame counter */
+	unsigned ch;
+};
+
+/* global device status */
+struct tw686x_dev {
+	spinlock_t irq_lock;
+
+	struct v4l2_device v4l2_dev;
+	struct snd_card *card;	/* sound card */
+
+	unsigned video_active;	/* active video channel mask */
+
+	char name[32];
+	unsigned 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 reg)
+{
+	return readl(dev->mmio + reg);
+}
+
+static inline void reg_write(struct tw686x_dev *dev, unsigned reg,
+			     uint32_t value)
+{
+	writel(value, dev->mmio + reg);
+}
+
+static inline unsigned max_channels(struct tw686x_dev *dev)
+{
+	return dev->type & TYPE_MAX_CHANNELS; /* 4 or 8 channels */
+}
+
+static inline unsigned 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);
-- 
2.7.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 2/4] media: Support Intersil/Techwell TW686x-based video capture cards
  2016-03-21 11:41 [PATCH 0/4] tw686x drivers Hans Verkuil
  2016-03-21 11:41 ` [PATCH 1/4] TW686x frame grabber driver Hans Verkuil
@ 2016-03-21 11:41 ` Hans Verkuil
  2016-03-22 16:16   ` Ezequiel Garcia
  2016-03-21 11:41 ` [PATCH 3/4] tw686x-kh: specify that the DMA is 32 bits Hans Verkuil
  2016-03-21 11:41 ` [PATCH 4/4] tw686x-kh: add audio support to the TODO list Hans Verkuil
  3 siblings, 1 reply; 6+ messages in thread
From: Hans Verkuil @ 2016-03-21 11:41 UTC (permalink / raw)
  To: linux-media; +Cc: Krzysztof Halasa, Ezequiel Garcia, Hans Verkuil

From: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>

This commit introduces the support for the Techwell TW686x video
capture IC. This hardware supports a few DMA modes, including
scatter-gather and frame (contiguous).

This commit makes little use of the DMA engine and instead has
a memcpy based implementation. DMA frame and scatter-gather modes
support may be added in the future.

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).

Cc: Krzysztof Halasa <khalasa@piap.pl>
Signed-off-by: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>
Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Tested-by: Hans Verkuil <hans.verkuil@cisco.com>
---
 MAINTAINERS                             |   8 +
 drivers/media/pci/Kconfig               |   1 +
 drivers/media/pci/Makefile              |   1 +
 drivers/media/pci/tw686x/Kconfig        |  18 +
 drivers/media/pci/tw686x/Makefile       |   3 +
 drivers/media/pci/tw686x/tw686x-audio.c | 386 +++++++++++++
 drivers/media/pci/tw686x/tw686x-core.c  | 415 ++++++++++++++
 drivers/media/pci/tw686x/tw686x-regs.h  | 122 +++++
 drivers/media/pci/tw686x/tw686x-video.c | 927 ++++++++++++++++++++++++++++++++
 drivers/media/pci/tw686x/tw686x.h       | 158 ++++++
 10 files changed, 2039 insertions(+)
 create mode 100644 drivers/media/pci/tw686x/Kconfig
 create mode 100644 drivers/media/pci/tw686x/Makefile
 create mode 100644 drivers/media/pci/tw686x/tw686x-audio.c
 create mode 100644 drivers/media/pci/tw686x/tw686x-core.c
 create mode 100644 drivers/media/pci/tw686x/tw686x-regs.h
 create mode 100644 drivers/media/pci/tw686x/tw686x-video.c
 create mode 100644 drivers/media/pci/tw686x/tw686x.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 99bd725..605fd55 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11119,6 +11119,14 @@ W:	https://linuxtv.org
 S:	Odd Fixes
 F:	drivers/media/pci/tw68/
 
+TW686X VIDEO4LINUX DRIVER
+M:	Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>
+L:	linux-media@vger.kernel.org
+T:	git git://linuxtv.org/media_tree.git
+W:	http://linuxtv.org
+S:	Maintained
+F:	drivers/media/pci/tw686x/
+
 TPM DEVICE DRIVER
 M:	Peter Huewe <peterhuewe@gmx.de>
 M:	Marcel Selhorst <tpmdd@selhorst.net>
diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
index 48a611b..4f6467f 100644
--- a/drivers/media/pci/Kconfig
+++ b/drivers/media/pci/Kconfig
@@ -14,6 +14,7 @@ source "drivers/media/pci/meye/Kconfig"
 source "drivers/media/pci/solo6x10/Kconfig"
 source "drivers/media/pci/sta2x11/Kconfig"
 source "drivers/media/pci/tw68/Kconfig"
+source "drivers/media/pci/tw686x/Kconfig"
 source "drivers/media/pci/zoran/Kconfig"
 endif
 
diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
index 5f8aacb..2e54c36 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_VIDEO_BT848) += bt8xx/
 obj-$(CONFIG_VIDEO_SAA7134) += saa7134/
 obj-$(CONFIG_VIDEO_SAA7164) += saa7164/
 obj-$(CONFIG_VIDEO_TW68) += tw68/
+obj-$(CONFIG_VIDEO_TW686X) += tw686x/
 obj-$(CONFIG_VIDEO_DT3155) += dt3155/
 obj-$(CONFIG_VIDEO_MEYE) += meye/
 obj-$(CONFIG_STA2X11_VIP) += sta2x11/
diff --git a/drivers/media/pci/tw686x/Kconfig b/drivers/media/pci/tw686x/Kconfig
new file mode 100644
index 0000000..fb85369
--- /dev/null
+++ b/drivers/media/pci/tw686x/Kconfig
@@ -0,0 +1,18 @@
+config VIDEO_TW686X
+	tristate "Intersil/Techwell TW686x video capture cards"
+	depends on PCI && VIDEO_DEV && VIDEO_V4L2 && SND
+	depends on HAS_DMA
+	select VIDEOBUF2_VMALLOC
+	select SND_PCM
+	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.
diff --git a/drivers/media/pci/tw686x/Makefile b/drivers/media/pci/tw686x/Makefile
new file mode 100644
index 0000000..9981954
--- /dev/null
+++ b/drivers/media/pci/tw686x/Makefile
@@ -0,0 +1,3 @@
+tw686x-objs := tw686x-core.o tw686x-video.o tw686x-audio.o
+
+obj-$(CONFIG_VIDEO_TW686X) += tw686x.o
diff --git a/drivers/media/pci/tw686x/tw686x-audio.c b/drivers/media/pci/tw686x/tw686x-audio.c
new file mode 100644
index 0000000..91459ab
--- /dev/null
+++ b/drivers/media/pci/tw686x/tw686x-audio.c
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2015 VanguardiaSur - www.vanguardiasur.com.ar
+ *
+ * Based on the audio support from the tw6869 driver:
+ * Copyright 2015 www.starterkit.ru <info@starterkit.ru>
+ *
+ * Based on:
+ * Driver for Intersil|Techwell TW6869 based DVR cards
+ * (c) 2011-12 liran <jli11@intersil.com> [Intersil|Techwell China]
+ *
+ * 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/types.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kmod.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+#include "tw686x.h"
+#include "tw686x-regs.h"
+
+#define AUDIO_CHANNEL_OFFSET 8
+
+void tw686x_audio_irq(struct tw686x_dev *dev, unsigned long requests,
+		      unsigned int pb_status)
+{
+	unsigned long flags;
+	unsigned int ch, pb;
+
+	for_each_set_bit(ch, &requests, max_channels(dev)) {
+		struct tw686x_audio_channel *ac = &dev->audio_channels[ch];
+		struct tw686x_audio_buf *done = NULL;
+		struct tw686x_audio_buf *next = NULL;
+		struct tw686x_dma_desc *desc;
+
+		pb = !!(pb_status & BIT(AUDIO_CHANNEL_OFFSET + ch));
+
+		spin_lock_irqsave(&ac->lock, flags);
+
+		/* Sanity check */
+		if (!ac->ss || !ac->curr_bufs[0] || !ac->curr_bufs[1]) {
+			spin_unlock_irqrestore(&ac->lock, flags);
+			continue;
+		}
+
+		if (!list_empty(&ac->buf_list)) {
+			next = list_first_entry(&ac->buf_list,
+					struct tw686x_audio_buf, list);
+			list_move_tail(&next->list, &ac->buf_list);
+			done = ac->curr_bufs[!pb];
+			ac->curr_bufs[pb] = next;
+		}
+		spin_unlock_irqrestore(&ac->lock, flags);
+
+		desc = &ac->dma_descs[pb];
+		if (done && next && desc->virt) {
+			memcpy(done->virt, desc->virt, desc->size);
+			ac->ptr = done->dma - ac->buf[0].dma;
+			snd_pcm_period_elapsed(ac->ss);
+		}
+	}
+}
+
+static int tw686x_pcm_hw_params(struct snd_pcm_substream *ss,
+				struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw_params));
+}
+
+static int tw686x_pcm_hw_free(struct snd_pcm_substream *ss)
+{
+	return snd_pcm_lib_free_pages(ss);
+}
+
+/*
+ * The audio device rate is global and shared among all
+ * capture channels. The driver makes no effort to prevent
+ * rate modifications. User is free change the rate, but it
+ * means changing the rate for all capture sub-devices.
+ */
+static const struct snd_pcm_hardware tw686x_capture_hw = {
+	.info			= (SNDRV_PCM_INFO_MMAP |
+				   SNDRV_PCM_INFO_INTERLEAVED |
+				   SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				   SNDRV_PCM_INFO_MMAP_VALID),
+	.formats		= SNDRV_PCM_FMTBIT_S16_LE,
+	.rates			= SNDRV_PCM_RATE_8000_48000,
+	.rate_min		= 8000,
+	.rate_max		= 48000,
+	.channels_min		= 1,
+	.channels_max		= 1,
+	.buffer_bytes_max	= TW686X_AUDIO_PAGE_MAX * TW686X_AUDIO_PAGE_SZ,
+	.period_bytes_min	= TW686X_AUDIO_PAGE_SZ,
+	.period_bytes_max	= TW686X_AUDIO_PAGE_SZ,
+	.periods_min		= TW686X_AUDIO_PERIODS_MIN,
+	.periods_max		= TW686X_AUDIO_PERIODS_MAX,
+};
+
+static int tw686x_pcm_open(struct snd_pcm_substream *ss)
+{
+	struct tw686x_dev *dev = snd_pcm_substream_chip(ss);
+	struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number];
+	struct snd_pcm_runtime *rt = ss->runtime;
+	int err;
+
+	ac->ss = ss;
+	rt->hw = tw686x_capture_hw;
+
+	err = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int tw686x_pcm_close(struct snd_pcm_substream *ss)
+{
+	struct tw686x_dev *dev = snd_pcm_substream_chip(ss);
+	struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number];
+
+	ac->ss = NULL;
+	return 0;
+}
+
+static int tw686x_pcm_prepare(struct snd_pcm_substream *ss)
+{
+	struct tw686x_dev *dev = snd_pcm_substream_chip(ss);
+	struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number];
+	struct snd_pcm_runtime *rt = ss->runtime;
+	unsigned int period_size = snd_pcm_lib_period_bytes(ss);
+	struct tw686x_audio_buf *p_buf, *b_buf;
+	unsigned long flags;
+	int i;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	tw686x_disable_channel(dev, AUDIO_CHANNEL_OFFSET + ac->ch);
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	if (dev->audio_rate != rt->rate) {
+		u32 reg;
+
+		dev->audio_rate = rt->rate;
+		reg = ((125000000 / rt->rate) << 16) +
+		       ((125000000 % rt->rate) << 16) / rt->rate;
+
+		reg_write(dev, AUDIO_CONTROL2, reg);
+	}
+
+	if (period_size != TW686X_AUDIO_PAGE_SZ ||
+	    rt->periods < TW686X_AUDIO_PERIODS_MIN ||
+	    rt->periods > TW686X_AUDIO_PERIODS_MAX) {
+		return -EINVAL;
+	}
+
+	spin_lock_irqsave(&ac->lock, flags);
+	INIT_LIST_HEAD(&ac->buf_list);
+
+	for (i = 0; i < rt->periods; i++) {
+		ac->buf[i].dma = rt->dma_addr + period_size * i;
+		ac->buf[i].virt = rt->dma_area + period_size * i;
+		INIT_LIST_HEAD(&ac->buf[i].list);
+		list_add_tail(&ac->buf[i].list, &ac->buf_list);
+	}
+
+	p_buf =	list_first_entry(&ac->buf_list, struct tw686x_audio_buf, list);
+	list_move_tail(&p_buf->list, &ac->buf_list);
+
+	b_buf =	list_first_entry(&ac->buf_list, struct tw686x_audio_buf, list);
+	list_move_tail(&b_buf->list, &ac->buf_list);
+
+	ac->curr_bufs[0] = p_buf;
+	ac->curr_bufs[1] = b_buf;
+	ac->ptr = 0;
+	spin_unlock_irqrestore(&ac->lock, flags);
+
+	return 0;
+}
+
+static int tw686x_pcm_trigger(struct snd_pcm_substream *ss, int cmd)
+{
+	struct tw686x_dev *dev = snd_pcm_substream_chip(ss);
+	struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number];
+	unsigned long flags;
+	int err = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		if (ac->curr_bufs[0] && ac->curr_bufs[1]) {
+			spin_lock_irqsave(&dev->lock, flags);
+			tw686x_enable_channel(dev,
+				AUDIO_CHANNEL_OFFSET + ac->ch);
+			spin_unlock_irqrestore(&dev->lock, flags);
+
+			mod_timer(&dev->dma_delay_timer,
+				  jiffies + msecs_to_jiffies(100));
+		} else {
+			err = -EIO;
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		spin_lock_irqsave(&dev->lock, flags);
+		tw686x_disable_channel(dev, AUDIO_CHANNEL_OFFSET + ac->ch);
+		spin_unlock_irqrestore(&dev->lock, flags);
+
+		spin_lock_irqsave(&ac->lock, flags);
+		ac->curr_bufs[0] = NULL;
+		ac->curr_bufs[1] = NULL;
+		spin_unlock_irqrestore(&ac->lock, flags);
+		break;
+	default:
+		err = -EINVAL;
+	}
+	return err;
+}
+
+static snd_pcm_uframes_t tw686x_pcm_pointer(struct snd_pcm_substream *ss)
+{
+	struct tw686x_dev *dev = snd_pcm_substream_chip(ss);
+	struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number];
+
+	return bytes_to_frames(ss->runtime, ac->ptr);
+}
+
+static struct snd_pcm_ops tw686x_pcm_ops = {
+	.open = tw686x_pcm_open,
+	.close = tw686x_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = tw686x_pcm_hw_params,
+	.hw_free = tw686x_pcm_hw_free,
+	.prepare = tw686x_pcm_prepare,
+	.trigger = tw686x_pcm_trigger,
+	.pointer = tw686x_pcm_pointer,
+};
+
+static int tw686x_snd_pcm_init(struct tw686x_dev *dev)
+{
+	struct snd_card *card = dev->snd_card;
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *ss;
+	unsigned int i;
+	int err;
+
+	err = snd_pcm_new(card, card->driver, 0, 0, max_channels(dev), &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &tw686x_pcm_ops);
+	snd_pcm_chip(pcm) = dev;
+	pcm->info_flags = 0;
+	strlcpy(pcm->name, "tw686x PCM", sizeof(pcm->name));
+
+	for (i = 0, ss = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+	     ss; ss = ss->next, i++)
+		snprintf(ss->name, sizeof(ss->name), "vch%u audio", i);
+
+	return snd_pcm_lib_preallocate_pages_for_all(pcm,
+				SNDRV_DMA_TYPE_DEV,
+				snd_dma_pci_data(dev->pci_dev),
+				TW686X_AUDIO_PAGE_MAX * TW686X_AUDIO_PAGE_SZ,
+				TW686X_AUDIO_PAGE_MAX * TW686X_AUDIO_PAGE_SZ);
+}
+
+static void tw686x_audio_dma_free(struct tw686x_dev *dev,
+				  struct tw686x_audio_channel *ac)
+{
+	int pb;
+
+	for (pb = 0; pb < 2; pb++) {
+		if (!ac->dma_descs[pb].virt)
+			continue;
+		pci_free_consistent(dev->pci_dev, ac->dma_descs[pb].size,
+				    ac->dma_descs[pb].virt,
+				    ac->dma_descs[pb].phys);
+		ac->dma_descs[pb].virt = NULL;
+	}
+}
+
+static int tw686x_audio_dma_alloc(struct tw686x_dev *dev,
+				  struct tw686x_audio_channel *ac)
+{
+	int pb;
+
+	for (pb = 0; pb < 2; pb++) {
+		u32 reg = pb ? ADMA_B_ADDR[ac->ch] : ADMA_P_ADDR[ac->ch];
+		void *virt;
+
+		virt = pci_alloc_consistent(dev->pci_dev, TW686X_AUDIO_PAGE_SZ,
+					    &ac->dma_descs[pb].phys);
+		if (!virt) {
+			dev_err(&dev->pci_dev->dev,
+				"dma%d: unable to allocate audio DMA %s-buffer\n",
+				ac->ch, pb ? "B" : "P");
+			return -ENOMEM;
+		}
+		ac->dma_descs[pb].virt = virt;
+		ac->dma_descs[pb].size = TW686X_AUDIO_PAGE_SZ;
+		reg_write(dev, reg, ac->dma_descs[pb].phys);
+	}
+	return 0;
+}
+
+void tw686x_audio_free(struct tw686x_dev *dev)
+{
+	unsigned long flags;
+	u32 dma_ch_mask;
+	u32 dma_cmd;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	dma_cmd = reg_read(dev, DMA_CMD);
+	dma_ch_mask = reg_read(dev, DMA_CHANNEL_ENABLE);
+	reg_write(dev, DMA_CMD, dma_cmd & ~0xff00);
+	reg_write(dev, DMA_CHANNEL_ENABLE, dma_ch_mask & ~0xff00);
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	if (!dev->snd_card)
+		return;
+	snd_card_free(dev->snd_card);
+	dev->snd_card = NULL;
+}
+
+int tw686x_audio_init(struct tw686x_dev *dev)
+{
+	struct pci_dev *pci_dev = dev->pci_dev;
+	struct snd_card *card;
+	int err, ch;
+
+	/*
+	 * AUDIO_CONTROL1
+	 * DMA byte length [31:19] = 4096 (i.e. ALSA period)
+	 * External audio enable [0] = enabled
+	 */
+	reg_write(dev, AUDIO_CONTROL1, 0x80000001);
+
+	err = snd_card_new(&pci_dev->dev, SNDRV_DEFAULT_IDX1,
+			   SNDRV_DEFAULT_STR1,
+			   THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	dev->snd_card = card;
+	strlcpy(card->driver, "tw686x", sizeof(card->driver));
+	strlcpy(card->shortname, "tw686x", sizeof(card->shortname));
+	strlcpy(card->longname, pci_name(pci_dev), sizeof(card->longname));
+	snd_card_set_dev(card, &pci_dev->dev);
+
+	for (ch = 0; ch < max_channels(dev); ch++) {
+		struct tw686x_audio_channel *ac;
+
+		ac = &dev->audio_channels[ch];
+		spin_lock_init(&ac->lock);
+		ac->dev = dev;
+		ac->ch = ch;
+
+		err = tw686x_audio_dma_alloc(dev, ac);
+		if (err < 0)
+			goto err_cleanup;
+	}
+
+	err = tw686x_snd_pcm_init(dev);
+	if (err < 0)
+		goto err_cleanup;
+
+	err = snd_card_register(card);
+	if (!err)
+		return 0;
+
+err_cleanup:
+	for (ch = 0; ch < max_channels(dev); ch++) {
+		if (!dev->audio_channels[ch].dev)
+			continue;
+		tw686x_audio_dma_free(dev, &dev->audio_channels[ch]);
+	}
+	snd_card_free(card);
+	dev->snd_card = NULL;
+	return err;
+}
diff --git a/drivers/media/pci/tw686x/tw686x-core.c b/drivers/media/pci/tw686x/tw686x-core.c
new file mode 100644
index 0000000..cf53b0e
--- /dev/null
+++ b/drivers/media/pci/tw686x/tw686x-core.c
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2015 VanguardiaSur - www.vanguardiasur.com.ar
+ *
+ * Based on original driver by Krzysztof Ha?asa:
+ * Copyright (C) 2015 Industrial Research Institute for Automation
+ * and Measurements PIAP
+ *
+ * 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.
+ *
+ * Notes
+ * -----
+ *
+ * 1. Under stress-testing, it has been observed that the PCIe link
+ * goes down, without reason. Therefore, the driver takes special care
+ * to allow device hot-unplugging.
+ *
+ * 2. TW686X devices are capable of setting a few different DMA modes,
+ * including: scatter-gather, field and frame modes. However,
+ * under stress testings it has been found that the machine can
+ * freeze completely if DMA registers are programmed while streaming
+ * is active.
+ * This driver tries to access hardware registers as infrequently
+ * as possible by:
+ *   i.  allocating fixed DMA buffers and memcpy'ing into
+ *       vmalloc'ed buffers
+ *   ii. using a timer to mitigate the rate of DMA reset operations,
+ *       on DMA channels error.
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci_ids.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+
+#include "tw686x.h"
+#include "tw686x-regs.h"
+
+/*
+ * This module parameter allows to control the DMA_TIMER_INTERVAL value.
+ * The DMA_TIMER_INTERVAL register controls the minimum DMA interrupt
+ * time span (iow, the maximum DMA interrupt rate) thus allowing for
+ * IRQ coalescing.
+ *
+ * The chip datasheet does not mention a time unit for this value, so
+ * users wanting fine-grain control over the interrupt rate should
+ * determine the desired value through testing.
+ */
+static u32 dma_interval = 0x00098968;
+module_param(dma_interval, int, 0444);
+MODULE_PARM_DESC(dma_interval, "Minimum time span for DMA interrupting host");
+
+void tw686x_disable_channel(struct tw686x_dev *dev, unsigned int channel)
+{
+	u32 dma_en = reg_read(dev, DMA_CHANNEL_ENABLE);
+	u32 dma_cmd = reg_read(dev, DMA_CMD);
+
+	dma_en &= ~BIT(channel);
+	dma_cmd &= ~BIT(channel);
+
+	/* Must remove it from pending too */
+	dev->pending_dma_en &= ~BIT(channel);
+	dev->pending_dma_cmd &= ~BIT(channel);
+
+	/* Stop DMA if no channels are enabled */
+	if (!dma_en)
+		dma_cmd = 0;
+	reg_write(dev, DMA_CHANNEL_ENABLE, dma_en);
+	reg_write(dev, DMA_CMD, dma_cmd);
+}
+
+void tw686x_enable_channel(struct tw686x_dev *dev, unsigned int channel)
+{
+	u32 dma_en = reg_read(dev, DMA_CHANNEL_ENABLE);
+	u32 dma_cmd = reg_read(dev, DMA_CMD);
+
+	dev->pending_dma_en |= dma_en | BIT(channel);
+	dev->pending_dma_cmd |= dma_cmd | DMA_CMD_ENABLE | BIT(channel);
+}
+
+/*
+ * The purpose of this awful hack is to avoid enabling the DMA
+ * channels "too fast" which makes some TW686x devices very
+ * angry and freeze the CPU (see note 1).
+ */
+static void tw686x_dma_delay(unsigned long data)
+{
+	struct tw686x_dev *dev = (struct tw686x_dev *)data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+
+	reg_write(dev, DMA_CHANNEL_ENABLE, dev->pending_dma_en);
+	reg_write(dev, DMA_CMD, dev->pending_dma_cmd);
+	dev->pending_dma_en = 0;
+	dev->pending_dma_cmd = 0;
+
+	spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+static void tw686x_reset_channels(struct tw686x_dev *dev, unsigned int ch_mask)
+{
+	u32 dma_en, dma_cmd;
+
+	dma_en = reg_read(dev, DMA_CHANNEL_ENABLE);
+	dma_cmd = reg_read(dev, DMA_CMD);
+
+	/*
+	 * Save pending register status, the timer will
+	 * restore them.
+	 */
+	dev->pending_dma_en |= dma_en;
+	dev->pending_dma_cmd |= dma_cmd;
+
+	/* Disable the reset channels */
+	reg_write(dev, DMA_CHANNEL_ENABLE, dma_en & ~ch_mask);
+
+	if ((dma_en & ~ch_mask) == 0) {
+		dev_dbg(&dev->pci_dev->dev, "reset: stopping DMA\n");
+		dma_cmd &= ~DMA_CMD_ENABLE;
+	}
+	reg_write(dev, DMA_CMD, dma_cmd & ~ch_mask);
+}
+
+static irqreturn_t tw686x_irq(int irq, void *dev_id)
+{
+	struct tw686x_dev *dev = (struct tw686x_dev *)dev_id;
+	unsigned int video_requests, audio_requests, reset_ch;
+	u32 fifo_status, fifo_signal, fifo_ov, fifo_bad, fifo_errors;
+	u32 int_status, dma_en, video_en, pb_status;
+	unsigned long flags;
+
+	int_status = reg_read(dev, INT_STATUS); /* cleared on read */
+	fifo_status = reg_read(dev, VIDEO_FIFO_STATUS);
+
+	/* INT_STATUS does not include FIFO_STATUS errors! */
+	if (!int_status && !TW686X_FIFO_ERROR(fifo_status))
+		return IRQ_NONE;
+
+	if (int_status & INT_STATUS_DMA_TOUT) {
+		dev_dbg(&dev->pci_dev->dev,
+			"DMA timeout. Resetting DMA for all channels\n");
+		reset_ch = ~0;
+		goto reset_channels;
+	}
+
+	spin_lock_irqsave(&dev->lock, flags);
+	dma_en = reg_read(dev, DMA_CHANNEL_ENABLE);
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	video_en = dma_en & 0xff;
+	fifo_signal = ~(fifo_status & 0xff) & video_en;
+	fifo_ov = fifo_status >> 24;
+	fifo_bad = fifo_status >> 16;
+
+	/* Mask of channels with signal and FIFO errors */
+	fifo_errors = fifo_signal & (fifo_ov | fifo_bad);
+
+	reset_ch = 0;
+	pb_status = reg_read(dev, PB_STATUS);
+
+	/* Coalesce video frame/error events */
+	video_requests = (int_status & video_en) | fifo_errors;
+	audio_requests = (int_status & dma_en) >> 8;
+
+	if (video_requests)
+		tw686x_video_irq(dev, video_requests, pb_status,
+				 fifo_status, &reset_ch);
+	if (audio_requests)
+		tw686x_audio_irq(dev, audio_requests, pb_status);
+
+reset_channels:
+	if (reset_ch) {
+		spin_lock_irqsave(&dev->lock, flags);
+		tw686x_reset_channels(dev, reset_ch);
+		spin_unlock_irqrestore(&dev->lock, flags);
+		mod_timer(&dev->dma_delay_timer,
+			  jiffies + msecs_to_jiffies(100));
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void tw686x_dev_release(struct v4l2_device *v4l2_dev)
+{
+	struct tw686x_dev *dev = container_of(v4l2_dev, struct tw686x_dev,
+					      v4l2_dev);
+	unsigned int ch;
+
+	for (ch = 0; ch < max_channels(dev); ch++)
+		v4l2_ctrl_handler_free(&dev->video_channels[ch].ctrl_handler);
+
+	v4l2_device_unregister(&dev->v4l2_dev);
+
+	kfree(dev->audio_channels);
+	kfree(dev->video_channels);
+	kfree(dev);
+}
+
+static int tw686x_probe(struct pci_dev *pci_dev,
+			const struct pci_device_id *pci_id)
+{
+	struct tw686x_dev *dev;
+	int err;
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+	dev->type = pci_id->driver_data;
+	sprintf(dev->name, "tw%04X", pci_dev->device);
+
+	dev->video_channels = kcalloc(max_channels(dev),
+		sizeof(*dev->video_channels), GFP_KERNEL);
+	if (!dev->video_channels) {
+		err = -ENOMEM;
+		goto free_dev;
+	}
+
+	dev->audio_channels = kcalloc(max_channels(dev),
+		sizeof(*dev->audio_channels), GFP_KERNEL);
+	if (!dev->audio_channels) {
+		err = -ENOMEM;
+		goto free_video;
+	}
+
+	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 (pci_enable_device(pci_dev)) {
+		err = -EIO;
+		goto free_audio;
+	}
+
+	pci_set_master(pci_dev);
+	err = pci_set_dma_mask(pci_dev, DMA_BIT_MASK(32));
+	if (err) {
+		dev_err(&pci_dev->dev, "32-bit PCI DMA not supported\n");
+		err = -EIO;
+		goto disable_pci;
+	}
+
+	err = pci_request_regions(pci_dev, dev->name);
+	if (err) {
+		dev_err(&pci_dev->dev, "unable to request PCI region\n");
+		goto disable_pci;
+	}
+
+	dev->mmio = pci_ioremap_bar(pci_dev, 0);
+	if (!dev->mmio) {
+		dev_err(&pci_dev->dev, "unable to remap PCI region\n");
+		err = -ENOMEM;
+		goto free_region;
+	}
+
+	/* Reset all subsystems */
+	reg_write(dev, SYS_SOFT_RST, 0x0f);
+	mdelay(1);
+
+	reg_write(dev, SRST[0], 0x3f);
+	if (max_channels(dev) > 4)
+		reg_write(dev, SRST[1], 0x3f);
+
+	/* Disable the DMA engine */
+	reg_write(dev, DMA_CMD, 0);
+	reg_write(dev, DMA_CHANNEL_ENABLE, 0);
+
+	/* Enable DMA FIFO overflow and pointer check */
+	reg_write(dev, DMA_CONFIG, 0xffffff04);
+	reg_write(dev, DMA_CHANNEL_TIMEOUT, 0x140c8584);
+	reg_write(dev, DMA_TIMER_INTERVAL, dma_interval);
+
+	spin_lock_init(&dev->lock);
+
+	err = request_irq(pci_dev->irq, tw686x_irq, IRQF_SHARED,
+			  dev->name, dev);
+	if (err < 0) {
+		dev_err(&pci_dev->dev, "unable to request interrupt\n");
+		goto iounmap;
+	}
+
+	setup_timer(&dev->dma_delay_timer,
+		    tw686x_dma_delay, (unsigned long) dev);
+
+	/*
+	 * This must be set right before initializing v4l2_dev.
+	 * It's used to release resources after the last handle
+	 * held is released.
+	 */
+	dev->v4l2_dev.release = tw686x_dev_release;
+	err = tw686x_video_init(dev);
+	if (err) {
+		dev_err(&pci_dev->dev, "can't register video\n");
+		goto free_irq;
+	}
+
+	err = tw686x_audio_init(dev);
+	if (err)
+		dev_warn(&pci_dev->dev, "can't register audio\n");
+
+	pci_set_drvdata(pci_dev, dev);
+	return 0;
+
+free_irq:
+	free_irq(pci_dev->irq, dev);
+iounmap:
+	pci_iounmap(pci_dev, dev->mmio);
+free_region:
+	pci_release_regions(pci_dev);
+disable_pci:
+	pci_disable_device(pci_dev);
+free_audio:
+	kfree(dev->audio_channels);
+free_video:
+	kfree(dev->video_channels);
+free_dev:
+	kfree(dev);
+	return err;
+}
+
+static void tw686x_remove(struct pci_dev *pci_dev)
+{
+	struct tw686x_dev *dev = pci_get_drvdata(pci_dev);
+	unsigned long flags;
+
+	/* This guarantees the IRQ handler is no longer running,
+	 * which means we can kiss good-bye some resources.
+	 */
+	free_irq(pci_dev->irq, dev);
+
+	tw686x_video_free(dev);
+	tw686x_audio_free(dev);
+	del_timer_sync(&dev->dma_delay_timer);
+
+	pci_iounmap(pci_dev, dev->mmio);
+	pci_release_regions(pci_dev);
+	pci_disable_device(pci_dev);
+
+	/*
+	 * Setting pci_dev to NULL allows to detect hardware is no longer
+	 * available and will be used by vb2_ops. This is required because
+	 * the device sometimes hot-unplugs itself as the result of a PCIe
+	 * link down.
+	 * The lock is really important here.
+	 */
+	spin_lock_irqsave(&dev->lock, flags);
+	dev->pci_dev = NULL;
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	/*
+	 * This calls tw686x_dev_release if it's the last reference.
+	 * Otherwise, release is postponed until there are no users left.
+	 */
+	v4l2_device_put(&dev->v4l2_dev);
+}
+
+/*
+ * 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.
+ */
+
+/* driver_data is number of A/V channels */
+static const struct pci_device_id tw686x_pci_tbl[] = {
+	{
+		PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, 0x6864),
+		.driver_data = 4
+	},
+	{
+		PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, 0x6865), /* not tested */
+		.driver_data = 4 | TYPE_SECOND_GEN
+	},
+	/*
+	 * TW6868 supports 8 A/V channels with an external TW2865 chip;
+	 * not supported by the driver.
+	 */
+	{
+		PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, 0x6868), /* not tested */
+		.driver_data = 4
+	},
+	{
+		PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, 0x6869),
+		.driver_data = 8 | TYPE_SECOND_GEN},
+	{}
+};
+MODULE_DEVICE_TABLE(pci, tw686x_pci_tbl);
+
+static struct pci_driver tw686x_pci_driver = {
+	.name = "tw686x",
+	.id_table = tw686x_pci_tbl,
+	.probe = tw686x_probe,
+	.remove = tw686x_remove,
+};
+module_pci_driver(tw686x_pci_driver);
+
+MODULE_DESCRIPTION("Driver for video frame grabber cards based on Intersil/Techwell TW686[4589]");
+MODULE_AUTHOR("Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>");
+MODULE_AUTHOR("Krzysztof Ha?asa <khalasa@piap.pl>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/pci/tw686x/tw686x-regs.h b/drivers/media/pci/tw686x/tw686x-regs.h
new file mode 100644
index 0000000..fcef586
--- /dev/null
+++ b/drivers/media/pci/tw686x/tw686x-regs.h
@@ -0,0 +1,122 @@
+/* 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(0x10d)
+#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 ANPWRDOWN		VDREG2(0x1ce)
+#define AIGAIN			((const u16[8]) { 0x1d0, 0x1d1, 0x1d2, 0x1d3, \
+						  0x2d0, 0x2d1, 0x2d2, 0x2d3 })
+
+#define SYS_MODE_DMA_SHIFT	13
+
+#define DMA_CMD_ENABLE		BIT(31)
+#define INT_STATUS_DMA_TOUT	BIT(17)
+#define TW686X_VIDSTAT_HLOCK	BIT(6)
+#define TW686X_VIDSTAT_VDLOSS	BIT(7)
+
+#define TW686X_STD_NTSC_M	0
+#define TW686X_STD_PAL		1
+#define TW686X_STD_SECAM	2
+#define TW686X_STD_NTSC_443	3
+#define TW686X_STD_PAL_M	4
+#define TW686X_STD_PAL_CN	5
+#define TW686X_STD_PAL_60	6
+
+#define TW686X_FIFO_ERROR(x)	(x & ~(0xff))
diff --git a/drivers/media/pci/tw686x/tw686x-video.c b/drivers/media/pci/tw686x/tw686x-video.c
new file mode 100644
index 0000000..a3505aa
--- /dev/null
+++ b/drivers/media/pci/tw686x/tw686x-video.c
@@ -0,0 +1,927 @@
+/*
+ * Copyright (C) 2015 VanguardiaSur - www.vanguardiasur.com.ar
+ *
+ * Based on original driver by Krzysztof Ha?asa:
+ * Copyright (C) 2015 Industrial Research Institute for Automation
+ * and Measurements PIAP
+ *
+ * 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/delay.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 <media/videobuf2-vmalloc.h>
+#include "tw686x.h"
+#include "tw686x-regs.h"
+
+#define TW686X_INPUTS_PER_CH		4
+#define TW686X_VIDEO_WIDTH		720
+#define TW686X_VIDEO_HEIGHT(id)		((id & V4L2_STD_625_50) ? 576 : 480)
+
+static const struct tw686x_format formats[] = {
+	{
+		.fourcc = V4L2_PIX_FMT_UYVY,
+		.mode = 0,
+		.depth = 16,
+	}, {
+		.fourcc = V4L2_PIX_FMT_RGB565,
+		.mode = 5,
+		.depth = 16,
+	}, {
+		.fourcc = V4L2_PIX_FMT_YUYV,
+		.mode = 6,
+		.depth = 16,
+	}
+};
+
+static unsigned int tw686x_fields_map(v4l2_std_id std, unsigned int fps)
+{
+	static const unsigned int map[15] = {
+		0x00000000, 0x00000001, 0x00004001, 0x00104001, 0x00404041,
+		0x01041041, 0x01104411, 0x01111111, 0x04444445, 0x04511445,
+		0x05145145, 0x05151515, 0x05515455, 0x05551555, 0x05555555
+	};
+
+	static const unsigned int std_625_50[26] = {
+		0, 1, 1, 2,  3,  3,  4,  4,  5,  5,  6,  7,  7,
+		   8, 8, 9, 10, 10, 11, 11, 12, 13, 13, 14, 14, 0
+	};
+
+	static const unsigned int std_525_60[31] = {
+		0, 1, 1, 1, 2,  2,  3,  3,  4,  4,  5,  5,  6,  6, 7, 7,
+		   8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 0, 0
+	};
+
+	unsigned int i =
+		(std & V4L2_STD_625_50) ? std_625_50[fps] : std_525_60[fps];
+
+	return map[i];
+}
+
+static void tw686x_set_framerate(struct tw686x_video_channel *vc,
+				 unsigned int fps)
+{
+	unsigned int map;
+
+	if (vc->fps == fps)
+		return;
+
+	map = tw686x_fields_map(vc->video_standard, fps) << 1;
+	map |= map << 1;
+	if (map > 0)
+		map |= BIT(31);
+	reg_write(vc->dev, VIDEO_FIELD_CTRL[vc->ch], map);
+	vc->fps = fps;
+}
+
+static const struct tw686x_format *format_by_fourcc(unsigned fourcc)
+{
+	unsigned cnt;
+
+	for (cnt = 0; cnt < ARRAY_SIZE(formats); cnt++)
+		if (formats[cnt].fourcc == fourcc)
+			return &formats[cnt];
+	return NULL;
+}
+
+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 szimage =
+		(vc->width * vc->height * vc->format->depth) >> 3;
+
+	/*
+	 * Let's request at least three buffers: two for the
+	 * DMA engine and one for userspace.
+	 */
+	if (vq->num_buffers + *nbuffers < 3)
+		*nbuffers = 3 - vq->num_buffers;
+
+	if (*nplanes) {
+		if (*nplanes != 1 || sizes[0] < szimage)
+			return -EINVAL;
+		return 0;
+	}
+
+	sizes[0] = szimage;
+	*nplanes = 1;
+	return 0;
+}
+
+static void tw686x_buf_queue(struct vb2_buffer *vb)
+{
+	struct tw686x_video_channel *vc = vb2_get_drv_priv(vb->vb2_queue);
+	struct tw686x_dev *dev = vc->dev;
+	struct pci_dev *pci_dev;
+	unsigned long flags;
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct tw686x_v4l2_buf *buf =
+		container_of(vbuf, struct tw686x_v4l2_buf, vb);
+
+	/* Check device presence */
+	spin_lock_irqsave(&dev->lock, flags);
+	pci_dev = dev->pci_dev;
+	spin_unlock_irqrestore(&dev->lock, flags);
+	if (!pci_dev) {
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+		return;
+	}
+
+	spin_lock_irqsave(&vc->qlock, flags);
+	list_add_tail(&buf->list, &vc->vidq_queued);
+	spin_unlock_irqrestore(&vc->qlock, flags);
+}
+
+/*
+ * We can call this even when alloc_dma failed for the given channel
+ */
+static void tw686x_free_dma(struct tw686x_video_channel *vc, unsigned int pb)
+{
+	struct tw686x_dma_desc *desc = &vc->dma_descs[pb];
+	struct tw686x_dev *dev = vc->dev;
+	struct pci_dev *pci_dev;
+	unsigned long flags;
+
+	/* Check device presence. Shouldn't really happen! */
+	spin_lock_irqsave(&dev->lock, flags);
+	pci_dev = dev->pci_dev;
+	spin_unlock_irqrestore(&dev->lock, flags);
+	if (!pci_dev) {
+		WARN(1, "trying to deallocate on missing device\n");
+		return;
+	}
+
+	if (desc->virt) {
+		pci_free_consistent(dev->pci_dev, desc->size,
+				    desc->virt, desc->phys);
+		desc->virt = NULL;
+	}
+}
+
+static int tw686x_alloc_dma(struct tw686x_video_channel *vc, unsigned int pb)
+{
+	struct tw686x_dev *dev = vc->dev;
+	u32 reg = pb ? VDMA_B_ADDR[vc->ch] : VDMA_P_ADDR[vc->ch];
+	unsigned int len;
+	void *virt;
+
+	WARN(vc->dma_descs[pb].virt,
+	     "Allocating buffer but previous still here\n");
+
+	len = (vc->width * vc->height * vc->format->depth) >> 3;
+	virt = pci_alloc_consistent(dev->pci_dev, len,
+				    &vc->dma_descs[pb].phys);
+	if (!virt) {
+		v4l2_err(&dev->v4l2_dev,
+			 "dma%d: unable to allocate %s-buffer\n",
+			 vc->ch, pb ? "B" : "P");
+		return -ENOMEM;
+	}
+	vc->dma_descs[pb].size = len;
+	vc->dma_descs[pb].virt = virt;
+	reg_write(dev, reg, vc->dma_descs[pb].phys);
+
+	return 0;
+}
+
+static void tw686x_buffer_refill(struct tw686x_video_channel *vc,
+				 unsigned int pb)
+{
+	struct tw686x_v4l2_buf *buf;
+
+	while (!list_empty(&vc->vidq_queued)) {
+
+		buf = list_first_entry(&vc->vidq_queued,
+			struct tw686x_v4l2_buf, list);
+		list_del(&buf->list);
+
+		vc->curr_bufs[pb] = buf;
+		return;
+	}
+	vc->curr_bufs[pb] = NULL;
+}
+
+static void tw686x_clear_queue(struct tw686x_video_channel *vc,
+			       enum vb2_buffer_state state)
+{
+	unsigned int pb;
+
+	while (!list_empty(&vc->vidq_queued)) {
+		struct tw686x_v4l2_buf *buf;
+
+		buf = list_first_entry(&vc->vidq_queued,
+			struct tw686x_v4l2_buf, list);
+		list_del(&buf->list);
+		vb2_buffer_done(&buf->vb.vb2_buf, state);
+	}
+
+	for (pb = 0; pb < 2; pb++) {
+		if (vc->curr_bufs[pb])
+			vb2_buffer_done(&vc->curr_bufs[pb]->vb.vb2_buf, state);
+		vc->curr_bufs[pb] = NULL;
+	}
+}
+
+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;
+	struct pci_dev *pci_dev;
+	unsigned long flags;
+	int pb, err;
+
+	/* Check device presence */
+	spin_lock_irqsave(&dev->lock, flags);
+	pci_dev = dev->pci_dev;
+	spin_unlock_irqrestore(&dev->lock, flags);
+	if (!pci_dev) {
+		err = -ENODEV;
+		goto err_clear_queue;
+	}
+
+	spin_lock_irqsave(&vc->qlock, flags);
+
+	/* Sanity check */
+	if (!vc->dma_descs[0].virt || !vc->dma_descs[1].virt) {
+		spin_unlock_irqrestore(&vc->qlock, flags);
+		v4l2_err(&dev->v4l2_dev,
+			 "video%d: refusing to start without DMA buffers\n",
+			 vc->num);
+		err = -ENOMEM;
+		goto err_clear_queue;
+	}
+
+	for (pb = 0; pb < 2; pb++)
+		tw686x_buffer_refill(vc, pb);
+	spin_unlock_irqrestore(&vc->qlock, flags);
+
+	vc->sequence = 0;
+	vc->pb = 0;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	tw686x_enable_channel(dev, vc->ch);
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	mod_timer(&dev->dma_delay_timer, jiffies + msecs_to_jiffies(100));
+
+	return 0;
+
+err_clear_queue:
+	spin_lock_irqsave(&vc->qlock, flags);
+	tw686x_clear_queue(vc, VB2_BUF_STATE_QUEUED);
+	spin_unlock_irqrestore(&vc->qlock, flags);
+	return err;
+}
+
+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;
+	struct pci_dev *pci_dev;
+	unsigned long flags;
+
+	/* Check device presence */
+	spin_lock_irqsave(&dev->lock, flags);
+	pci_dev = dev->pci_dev;
+	spin_unlock_irqrestore(&dev->lock, flags);
+	if (pci_dev)
+		tw686x_disable_channel(dev, vc->ch);
+
+	spin_lock_irqsave(&vc->qlock, flags);
+	tw686x_clear_queue(vc, VB2_BUF_STATE_ERROR);
+	spin_unlock_irqrestore(&vc->qlock, flags);
+}
+
+static int tw686x_buf_prepare(struct vb2_buffer *vb)
+{
+	struct tw686x_video_channel *vc = vb2_get_drv_priv(vb->vb2_queue);
+	unsigned int size =
+		(vc->width * vc->height * vc->format->depth) >> 3;
+
+	if (vb2_plane_size(vb, 0) < size)
+		return -EINVAL;
+	vb2_set_plane_payload(vb, 0, size);
+	return 0;
+}
+
+static struct vb2_ops tw686x_video_qops = {
+	.queue_setup		= tw686x_queue_setup,
+	.buf_queue		= tw686x_buf_queue,
+	.buf_prepare		= tw686x_buf_prepare,
+	.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 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 = V4L2_FIELD_INTERLACED;
+	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)
+{
+	struct tw686x_video_channel *vc = video_drvdata(file);
+	unsigned int video_height = TW686X_VIDEO_HEIGHT(vc->video_standard);
+	const struct tw686x_format *format;
+
+	format = format_by_fourcc(f->fmt.pix.pixelformat);
+	if (!format) {
+		format = &formats[0];
+		f->fmt.pix.pixelformat = format->fourcc;
+	}
+
+	if (f->fmt.pix.width <= TW686X_VIDEO_WIDTH / 2)
+		f->fmt.pix.width = TW686X_VIDEO_WIDTH / 2;
+	else
+		f->fmt.pix.width = TW686X_VIDEO_WIDTH;
+
+	if (f->fmt.pix.height <= video_height / 2)
+		f->fmt.pix.height = video_height / 2;
+	else
+		f->fmt.pix.height = video_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;
+	f->fmt.pix.field = V4L2_FIELD_INTERLACED;
+
+	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);
+	u32 val, width, line_width, height;
+	unsigned long bitsperframe;
+	int err, pb;
+
+	if (vb2_is_busy(&vc->vidq))
+		return -EBUSY;
+
+	bitsperframe = vc->width * vc->height * vc->format->depth;
+	err = tw686x_try_fmt_vid_cap(file, priv, f);
+	if (err)
+		return err;
+
+	vc->format = format_by_fourcc(f->fmt.pix.pixelformat);
+	vc->width = f->fmt.pix.width;
+	vc->height = f->fmt.pix.height;
+
+	/* We need new DMA buffers if the framesize has changed */
+	if (bitsperframe != vc->width * vc->height * vc->format->depth) {
+		for (pb = 0; pb < 2; pb++)
+			tw686x_free_dma(vc, pb);
+
+		for (pb = 0; pb < 2; pb++) {
+			err = tw686x_alloc_dma(vc, pb);
+			if (err) {
+				if (pb > 0)
+					tw686x_free_dma(vc, 0);
+				return err;
+			}
+		}
+	}
+
+	val = reg_read(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch]);
+
+	if (vc->width <= TW686X_VIDEO_WIDTH / 2)
+		val |= BIT(23);
+	else
+		val &= ~BIT(23);
+
+	if (vc->height <= TW686X_VIDEO_HEIGHT(vc->video_standard) / 2)
+		val |= BIT(24);
+	else
+		val &= ~BIT(24);
+
+	val &= ~(0x7 << 20);
+	val |= vc->format->mode << 20;
+	reg_write(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch], val);
+
+	/* Program the DMA frame size */
+	width = (vc->width * 2) & 0x7ff;
+	height = vc->height / 2;
+	line_width = (vc->width * 2) & 0x7ff;
+	val = (height << 22) | (line_width << 11)  | width;
+	reg_write(vc->dev, VDMA_WHP[vc->ch], val);
+	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;
+
+	strlcpy(cap->driver, "tw686x", sizeof(cap->driver));
+	strlcpy(cap->card, dev->name, sizeof(cap->card));
+	snprintf(cap->bus_info, sizeof(cap->bus_info),
+		 "PCI:%s", pci_name(dev->pci_dev));
+	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING |
+			   V4L2_CAP_READWRITE;
+	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);
+	struct v4l2_format f;
+	u32 val, ret;
+
+	if (vc->video_standard == id)
+		return 0;
+
+	if (vb2_is_busy(&vc->vidq))
+		return -EBUSY;
+
+	if (id & V4L2_STD_NTSC)
+		val = 0;
+	else if (id & V4L2_STD_PAL)
+		val = 1;
+	else if (id & V4L2_STD_SECAM)
+		val = 2;
+	else if (id & V4L2_STD_NTSC_443)
+		val = 3;
+	else if (id & V4L2_STD_PAL_M)
+		val = 4;
+	else if (id & V4L2_STD_PAL_Nc)
+		val = 5;
+	else if (id & V4L2_STD_PAL_60)
+		val = 6;
+	else
+		return -EINVAL;
+
+	vc->video_standard = id;
+	reg_write(vc->dev, SDT[vc->ch], val);
+
+	val = reg_read(vc->dev, VIDEO_CONTROL1);
+	if (id & V4L2_STD_625_50)
+		val |= (1 << (SYS_MODE_DMA_SHIFT + vc->ch));
+	else
+		val &= ~(1 << (SYS_MODE_DMA_SHIFT + vc->ch));
+	reg_write(vc->dev, VIDEO_CONTROL1, val);
+
+	/*
+	 * Adjust format after V4L2_STD_525_60/V4L2_STD_625_50 change,
+	 * calling g_fmt and s_fmt will sanitize the height
+	 * according to the standard.
+	 */
+	ret = tw686x_g_fmt_vid_cap(file, priv, &f);
+	if (!ret)
+		tw686x_s_fmt_vid_cap(file, priv, &f);
+	return 0;
+}
+
+static int tw686x_querystd(struct file *file, void *priv, v4l2_std_id *std)
+{
+	struct tw686x_video_channel *vc = video_drvdata(file);
+	struct tw686x_dev *dev = vc->dev;
+	unsigned int old_std, detected_std = 0;
+	unsigned long end;
+
+	if (vb2_is_streaming(&vc->vidq))
+		return -EBUSY;
+
+	/* Enable and start standard detection */
+	old_std = reg_read(dev, SDT[vc->ch]);
+	reg_write(dev, SDT[vc->ch], 0x7);
+	reg_write(dev, SDT_EN[vc->ch], 0xff);
+
+	end = jiffies + msecs_to_jiffies(500);
+	while (time_is_after_jiffies(end)) {
+
+		detected_std = reg_read(dev, SDT[vc->ch]);
+		if (!(detected_std & BIT(7)))
+			break;
+		msleep(100);
+	}
+	reg_write(dev, SDT[vc->ch], old_std);
+
+	/* Exit if still busy */
+	if (detected_std & BIT(7))
+		return 0;
+
+	detected_std = (detected_std >> 4) & 0x7;
+	switch (detected_std) {
+	case TW686X_STD_NTSC_M:
+		*std &= V4L2_STD_NTSC;
+		break;
+	case TW686X_STD_NTSC_443:
+		*std &= V4L2_STD_NTSC_443;
+		break;
+	case TW686X_STD_PAL_M:
+		*std &= V4L2_STD_PAL_M;
+		break;
+	case TW686X_STD_PAL_60:
+		*std &= V4L2_STD_PAL_60;
+		break;
+	case TW686X_STD_PAL:
+		*std &= V4L2_STD_PAL;
+		break;
+	case TW686X_STD_PAL_CN:
+		*std &= V4L2_STD_PAL_Nc;
+		break;
+	case TW686X_STD_SECAM:
+		*std &= V4L2_STD_SECAM;
+		break;
+	default:
+		*std = 0;
+	}
+	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;
+	f->pixelformat = formats[f->index].fourcc;
+	return 0;
+}
+
+static int tw686x_s_input(struct file *file, void *priv, unsigned int i)
+{
+	struct tw686x_video_channel *vc = video_drvdata(file);
+	u32 val;
+
+	if (i >= TW686X_INPUTS_PER_CH)
+		return -EINVAL;
+	if (i == vc->input)
+		return 0;
+	/*
+	 * Not sure we are able to support on the fly input change
+	 */
+	if (vb2_is_busy(&vc->vidq))
+		return -EBUSY;
+
+	vc->input = i;
+
+	val = reg_read(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch]);
+	val &= ~(0x3 << 30);
+	val |= i << 30;
+	reg_write(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch], val);
+	return 0;
+}
+
+static int tw686x_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	struct tw686x_video_channel *vc = video_drvdata(file);
+
+	*i = vc->input;
+	return 0;
+}
+
+static int tw686x_enum_input(struct file *file, void *priv,
+			     struct v4l2_input *i)
+{
+	struct tw686x_video_channel *vc = video_drvdata(file);
+	unsigned int vidstat;
+
+	if (i->index >= TW686X_INPUTS_PER_CH)
+		return -EINVAL;
+
+	snprintf(i->name, sizeof(i->name), "Composite%d", i->index);
+	i->type = V4L2_INPUT_TYPE_CAMERA;
+	i->std = vc->device->tvnorms;
+	i->capabilities = V4L2_IN_CAP_STD;
+
+	vidstat = reg_read(vc->dev, VIDSTAT[vc->ch]);
+	i->status = 0;
+	if (vidstat & TW686X_VIDSTAT_VDLOSS)
+		i->status |= V4L2_IN_ST_NO_SIGNAL;
+	if (!(vidstat & TW686X_VIDSTAT_HLOCK))
+		i->status |= V4L2_IN_ST_NO_H_LOCK;
+
+	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_g_fmt_vid_cap		= tw686x_g_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap		= tw686x_s_fmt_vid_cap,
+	.vidioc_enum_fmt_vid_cap	= tw686x_enum_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap		= tw686x_try_fmt_vid_cap,
+
+	.vidioc_querystd		= tw686x_querystd,
+	.vidioc_g_std			= tw686x_g_std,
+	.vidioc_s_std			= tw686x_s_std,
+
+	.vidioc_enum_input		= tw686x_enum_input,
+	.vidioc_g_input			= tw686x_g_input,
+	.vidioc_s_input			= tw686x_s_input,
+
+	.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_prepare_buf		= vb2_ioctl_prepare_buf,
+
+	.vidioc_log_status		= v4l2_ctrl_log_status,
+	.vidioc_subscribe_event		= v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
+};
+
+static void tw686x_buffer_copy(struct tw686x_video_channel *vc,
+			       unsigned int pb, struct vb2_v4l2_buffer *vb)
+{
+	struct tw686x_dma_desc *desc = &vc->dma_descs[pb];
+	struct vb2_buffer *vb2_buf = &vb->vb2_buf;
+
+	vb->field = V4L2_FIELD_INTERLACED;
+	vb->sequence = vc->sequence++;
+
+	memcpy(vb2_plane_vaddr(vb2_buf, 0), desc->virt, desc->size);
+	vb2_buf->timestamp = ktime_get_ns();
+	vb2_buffer_done(vb2_buf, VB2_BUF_STATE_DONE);
+}
+
+void tw686x_video_irq(struct tw686x_dev *dev, unsigned long requests,
+		      unsigned int pb_status, unsigned int fifo_status,
+		      unsigned int *reset_ch)
+{
+	struct tw686x_video_channel *vc;
+	struct vb2_v4l2_buffer *vb;
+	unsigned long flags;
+	unsigned int ch, pb;
+
+	for_each_set_bit(ch, &requests, max_channels(dev)) {
+		vc = &dev->video_channels[ch];
+
+		/*
+		 * This can either be a blue frame (with signal-lost bit set)
+		 * or a good frame (with signal-lost bit clear). If we have just
+		 * got signal, then this channel needs resetting.
+		 */
+		if (vc->no_signal && !(fifo_status & BIT(ch))) {
+			v4l2_printk(KERN_DEBUG, &dev->v4l2_dev,
+				    "video%d: signal recovered\n", vc->num);
+			vc->no_signal = false;
+			*reset_ch |= BIT(ch);
+			vc->pb = 0;
+			continue;
+		}
+		vc->no_signal = !!(fifo_status & BIT(ch));
+
+		/* Check FIFO errors only if there's signal */
+		if (!vc->no_signal) {
+			u32 fifo_ov, fifo_bad;
+
+			fifo_ov = (fifo_status >> 24) & BIT(ch);
+			fifo_bad = (fifo_status >> 16) & BIT(ch);
+			if (fifo_ov || fifo_bad) {
+				/* Mark this channel for reset */
+				v4l2_printk(KERN_DEBUG, &dev->v4l2_dev,
+					    "video%d: FIFO error\n", vc->num);
+				*reset_ch |= BIT(ch);
+				vc->pb = 0;
+				continue;
+			}
+		}
+
+		pb = !!(pb_status & BIT(ch));
+		if (vc->pb != pb) {
+			/* Mark this channel for reset */
+			v4l2_printk(KERN_DEBUG, &dev->v4l2_dev,
+				    "video%d: unexpected p-b buffer!\n",
+				    vc->num);
+			*reset_ch |= BIT(ch);
+			vc->pb = 0;
+			continue;
+		}
+
+		/* handle video stream */
+		spin_lock_irqsave(&vc->qlock, flags);
+		if (vc->curr_bufs[pb]) {
+			vb = &vc->curr_bufs[pb]->vb;
+			tw686x_buffer_copy(vc, pb, vb);
+		}
+		vc->pb = !pb;
+		tw686x_buffer_refill(vc, pb);
+		spin_unlock_irqrestore(&vc->qlock, flags);
+	}
+}
+
+void tw686x_video_free(struct tw686x_dev *dev)
+{
+	unsigned int ch, pb;
+
+	for (ch = 0; ch < max_channels(dev); ch++) {
+		struct tw686x_video_channel *vc = &dev->video_channels[ch];
+
+		if (vc->device)
+			video_unregister_device(vc->device);
+
+		for (pb = 0; pb < 2; pb++)
+			tw686x_free_dma(vc, pb);
+	}
+}
+
+int tw686x_video_init(struct tw686x_dev *dev)
+{
+	unsigned int ch, val, pb;
+	int err;
+
+	err = v4l2_device_register(&dev->pci_dev->dev, &dev->v4l2_dev);
+	if (err)
+		return err;
+
+	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 */
+		vc->format = &formats[0];
+		vc->video_standard = V4L2_STD_NTSC;
+		vc->width = TW686X_VIDEO_WIDTH;
+		vc->height = TW686X_VIDEO_HEIGHT(vc->video_standard);
+		vc->input = 0;
+
+		reg_write(vc->dev, SDT[ch], 0);
+		tw686x_set_framerate(vc, 30);
+
+		reg_write(dev, VDELAY_LO[ch], 0x14);
+		reg_write(dev, HACTIVE_LO[ch], 0xd0);
+		reg_write(dev, VIDEO_SIZE[ch], 0);
+
+		for (pb = 0; pb < 2; pb++) {
+			err = tw686x_alloc_dma(vc, pb);
+			if (err)
+				goto error;
+		}
+
+		vc->vidq.io_modes = VB2_READ | VB2_MMAP | VB2_DMABUF;
+		vc->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		vc->vidq.drv_priv = vc;
+		vc->vidq.buf_struct_size = sizeof(struct tw686x_v4l2_buf);
+		vc->vidq.ops = &tw686x_video_qops;
+		vc->vidq.mem_ops = &vb2_vmalloc_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) {
+			v4l2_err(&dev->v4l2_dev,
+				 "dma%d: cannot init vb2 queue\n", ch);
+			goto error;
+		}
+
+		err = v4l2_ctrl_handler_init(&vc->ctrl_handler, 4);
+		if (err) {
+			v4l2_err(&dev->v4l2_dev,
+				 "dma%d: cannot init ctrl handler\n", ch);
+			goto error;
+		}
+		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, 100);
+		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, -128, 127, 1, 0);
+		err = vc->ctrl_handler.error;
+		if (err)
+			goto error;
+
+		err = v4l2_ctrl_handler_setup(&vc->ctrl_handler);
+		if (err)
+			goto error;
+
+		vdev = video_device_alloc();
+		if (!vdev) {
+			v4l2_err(&dev->v4l2_dev,
+				 "dma%d: unable to allocate device\n", ch);
+			err = -ENOMEM;
+			goto error;
+		}
+
+		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_525_60 | V4L2_STD_625_50;
+		vdev->minor = -1;
+		vdev->lock = &vc->vb_mutex;
+		vdev->ctrl_handler = &vc->ctrl_handler;
+		vc->device = vdev;
+		video_set_drvdata(vdev, vc);
+
+		err = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+		if (err < 0)
+			goto error;
+		vc->num = vdev->num;
+	}
+
+	/* Set DMA frame mode on all channels. Only supported mode for now. */
+	val = TW686X_DEF_PHASE_REF;
+	for (ch = 0; ch < max_channels(dev); ch++)
+		val |= TW686X_FRAME_MODE << (16 + ch * 2);
+	reg_write(dev, PHASE_REF, val);
+
+	reg_write(dev, MISC2[0], 0xe7);
+	reg_write(dev, VCTRL1[0], 0xcc);
+	reg_write(dev, LOOP[0], 0xa5);
+	if (max_channels(dev) > 4) {
+		reg_write(dev, VCTRL1[1], 0xcc);
+		reg_write(dev, LOOP[1], 0xa5);
+		reg_write(dev, MISC2[1], 0xe7);
+	}
+	return 0;
+
+error:
+	tw686x_video_free(dev);
+	return err;
+}
diff --git a/drivers/media/pci/tw686x/tw686x.h b/drivers/media/pci/tw686x/tw686x.h
new file mode 100644
index 0000000..103dd4a
--- /dev/null
+++ b/drivers/media/pci/tw686x/tw686x.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2015 VanguardiaSur - www.vanguardiasur.com.ar
+ *
+ * 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/mutex.h>
+#include <linux/pci.h>
+#include <linux/timer.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>
+#include <media/videobuf2-v4l2.h>
+#include <sound/pcm.h>
+
+#include "tw686x-regs.h"
+
+#define TYPE_MAX_CHANNELS	0x0f
+#define TYPE_SECOND_GEN		0x10
+#define TW686X_DEF_PHASE_REF	0x1518
+
+#define TW686X_FIELD_MODE	0x3
+#define TW686X_FRAME_MODE	0x2
+/* 0x1 is reserved */
+#define TW686X_SG_MODE		0x0
+
+#define TW686X_AUDIO_PAGE_SZ		4096
+#define TW686X_AUDIO_PAGE_MAX		16
+#define TW686X_AUDIO_PERIODS_MIN	2
+#define TW686X_AUDIO_PERIODS_MAX	TW686X_AUDIO_PAGE_MAX
+
+struct tw686x_format {
+	char *name;
+	unsigned fourcc;
+	unsigned depth;
+	unsigned mode;
+};
+
+struct tw686x_dma_desc {
+	dma_addr_t phys;
+	void *virt;
+	unsigned size;
+};
+
+struct tw686x_audio_buf {
+	dma_addr_t dma;
+	void *virt;
+	struct list_head list;
+};
+
+struct tw686x_v4l2_buf {
+	struct vb2_v4l2_buffer vb;
+	struct list_head list;
+};
+
+struct tw686x_audio_channel {
+	struct tw686x_dev *dev;
+	struct snd_pcm_substream *ss;
+	unsigned int ch;
+	struct tw686x_audio_buf *curr_bufs[2];
+	struct tw686x_dma_desc dma_descs[2];
+	dma_addr_t ptr;
+
+	struct tw686x_audio_buf buf[TW686X_AUDIO_PAGE_MAX];
+	struct list_head buf_list;
+	spinlock_t lock;
+};
+
+struct tw686x_video_channel {
+	struct tw686x_dev *dev;
+
+	struct vb2_queue vidq;
+	struct list_head vidq_queued;
+	struct video_device *device;
+	struct tw686x_v4l2_buf *curr_bufs[2];
+	struct tw686x_dma_desc dma_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;
+	unsigned int h_halve, v_halve;
+	unsigned int ch;
+	unsigned int num;
+	unsigned int fps;
+	unsigned int input;
+	unsigned int sequence;
+	unsigned int pb;
+	bool no_signal;
+};
+
+/**
+ * struct tw686x_dev - global device status
+ * @lock: spinlock controlling access to the
+ *        shared device registers (DMA enable/disable).
+ */
+struct tw686x_dev {
+	spinlock_t lock;
+
+	struct v4l2_device v4l2_dev;
+	struct snd_card *snd_card;
+
+	char name[32];
+	unsigned int type;
+	struct pci_dev *pci_dev;
+	__u32 __iomem *mmio;
+
+	void *alloc_ctx;
+
+	struct tw686x_video_channel *video_channels;
+	struct tw686x_audio_channel *audio_channels;
+
+	int audio_rate; /* per-device value */
+
+	struct timer_list dma_delay_timer;
+	u32 pending_dma_en; /* must be protected by lock */
+	u32 pending_dma_cmd; /* must be protected by lock */
+};
+
+static inline uint32_t reg_read(struct tw686x_dev *dev, unsigned reg)
+{
+	return readl(dev->mmio + reg);
+}
+
+static inline void reg_write(struct tw686x_dev *dev, unsigned reg,
+			     uint32_t value)
+{
+	writel(value, dev->mmio + reg);
+}
+
+static inline unsigned max_channels(struct tw686x_dev *dev)
+{
+	return dev->type & TYPE_MAX_CHANNELS; /* 4 or 8 channels */
+}
+
+void tw686x_enable_channel(struct tw686x_dev *dev, unsigned int channel);
+void tw686x_disable_channel(struct tw686x_dev *dev, unsigned int channel);
+
+int tw686x_video_init(struct tw686x_dev *dev);
+void tw686x_video_free(struct tw686x_dev *dev);
+void tw686x_video_irq(struct tw686x_dev *dev, unsigned long requests,
+		      unsigned int pb_status, unsigned int fifo_status,
+		      unsigned int *reset_ch);
+
+int tw686x_audio_init(struct tw686x_dev *dev);
+void tw686x_audio_free(struct tw686x_dev *dev);
+void tw686x_audio_irq(struct tw686x_dev *dev, unsigned long requests,
+		      unsigned int pb_status);
-- 
2.7.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 3/4] tw686x-kh: specify that the DMA is 32 bits
  2016-03-21 11:41 [PATCH 0/4] tw686x drivers Hans Verkuil
  2016-03-21 11:41 ` [PATCH 1/4] TW686x frame grabber driver Hans Verkuil
  2016-03-21 11:41 ` [PATCH 2/4] media: Support Intersil/Techwell TW686x-based video capture cards Hans Verkuil
@ 2016-03-21 11:41 ` Hans Verkuil
  2016-03-21 11:41 ` [PATCH 4/4] tw686x-kh: add audio support to the TODO list Hans Verkuil
  3 siblings, 0 replies; 6+ messages in thread
From: Hans Verkuil @ 2016-03-21 11:41 UTC (permalink / raw)
  To: linux-media; +Cc: Krzysztof Halasa, Ezequiel Garcia, Hans Verkuil

From: Hans Verkuil <hans.verkuil@cisco.com>

Set vb2_queue.gfp_flags to GFP_DMA32. Otherwise it will start to
create bounce buffers which is something you want to avoid since
those are in limited supply.

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
---
 drivers/staging/media/tw686x-kh/tw686x-kh-video.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/staging/media/tw686x-kh/tw686x-kh-video.c b/drivers/staging/media/tw686x-kh/tw686x-kh-video.c
index fb7a784..caf045a 100644
--- a/drivers/staging/media/tw686x-kh/tw686x-kh-video.c
+++ b/drivers/staging/media/tw686x-kh/tw686x-kh-video.c
@@ -766,6 +766,7 @@ int tw686x_video_init(struct tw686x_dev *dev)
 		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)
-- 
2.7.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 4/4] tw686x-kh: add audio support to the TODO list
  2016-03-21 11:41 [PATCH 0/4] tw686x drivers Hans Verkuil
                   ` (2 preceding siblings ...)
  2016-03-21 11:41 ` [PATCH 3/4] tw686x-kh: specify that the DMA is 32 bits Hans Verkuil
@ 2016-03-21 11:41 ` Hans Verkuil
  3 siblings, 0 replies; 6+ messages in thread
From: Hans Verkuil @ 2016-03-21 11:41 UTC (permalink / raw)
  To: linux-media; +Cc: Krzysztof Halasa, Ezequiel Garcia, Hans Verkuil

From: Hans Verkuil <hans.verkuil@cisco.com>

The mainline tw686x driver also supports audio, that's missing here
as well.

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
---
 drivers/staging/media/tw686x-kh/TODO | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/staging/media/tw686x-kh/TODO b/drivers/staging/media/tw686x-kh/TODO
index f226e80..480a495 100644
--- a/drivers/staging/media/tw686x-kh/TODO
+++ b/drivers/staging/media/tw686x-kh/TODO
@@ -1,3 +1,6 @@
-TODO: implement V4L2_FIELD_INTERLACED* mode(s).
+TODO:
+
+- implement V4L2_FIELD_INTERLACED* mode(s).
+- add audio support
 
 Please Cc: patches to Krzysztof Halasa <khalasa@piap.pl>.
-- 
2.7.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH 2/4] media: Support Intersil/Techwell TW686x-based video capture cards
  2016-03-21 11:41 ` [PATCH 2/4] media: Support Intersil/Techwell TW686x-based video capture cards Hans Verkuil
@ 2016-03-22 16:16   ` Ezequiel Garcia
  0 siblings, 0 replies; 6+ messages in thread
From: Ezequiel Garcia @ 2016-03-22 16:16 UTC (permalink / raw)
  To: Hans Verkuil; +Cc: linux-media, Krzysztof Halasa, Hans Verkuil

Hi Hans,

On 21 March 2016 at 08:41, Hans Verkuil <hverkuil@xs4all.nl> wrote:
> From: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>
>
> This commit introduces the support for the Techwell TW686x video
> capture IC. This hardware supports a few DMA modes, including
> scatter-gather and frame (contiguous).
>
> This commit makes little use of the DMA engine and instead has
> a memcpy based implementation. DMA frame and scatter-gather modes
> support may be added in the future.
>
> 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).
>
> Cc: Krzysztof Halasa <khalasa@piap.pl>
> Signed-off-by: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>
> Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
> Tested-by: Hans Verkuil <hans.verkuil@cisco.com>
[..]
> +int tw686x_video_init(struct tw686x_dev *dev)
> +{
> +       unsigned int ch, val, pb;
> +       int err;
> +
> +       err = v4l2_device_register(&dev->pci_dev->dev, &dev->v4l2_dev);
> +       if (err)
> +               return err;
> +
> +       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 */
> +               vc->format = &formats[0];
> +               vc->video_standard = V4L2_STD_NTSC;
> +               vc->width = TW686X_VIDEO_WIDTH;
> +               vc->height = TW686X_VIDEO_HEIGHT(vc->video_standard);
> +               vc->input = 0;
> +
> +               reg_write(vc->dev, SDT[ch], 0);
> +               tw686x_set_framerate(vc, 30);
> +
> +               reg_write(dev, VDELAY_LO[ch], 0x14);
> +               reg_write(dev, HACTIVE_LO[ch], 0xd0);
> +               reg_write(dev, VIDEO_SIZE[ch], 0);
> +
> +               for (pb = 0; pb < 2; pb++) {
> +                       err = tw686x_alloc_dma(vc, pb);
> +                       if (err)
> +                               goto error;
> +               }
> +
> +               vc->vidq.io_modes = VB2_READ | VB2_MMAP | VB2_DMABUF;
> +               vc->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> +               vc->vidq.drv_priv = vc;
> +               vc->vidq.buf_struct_size = sizeof(struct tw686x_v4l2_buf);
> +               vc->vidq.ops = &tw686x_video_qops;
> +               vc->vidq.mem_ops = &vb2_vmalloc_memops;
> +               vc->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +               vc->vidq.min_buffers_needed = 2;
> +               vc->vidq.lock = &vc->vb_mutex;
> +

I missed the GFP_DMA32 on vb2_queue.gfp_flags.

Feel free to amend it, unless you want me to submit a patch for you to pick.

Thanks,
-- 
Ezequiel García, VanguardiaSur
www.vanguardiasur.com.ar

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2016-03-22 16:16 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-03-21 11:41 [PATCH 0/4] tw686x drivers Hans Verkuil
2016-03-21 11:41 ` [PATCH 1/4] TW686x frame grabber driver Hans Verkuil
2016-03-21 11:41 ` [PATCH 2/4] media: Support Intersil/Techwell TW686x-based video capture cards Hans Verkuil
2016-03-22 16:16   ` Ezequiel Garcia
2016-03-21 11:41 ` [PATCH 3/4] tw686x-kh: specify that the DMA is 32 bits Hans Verkuil
2016-03-21 11:41 ` [PATCH 4/4] tw686x-kh: add audio support to the TODO list Hans Verkuil

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).