* [PATCHv3 0/2] Add driver for tw68xx PCI grabber boards
@ 2014-08-26 6:33 Hans Verkuil
2014-08-26 6:33 ` [PATCHv3 1/2] tw68: add support for Techwell " Hans Verkuil
2014-08-26 6:33 ` [PATCHv3 2/2] MAINTAINERS: add tw68 entry Hans Verkuil
0 siblings, 2 replies; 7+ messages in thread
From: Hans Verkuil @ 2014-08-26 6:33 UTC (permalink / raw)
To: linux-media
Changes since v2:
- Drop William Brack's email address since that's no longer valid. I have
not been able to contact him.
- A few dev_err calls in the interrupt handler were replaced by dev_dbg since
those interrupts occur during normal operation, either when starting streaming
or when switching inputs.
Add support for the tw68 driver. The driver has been out-of-tree for many
years on gitorious: https://gitorious.org/tw68/tw68-v2
I have refactored and ported that driver to the latest V4L2 core frameworks.
Tested with my Techwell tw6805a and tw6816 grabber boards.
Note that there is no audio support. If anyone is interested in adding alsa
support, please contact me. It's definitely doable.
These devices are quite common and people are using the out-of-tree driver,
so it would be nice to have proper support for this in the mainline kernel.
Regards,
Hans
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCHv3 1/2] tw68: add support for Techwell tw68xx PCI grabber boards
2014-08-26 6:33 [PATCHv3 0/2] Add driver for tw68xx PCI grabber boards Hans Verkuil
@ 2014-08-26 6:33 ` Hans Verkuil
2014-09-02 19:21 ` Mauro Carvalho Chehab
2014-08-26 6:33 ` [PATCHv3 2/2] MAINTAINERS: add tw68 entry Hans Verkuil
1 sibling, 1 reply; 7+ messages in thread
From: Hans Verkuil @ 2014-08-26 6:33 UTC (permalink / raw)
To: linux-media; +Cc: Hans Verkuil
From: Hans Verkuil <hans.verkuil@cisco.com>
Add support for the tw68 driver. The driver has been out-of-tree for many
years on gitorious: https://gitorious.org/tw68/tw68-v2
I have refactored and ported that driver to the latest V4L2 core frameworks.
Tested with my Techwell tw6805a and tw6816 grabber boards.
Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
---
drivers/media/pci/Kconfig | 1 +
drivers/media/pci/Makefile | 1 +
drivers/media/pci/tw68/Kconfig | 10 +
drivers/media/pci/tw68/Makefile | 3 +
drivers/media/pci/tw68/tw68-core.c | 434 ++++++++++++++
drivers/media/pci/tw68/tw68-reg.h | 195 +++++++
drivers/media/pci/tw68/tw68-risc.c | 230 ++++++++
drivers/media/pci/tw68/tw68-video.c | 1060 +++++++++++++++++++++++++++++++++++
drivers/media/pci/tw68/tw68.h | 231 ++++++++
9 files changed, 2165 insertions(+)
create mode 100644 drivers/media/pci/tw68/Kconfig
create mode 100644 drivers/media/pci/tw68/Makefile
create mode 100644 drivers/media/pci/tw68/tw68-core.c
create mode 100644 drivers/media/pci/tw68/tw68-reg.h
create mode 100644 drivers/media/pci/tw68/tw68-risc.c
create mode 100644 drivers/media/pci/tw68/tw68-video.c
create mode 100644 drivers/media/pci/tw68/tw68.h
diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
index 5c16c9c..9332807 100644
--- a/drivers/media/pci/Kconfig
+++ b/drivers/media/pci/Kconfig
@@ -20,6 +20,7 @@ source "drivers/media/pci/ivtv/Kconfig"
source "drivers/media/pci/zoran/Kconfig"
source "drivers/media/pci/saa7146/Kconfig"
source "drivers/media/pci/solo6x10/Kconfig"
+source "drivers/media/pci/tw68/Kconfig"
endif
if MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT
diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
index dc2ebbe..73d9c0f 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_VIDEO_CX88) += cx88/
obj-$(CONFIG_VIDEO_BT848) += bt8xx/
obj-$(CONFIG_VIDEO_SAA7134) += saa7134/
obj-$(CONFIG_VIDEO_SAA7164) += saa7164/
+obj-$(CONFIG_VIDEO_TW68) += tw68/
obj-$(CONFIG_VIDEO_MEYE) += meye/
obj-$(CONFIG_STA2X11_VIP) += sta2x11/
obj-$(CONFIG_VIDEO_SOLO6X10) += solo6x10/
diff --git a/drivers/media/pci/tw68/Kconfig b/drivers/media/pci/tw68/Kconfig
new file mode 100644
index 0000000..5425ba1
--- /dev/null
+++ b/drivers/media/pci/tw68/Kconfig
@@ -0,0 +1,10 @@
+config VIDEO_TW68
+ tristate "Techwell tw68x Video For Linux"
+ depends on VIDEO_DEV && PCI && VIDEO_V4L2
+ select I2C_ALGOBIT
+ select VIDEOBUF2_DMA_SG
+ ---help---
+ Support for Techwell tw68xx based frame grabber boards.
+
+ To compile this driver as a module, choose M here: the
+ module will be called tw68.
diff --git a/drivers/media/pci/tw68/Makefile b/drivers/media/pci/tw68/Makefile
new file mode 100644
index 0000000..3d02f28
--- /dev/null
+++ b/drivers/media/pci/tw68/Makefile
@@ -0,0 +1,3 @@
+tw68-objs := tw68-core.o tw68-video.o tw68-risc.o
+
+obj-$(CONFIG_VIDEO_TW68) += tw68.o
diff --git a/drivers/media/pci/tw68/tw68-core.c b/drivers/media/pci/tw68/tw68-core.c
new file mode 100644
index 0000000..baf93af
--- /dev/null
+++ b/drivers/media/pci/tw68/tw68-core.c
@@ -0,0 +1,434 @@
+/*
+ * tw68-core.c
+ * Core functions for the Techwell 68xx driver
+ *
+ * Much of this code is derived from the cx88 and sa7134 drivers, which
+ * were in turn derived from the bt87x driver. The original work was by
+ * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab,
+ * Hans Verkuil, Andy Walls and many others. Their work is gratefully
+ * acknowledged. Full credit goes to them - any problems within this code
+ * are mine.
+ *
+ * Copyright (C) 2009 William M. Brack
+ *
+ * Refactored and updated to the latest v4l core frameworks:
+ *
+ * Copyright (C) 2014 Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/kmod.h>
+#include <linux/sound.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/dma-mapping.h>
+#include <linux/pm.h>
+
+#include <media/v4l2-dev.h>
+#include "tw68.h"
+#include "tw68-reg.h"
+
+MODULE_DESCRIPTION("v4l2 driver module for tw6800 based video capture cards");
+MODULE_AUTHOR("William M. Brack");
+MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>");
+MODULE_LICENSE("GPL");
+
+static unsigned int latency = UNSET;
+module_param(latency, int, 0444);
+MODULE_PARM_DESC(latency, "pci latency timer");
+
+static unsigned int video_nr[] = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET };
+module_param_array(video_nr, int, NULL, 0444);
+MODULE_PARM_DESC(video_nr, "video device number");
+
+static unsigned int card[] = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET };
+module_param_array(card, int, NULL, 0444);
+MODULE_PARM_DESC(card, "card type");
+
+static atomic_t tw68_instance = ATOMIC_INIT(0);
+
+/* ------------------------------------------------------------------ */
+
+/*
+ * Please add any new PCI IDs to: http://pci-ids.ucw.cz. This keeps
+ * the PCI ID database up to date. Note that the entries must be
+ * added under vendor 0x1797 (Techwell Inc.) as subsystem IDs.
+ */
+struct pci_device_id tw68_pci_tbl[] = {
+ {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6800)},
+ {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6801)},
+ {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6804)},
+ {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6816_1)},
+ {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6816_2)},
+ {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6816_3)},
+ {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6816_4)},
+ {0,}
+};
+
+/* ------------------------------------------------------------------ */
+
+
+/*
+ * The device is given a "soft reset". According to the specifications,
+ * after this "all register content remain unchanged", so we also write
+ * to all specified registers manually as well (mostly to manufacturer's
+ * specified reset values)
+ */
+static int tw68_hw_init1(struct tw68_dev *dev)
+{
+ /* Assure all interrupts are disabled */
+ tw_writel(TW68_INTMASK, 0); /* 020 */
+ /* Clear any pending interrupts */
+ tw_writel(TW68_INTSTAT, 0xffffffff); /* 01C */
+ /* Stop risc processor, set default buffer level */
+ tw_writel(TW68_DMAC, 0x1600);
+
+ tw_writeb(TW68_ACNTL, 0x80); /* 218 soft reset */
+ msleep(100);
+
+ tw_writeb(TW68_INFORM, 0x40); /* 208 mux0, 27mhz xtal */
+ tw_writeb(TW68_OPFORM, 0x04); /* 20C analog line-lock */
+ tw_writeb(TW68_HSYNC, 0); /* 210 color-killer high sens */
+ tw_writeb(TW68_ACNTL, 0x42); /* 218 int vref #2, chroma adc off */
+
+ tw_writeb(TW68_CROP_HI, 0x02); /* 21C Hactive m.s. bits */
+ tw_writeb(TW68_VDELAY_LO, 0x12);/* 220 Mfg specified reset value */
+ tw_writeb(TW68_VACTIVE_LO, 0xf0);
+ tw_writeb(TW68_HDELAY_LO, 0x0f);
+ tw_writeb(TW68_HACTIVE_LO, 0xd0);
+
+ tw_writeb(TW68_CNTRL1, 0xcd); /* 230 Wide Chroma BPF B/W
+ * Secam reduction, Adap comb for
+ * NTSC, Op Mode 1 */
+
+ tw_writeb(TW68_VSCALE_LO, 0); /* 234 */
+ tw_writeb(TW68_SCALE_HI, 0x11); /* 238 */
+ tw_writeb(TW68_HSCALE_LO, 0); /* 23c */
+ tw_writeb(TW68_BRIGHT, 0); /* 240 */
+ tw_writeb(TW68_CONTRAST, 0x5c); /* 244 */
+ tw_writeb(TW68_SHARPNESS, 0x51);/* 248 */
+ tw_writeb(TW68_SAT_U, 0x80); /* 24C */
+ tw_writeb(TW68_SAT_V, 0x80); /* 250 */
+ tw_writeb(TW68_HUE, 0x00); /* 254 */
+
+ /* TODO - Check that none of these are set by control defaults */
+ tw_writeb(TW68_SHARP2, 0x53); /* 258 Mfg specified reset val */
+ tw_writeb(TW68_VSHARP, 0x80); /* 25C Sharpness Coring val 8 */
+ tw_writeb(TW68_CORING, 0x44); /* 260 CTI and Vert Peak coring */
+ tw_writeb(TW68_CNTRL2, 0x00); /* 268 No power saving enabled */
+ tw_writeb(TW68_SDT, 0x07); /* 270 Enable shadow reg, auto-det */
+ tw_writeb(TW68_SDTR, 0x7f); /* 274 All stds recog, don't start */
+ tw_writeb(TW68_CLMPG, 0x50); /* 280 Clamp end at 40 sys clocks */
+ tw_writeb(TW68_IAGC, 0x22); /* 284 Mfg specified reset val */
+ tw_writeb(TW68_AGCGAIN, 0xf0); /* 288 AGC gain when loop disabled */
+ tw_writeb(TW68_PEAKWT, 0xd8); /* 28C White peak threshold */
+ tw_writeb(TW68_CLMPL, 0x3c); /* 290 Y channel clamp level */
+/* tw_writeb(TW68_SYNCT, 0x38);*/ /* 294 Sync amplitude */
+ tw_writeb(TW68_SYNCT, 0x30); /* 294 Sync amplitude */
+ tw_writeb(TW68_MISSCNT, 0x44); /* 298 Horiz sync, VCR detect sens */
+ tw_writeb(TW68_PCLAMP, 0x28); /* 29C Clamp pos from PLL sync */
+ /* Bit DETV of VCNTL1 helps sync multi cams/chip board */
+ tw_writeb(TW68_VCNTL1, 0x04); /* 2A0 */
+ tw_writeb(TW68_VCNTL2, 0); /* 2A4 */
+ tw_writeb(TW68_CKILL, 0x68); /* 2A8 Mfg specified reset val */
+ tw_writeb(TW68_COMB, 0x44); /* 2AC Mfg specified reset val */
+ tw_writeb(TW68_LDLY, 0x30); /* 2B0 Max positive luma delay */
+ tw_writeb(TW68_MISC1, 0x14); /* 2B4 Mfg specified reset val */
+ tw_writeb(TW68_LOOP, 0xa5); /* 2B8 Mfg specified reset val */
+ tw_writeb(TW68_MISC2, 0xe0); /* 2BC Enable colour killer */
+ tw_writeb(TW68_MVSN, 0); /* 2C0 */
+ tw_writeb(TW68_CLMD, 0x05); /* 2CC slice level auto, clamp med. */
+ tw_writeb(TW68_IDCNTL, 0); /* 2D0 Writing zero to this register
+ * selects NTSC ID detection,
+ * but doesn't change the
+ * sensitivity (which has a reset
+ * value of 1E). Since we are
+ * not doing auto-detection, it
+ * has no real effect */
+ tw_writeb(TW68_CLCNTL1, 0); /* 2D4 */
+ tw_writel(TW68_VBIC, 0x03); /* 010 */
+ tw_writel(TW68_CAP_CTL, 0x03); /* 040 Enable both even & odd flds */
+ tw_writel(TW68_DMAC, 0x2000); /* patch set had 0x2080 */
+ tw_writel(TW68_TESTREG, 0); /* 02C */
+
+ /*
+ * Some common boards, especially inexpensive single-chip models,
+ * use the GPIO bits 0-3 to control an on-board video-output mux.
+ * For these boards, we need to set up the GPIO register into
+ * "normal" mode, set bits 0-3 as output, and then set those bits
+ * zero.
+ *
+ * Eventually, it would be nice if we could identify these boards
+ * uniquely, and only do this initialisation if the board has been
+ * identify. For the moment, however, it shouldn't hurt anything
+ * to do these steps.
+ */
+ tw_writel(TW68_GPIOC, 0); /* Set the GPIO to "normal", no ints */
+ tw_writel(TW68_GPOE, 0x0f); /* Set bits 0-3 to "output" */
+ tw_writel(TW68_GPDATA, 0); /* Set all bits to low state */
+
+ /* Initialize the device control structures */
+ mutex_init(&dev->lock);
+ spin_lock_init(&dev->slock);
+
+ /* Initialize any subsystems */
+ tw68_video_init1(dev);
+ return 0;
+}
+
+static irqreturn_t tw68_irq(int irq, void *dev_id)
+{
+ struct tw68_dev *dev = dev_id;
+ u32 status, orig;
+ int loop;
+
+ status = orig = tw_readl(TW68_INTSTAT) & dev->pci_irqmask;
+ /* Check if anything to do */
+ if (0 == status)
+ return IRQ_NONE; /* Nope - return */
+ for (loop = 0; loop < 10; loop++) {
+ if (status & dev->board_virqmask) /* video interrupt */
+ tw68_irq_video_done(dev, status);
+ status = tw_readl(TW68_INTSTAT) & dev->pci_irqmask;
+ if (0 == status)
+ return IRQ_HANDLED;
+ }
+ dev_dbg(&dev->pci->dev, "%s: **** INTERRUPT NOT HANDLED - clearing mask (orig 0x%08x, cur 0x%08x)",
+ dev->name, orig, tw_readl(TW68_INTSTAT));
+ dev_dbg(&dev->pci->dev, "%s: pci_irqmask 0x%08x; board_virqmask 0x%08x ****\n",
+ dev->name, dev->pci_irqmask, dev->board_virqmask);
+ tw_clearl(TW68_INTMASK, dev->pci_irqmask);
+ return IRQ_HANDLED;
+}
+
+static int tw68_initdev(struct pci_dev *pci_dev,
+ const struct pci_device_id *pci_id)
+{
+ struct tw68_dev *dev;
+ int vidnr = -1;
+ int err;
+
+ dev = devm_kzalloc(&pci_dev->dev, sizeof(*dev), GFP_KERNEL);
+ if (NULL == dev)
+ return -ENOMEM;
+
+ dev->instance = v4l2_device_set_name(&dev->v4l2_dev, "tw68",
+ &tw68_instance);
+
+ err = v4l2_device_register(&pci_dev->dev, &dev->v4l2_dev);
+ if (err)
+ return err;
+
+ /* pci init */
+ dev->pci = pci_dev;
+ if (pci_enable_device(pci_dev)) {
+ err = -EIO;
+ goto fail1;
+ }
+
+ dev->name = dev->v4l2_dev.name;
+
+ if (UNSET != latency) {
+ pr_info("%s: setting pci latency timer to %d\n",
+ dev->name, latency);
+ pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, latency);
+ }
+
+ /* print pci info */
+ pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &dev->pci_rev);
+ pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat);
+ pr_info("%s: found at %s, rev: %d, irq: %d, latency: %d, mmio: 0x%llx\n",
+ dev->name, pci_name(pci_dev), dev->pci_rev, pci_dev->irq,
+ dev->pci_lat, (u64)pci_resource_start(pci_dev, 0));
+ pci_set_master(pci_dev);
+ if (!pci_dma_supported(pci_dev, DMA_BIT_MASK(32))) {
+ pr_info("%s: Oops: no 32bit PCI DMA ???\n", dev->name);
+ err = -EIO;
+ goto fail1;
+ }
+
+ switch (pci_id->device) {
+ case PCI_DEVICE_ID_6800: /* TW6800 */
+ dev->vdecoder = TW6800;
+ dev->board_virqmask = TW68_VID_INTS;
+ break;
+ case PCI_DEVICE_ID_6801: /* Video decoder for TW6802 */
+ dev->vdecoder = TW6801;
+ dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX;
+ break;
+ case PCI_DEVICE_ID_6804: /* Video decoder for TW6804 */
+ dev->vdecoder = TW6804;
+ dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX;
+ break;
+ default:
+ dev->vdecoder = TWXXXX; /* To be announced */
+ dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX;
+ break;
+ }
+
+ /* get mmio */
+ if (!request_mem_region(pci_resource_start(pci_dev, 0),
+ pci_resource_len(pci_dev, 0),
+ dev->name)) {
+ err = -EBUSY;
+ pr_err("%s: can't get MMIO memory @ 0x%llx\n",
+ dev->name,
+ (unsigned long long)pci_resource_start(pci_dev, 0));
+ goto fail1;
+ }
+ dev->lmmio = ioremap(pci_resource_start(pci_dev, 0),
+ pci_resource_len(pci_dev, 0));
+ dev->bmmio = (__u8 __iomem *)dev->lmmio;
+ if (NULL == dev->lmmio) {
+ err = -EIO;
+ pr_err("%s: can't ioremap() MMIO memory\n",
+ dev->name);
+ goto fail2;
+ }
+ /* initialize hardware #1 */
+ /* Then do any initialisation wanted before interrupts are on */
+ tw68_hw_init1(dev);
+
+ /* get irq */
+ err = devm_request_irq(&pci_dev->dev, pci_dev->irq, tw68_irq,
+ IRQF_SHARED | IRQF_DISABLED, dev->name, dev);
+ if (err < 0) {
+ pr_err("%s: can't get IRQ %d\n",
+ dev->name, pci_dev->irq);
+ goto fail3;
+ }
+
+ /*
+ * Now do remainder of initialisation, first for
+ * things unique for this card, then for general board
+ */
+ if (dev->instance < TW68_MAXBOARDS)
+ vidnr = video_nr[dev->instance];
+ /* initialise video function first */
+ err = tw68_video_init2(dev, vidnr);
+ if (err < 0) {
+ pr_err("%s: can't register video device\n",
+ dev->name);
+ goto fail4;
+ }
+ tw_setl(TW68_INTMASK, dev->pci_irqmask);
+
+ pr_info("%s: registered device %s\n",
+ dev->name, video_device_node_name(&dev->vdev));
+
+ return 0;
+
+fail4:
+ video_unregister_device(&dev->vdev);
+fail3:
+ iounmap(dev->lmmio);
+fail2:
+ release_mem_region(pci_resource_start(pci_dev, 0),
+ pci_resource_len(pci_dev, 0));
+fail1:
+ v4l2_device_unregister(&dev->v4l2_dev);
+ return err;
+}
+
+static void tw68_finidev(struct pci_dev *pci_dev)
+{
+ struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
+ struct tw68_dev *dev =
+ container_of(v4l2_dev, struct tw68_dev, v4l2_dev);
+
+ /* shutdown subsystems */
+ tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN);
+ tw_writel(TW68_INTMASK, 0);
+
+ /* unregister */
+ video_unregister_device(&dev->vdev);
+ v4l2_ctrl_handler_free(&dev->hdl);
+
+ /* release resources */
+ iounmap(dev->lmmio);
+ release_mem_region(pci_resource_start(pci_dev, 0),
+ pci_resource_len(pci_dev, 0));
+
+ v4l2_device_unregister(&dev->v4l2_dev);
+}
+
+#ifdef CONFIG_PM
+
+static int tw68_suspend(struct pci_dev *pci_dev , pm_message_t state)
+{
+ struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
+ struct tw68_dev *dev = container_of(v4l2_dev,
+ struct tw68_dev, v4l2_dev);
+
+ tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN);
+ dev->pci_irqmask &= ~TW68_VID_INTS;
+ tw_writel(TW68_INTMASK, 0);
+
+ synchronize_irq(pci_dev->irq);
+
+ pci_save_state(pci_dev);
+ pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state));
+ vb2_discard_done(&dev->vidq);
+
+ return 0;
+}
+
+static int tw68_resume(struct pci_dev *pci_dev)
+{
+ struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
+ struct tw68_dev *dev = container_of(v4l2_dev,
+ struct tw68_dev, v4l2_dev);
+ struct tw68_buf *buf;
+ unsigned long flags;
+
+ pci_set_power_state(pci_dev, PCI_D0);
+ pci_restore_state(pci_dev);
+
+ /* Do things that are done in tw68_initdev ,
+ except of initializing memory structures.*/
+
+ msleep(100);
+
+ tw68_set_tvnorm_hw(dev);
+
+ /*resume unfinished buffer(s)*/
+ spin_lock_irqsave(&dev->slock, flags);
+ buf = container_of(dev->active.next, struct tw68_buf, list);
+
+ tw68_video_start_dma(dev, buf);
+
+ spin_unlock_irqrestore(&dev->slock, flags);
+
+ return 0;
+}
+#endif
+
+/* ----------------------------------------------------------- */
+
+static struct pci_driver tw68_pci_driver = {
+ .name = "tw68",
+ .id_table = tw68_pci_tbl,
+ .probe = tw68_initdev,
+ .remove = tw68_finidev,
+#ifdef CONFIG_PM
+ .suspend = tw68_suspend,
+ .resume = tw68_resume
+#endif
+};
+
+module_pci_driver(tw68_pci_driver);
diff --git a/drivers/media/pci/tw68/tw68-reg.h b/drivers/media/pci/tw68/tw68-reg.h
new file mode 100644
index 0000000..f60b3a8
--- /dev/null
+++ b/drivers/media/pci/tw68/tw68-reg.h
@@ -0,0 +1,195 @@
+/*
+ * tw68-reg.h - TW68xx register offsets
+ *
+ * Much of this code is derived from the cx88 and sa7134 drivers, which
+ * were in turn derived from the bt87x driver. The original work was by
+ * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab,
+ * Hans Verkuil, Andy Walls and many others. Their work is gratefully
+ * acknowledged. Full credit goes to them - any problems within this code
+ * are mine.
+ *
+ * Copyright (C) William M. Brack
+ *
+ * Refactored and updated to the latest v4l core frameworks:
+ *
+ * Copyright (C) 2014 Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+*/
+
+#ifndef _TW68_REG_H_
+#define _TW68_REG_H_
+
+/* ---------------------------------------------------------------------- */
+#define TW68_DMAC 0x000
+#define TW68_DMAP_SA 0x004
+#define TW68_DMAP_EXE 0x008
+#define TW68_DMAP_PP 0x00c
+#define TW68_VBIC 0x010
+#define TW68_SBUSC 0x014
+#define TW68_SBUSSD 0x018
+#define TW68_INTSTAT 0x01C
+#define TW68_INTMASK 0x020
+#define TW68_GPIOC 0x024
+#define TW68_GPOE 0x028
+#define TW68_TESTREG 0x02C
+#define TW68_SBUSRD 0x030
+#define TW68_SBUS_TRIG 0x034
+#define TW68_CAP_CTL 0x040
+#define TW68_SUBSYS 0x054
+#define TW68_I2C_RST 0x064
+#define TW68_VBIINST 0x06C
+/* define bits in FIFO and DMAP Control reg */
+#define TW68_DMAP_EN (1 << 0)
+#define TW68_FIFO_EN (1 << 1)
+/* define the Interrupt Status Register bits */
+#define TW68_SBDONE (1 << 0)
+#define TW68_DMAPI (1 << 1)
+#define TW68_GPINT (1 << 2)
+#define TW68_FFOF (1 << 3)
+#define TW68_FDMIS (1 << 4)
+#define TW68_DMAPERR (1 << 5)
+#define TW68_PABORT (1 << 6)
+#define TW68_SBDONE2 (1 << 12)
+#define TW68_SBERR2 (1 << 13)
+#define TW68_PPERR (1 << 14)
+#define TW68_FFERR (1 << 15)
+#define TW68_DET50 (1 << 16)
+#define TW68_FLOCK (1 << 17)
+#define TW68_CCVALID (1 << 18)
+#define TW68_VLOCK (1 << 19)
+#define TW68_FIELD (1 << 20)
+#define TW68_SLOCK (1 << 21)
+#define TW68_HLOCK (1 << 22)
+#define TW68_VDLOSS (1 << 23)
+#define TW68_SBERR (1 << 24)
+/* define the i2c control register bits */
+#define TW68_SBMODE (0)
+#define TW68_WREN (1)
+#define TW68_SSCLK (6)
+#define TW68_SSDAT (7)
+#define TW68_SBCLK (8)
+#define TW68_WDLEN (16)
+#define TW68_RDLEN (20)
+#define TW68_SBRW (24)
+#define TW68_SBDEV (25)
+
+#define TW68_SBMODE_B (1 << TW68_SBMODE)
+#define TW68_WREN_B (1 << TW68_WREN)
+#define TW68_SSCLK_B (1 << TW68_SSCLK)
+#define TW68_SSDAT_B (1 << TW68_SSDAT)
+#define TW68_SBRW_B (1 << TW68_SBRW)
+
+#define TW68_GPDATA 0x100
+#define TW68_STATUS1 0x204
+#define TW68_INFORM 0x208
+#define TW68_OPFORM 0x20C
+#define TW68_HSYNC 0x210
+#define TW68_ACNTL 0x218
+#define TW68_CROP_HI 0x21C
+#define TW68_VDELAY_LO 0x220
+#define TW68_VACTIVE_LO 0x224
+#define TW68_HDELAY_LO 0x228
+#define TW68_HACTIVE_LO 0x22C
+#define TW68_CNTRL1 0x230
+#define TW68_VSCALE_LO 0x234
+#define TW68_SCALE_HI 0x238
+#define TW68_HSCALE_LO 0x23C
+#define TW68_BRIGHT 0x240
+#define TW68_CONTRAST 0x244
+#define TW68_SHARPNESS 0x248
+#define TW68_SAT_U 0x24C
+#define TW68_SAT_V 0x250
+#define TW68_HUE 0x254
+#define TW68_SHARP2 0x258
+#define TW68_VSHARP 0x25C
+#define TW68_CORING 0x260
+#define TW68_VBICNTL 0x264
+#define TW68_CNTRL2 0x268
+#define TW68_CC_DATA 0x26C
+#define TW68_SDT 0x270
+#define TW68_SDTR 0x274
+#define TW68_RESERV2 0x278
+#define TW68_RESERV3 0x27C
+#define TW68_CLMPG 0x280
+#define TW68_IAGC 0x284
+#define TW68_AGCGAIN 0x288
+#define TW68_PEAKWT 0x28C
+#define TW68_CLMPL 0x290
+#define TW68_SYNCT 0x294
+#define TW68_MISSCNT 0x298
+#define TW68_PCLAMP 0x29C
+#define TW68_VCNTL1 0x2A0
+#define TW68_VCNTL2 0x2A4
+#define TW68_CKILL 0x2A8
+#define TW68_COMB 0x2AC
+#define TW68_LDLY 0x2B0
+#define TW68_MISC1 0x2B4
+#define TW68_LOOP 0x2B8
+#define TW68_MISC2 0x2BC
+#define TW68_MVSN 0x2C0
+#define TW68_STATUS2 0x2C4
+#define TW68_HFREF 0x2C8
+#define TW68_CLMD 0x2CC
+#define TW68_IDCNTL 0x2D0
+#define TW68_CLCNTL1 0x2D4
+
+/* Audio */
+#define TW68_ACKI1 0x300
+#define TW68_ACKI2 0x304
+#define TW68_ACKI3 0x308
+#define TW68_ACKN1 0x30C
+#define TW68_ACKN2 0x310
+#define TW68_ACKN3 0x314
+#define TW68_SDIV 0x318
+#define TW68_LRDIV 0x31C
+#define TW68_ACCNTL 0x320
+
+#define TW68_VSCTL 0x3B8
+#define TW68_CHROMAGVAL 0x3BC
+
+#define TW68_F2CROP_HI 0x3DC
+#define TW68_F2VDELAY_LO 0x3E0
+#define TW68_F2VACTIVE_LO 0x3E4
+#define TW68_F2HDELAY_LO 0x3E8
+#define TW68_F2HACTIVE_LO 0x3EC
+#define TW68_F2CNT 0x3F0
+#define TW68_F2VSCALE_LO 0x3F4
+#define TW68_F2SCALE_HI 0x3F8
+#define TW68_F2HSCALE_LO 0x3FC
+
+#define RISC_INT_BIT 0x08000000
+#define RISC_SYNCO 0xC0000000
+#define RISC_SYNCE 0xD0000000
+#define RISC_JUMP 0xB0000000
+#define RISC_LINESTART 0x90000000
+#define RISC_INLINE 0xA0000000
+
+#define VideoFormatNTSC 0
+#define VideoFormatNTSCJapan 0
+#define VideoFormatPALBDGHI 1
+#define VideoFormatSECAM 2
+#define VideoFormatNTSC443 3
+#define VideoFormatPALM 4
+#define VideoFormatPALN 5
+#define VideoFormatPALNC 5
+#define VideoFormatPAL60 6
+#define VideoFormatAuto 7
+
+#define ColorFormatRGB32 0x00
+#define ColorFormatRGB24 0x10
+#define ColorFormatRGB16 0x20
+#define ColorFormatRGB15 0x30
+#define ColorFormatYUY2 0x40
+#define ColorFormatBSWAP 0x04
+#define ColorFormatWSWAP 0x08
+#define ColorFormatGamma 0x80
+#endif
diff --git a/drivers/media/pci/tw68/tw68-risc.c b/drivers/media/pci/tw68/tw68-risc.c
new file mode 100644
index 0000000..7439db2
--- /dev/null
+++ b/drivers/media/pci/tw68/tw68-risc.c
@@ -0,0 +1,230 @@
+/*
+ * tw68_risc.c
+ * Part of the device driver for Techwell 68xx based cards
+ *
+ * Much of this code is derived from the cx88 and sa7134 drivers, which
+ * were in turn derived from the bt87x driver. The original work was by
+ * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab,
+ * Hans Verkuil, Andy Walls and many others. Their work is gratefully
+ * acknowledged. Full credit goes to them - any problems within this code
+ * are mine.
+ *
+ * Copyright (C) 2009 William M. Brack
+ *
+ * Refactored and updated to the latest v4l core frameworks:
+ *
+ * Copyright (C) 2014 Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "tw68.h"
+
+/**
+ * @rp pointer to current risc program position
+ * @sglist pointer to "scatter-gather list" of buffer pointers
+ * @offset offset to target memory buffer
+ * @sync_line 0 -> no sync, 1 -> odd sync, 2 -> even sync
+ * @bpl number of bytes per scan line
+ * @padding number of bytes of padding to add
+ * @lines number of lines in field
+ * @jump insert a jump at the start
+ */
+static __le32 *tw68_risc_field(__le32 *rp, struct scatterlist *sglist,
+ unsigned int offset, u32 sync_line,
+ unsigned int bpl, unsigned int padding,
+ unsigned int lines, bool jump)
+{
+ struct scatterlist *sg;
+ unsigned int line, todo, done;
+
+ if (jump) {
+ *(rp++) = cpu_to_le32(RISC_JUMP);
+ *(rp++) = 0;
+ }
+
+ /* sync instruction */
+ if (sync_line == 1)
+ *(rp++) = cpu_to_le32(RISC_SYNCO);
+ else
+ *(rp++) = cpu_to_le32(RISC_SYNCE);
+ *(rp++) = 0;
+
+ /* scan lines */
+ sg = sglist;
+ for (line = 0; line < lines; line++) {
+ /* calculate next starting position */
+ while (offset && offset >= sg_dma_len(sg)) {
+ offset -= sg_dma_len(sg);
+ sg = sg_next(sg);
+ }
+ if (bpl <= sg_dma_len(sg) - offset) {
+ /* fits into current chunk */
+ *(rp++) = cpu_to_le32(RISC_LINESTART |
+ /* (offset<<12) |*/ bpl);
+ *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset);
+ offset += bpl;
+ } else {
+ /*
+ * scanline needs to be split. Put the start in
+ * whatever memory remains using RISC_LINESTART,
+ * then the remainder into following addresses
+ * given by the scatter-gather list.
+ */
+ todo = bpl; /* one full line to be done */
+ /* first fragment */
+ done = (sg_dma_len(sg) - offset);
+ *(rp++) = cpu_to_le32(RISC_LINESTART |
+ (7 << 24) |
+ done);
+ *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset);
+ todo -= done;
+ sg = sg_next(sg);
+ /* succeeding fragments have no offset */
+ while (todo > sg_dma_len(sg)) {
+ *(rp++) = cpu_to_le32(RISC_INLINE |
+ (done << 12) |
+ sg_dma_len(sg));
+ *(rp++) = cpu_to_le32(sg_dma_address(sg));
+ todo -= sg_dma_len(sg);
+ sg = sg_next(sg);
+ done += sg_dma_len(sg);
+ }
+ if (todo) {
+ /* final chunk - offset 0, count 'todo' */
+ *(rp++) = cpu_to_le32(RISC_INLINE |
+ (done << 12) |
+ todo);
+ *(rp++) = cpu_to_le32(sg_dma_address(sg));
+ }
+ offset = todo;
+ }
+ offset += padding;
+ }
+
+ return rp;
+}
+
+/**
+ * tw68_risc_buffer
+ *
+ * This routine is called by tw68-video. It allocates
+ * memory for the dma controller "program" and then fills in that
+ * memory with the appropriate "instructions".
+ *
+ * @pci_dev structure with info about the pci
+ * slot which our device is in.
+ * @risc structure with info about the memory
+ * used for our controller program.
+ * @sglist scatter-gather list entry
+ * @top_offset offset within the risc program area for the
+ * first odd frame line
+ * @bottom_offset offset within the risc program area for the
+ * first even frame line
+ * @bpl number of data bytes per scan line
+ * @padding number of extra bytes to add at end of line
+ * @lines number of scan lines
+ */
+int tw68_risc_buffer(struct pci_dev *pci,
+ struct tw68_buf *buf,
+ struct scatterlist *sglist,
+ unsigned int top_offset,
+ unsigned int bottom_offset,
+ unsigned int bpl,
+ unsigned int padding,
+ unsigned int lines)
+{
+ u32 instructions, fields;
+ __le32 *rp;
+
+ fields = 0;
+ if (UNSET != top_offset)
+ fields++;
+ if (UNSET != bottom_offset)
+ fields++;
+ /*
+ * estimate risc mem: worst case is one write per page border +
+ * one write per scan line + syncs + 2 jumps (all 2 dwords).
+ * Padding can cause next bpl to start close to a page border.
+ * First DMA region may be smaller than PAGE_SIZE
+ */
+ instructions = fields * (1 + (((bpl + padding) * lines) /
+ PAGE_SIZE) + lines) + 4;
+ buf->size = instructions * 8;
+ buf->cpu = pci_alloc_consistent(pci, buf->size, &buf->dma);
+ if (buf->cpu == NULL)
+ return -ENOMEM;
+
+ /* write risc instructions */
+ rp = buf->cpu;
+ if (UNSET != top_offset) /* generates SYNCO */
+ rp = tw68_risc_field(rp, sglist, top_offset, 1,
+ bpl, padding, lines, true);
+ if (UNSET != bottom_offset) /* generates SYNCE */
+ rp = tw68_risc_field(rp, sglist, bottom_offset, 2,
+ bpl, padding, lines, top_offset == UNSET);
+
+ /* save pointer to jmp instruction address */
+ buf->jmp = rp;
+ buf->cpu[1] = cpu_to_le32(buf->dma + 8);
+ /* assure risc buffer hasn't overflowed */
+ BUG_ON((buf->jmp - buf->cpu + 2) * sizeof(buf->cpu[0]) > buf->size);
+ return 0;
+}
+
+#if 0
+/* ------------------------------------------------------------------ */
+/* debug helper code */
+
+static void tw68_risc_decode(u32 risc, u32 addr)
+{
+#define RISC_OP(reg) (((reg) >> 28) & 7)
+ static struct instr_details {
+ char *name;
+ u8 has_data_type;
+ u8 has_byte_info;
+ u8 has_addr;
+ } instr[8] = {
+ [RISC_OP(RISC_SYNCO)] = {"syncOdd", 0, 0, 0},
+ [RISC_OP(RISC_SYNCE)] = {"syncEven", 0, 0, 0},
+ [RISC_OP(RISC_JUMP)] = {"jump", 0, 0, 1},
+ [RISC_OP(RISC_LINESTART)] = {"lineStart", 1, 1, 1},
+ [RISC_OP(RISC_INLINE)] = {"inline", 1, 1, 1},
+ };
+ u32 p;
+
+ p = RISC_OP(risc);
+ if (!(risc & 0x80000000) || !instr[p].name) {
+ pr_debug("0x%08x [ INVALID ]\n", risc);
+ return;
+ }
+ pr_debug("0x%08x %-9s IRQ=%d",
+ risc, instr[p].name, (risc >> 27) & 1);
+ if (instr[p].has_data_type)
+ pr_debug(" Type=%d", (risc >> 24) & 7);
+ if (instr[p].has_byte_info)
+ pr_debug(" Start=0x%03x Count=%03u",
+ (risc >> 12) & 0xfff, risc & 0xfff);
+ if (instr[p].has_addr)
+ pr_debug(" StartAddr=0x%08x", addr);
+ pr_debug("\n");
+}
+
+void tw68_risc_program_dump(struct tw68_core *core, struct tw68_buf *buf)
+{
+ const __le32 *addr;
+
+ pr_debug("%s: risc_program_dump: risc=%p, buf->cpu=0x%p, buf->jmp=0x%p\n",
+ core->name, buf, buf->cpu, buf->jmp);
+ for (addr = buf->cpu; addr <= buf->jmp; addr += 2)
+ tw68_risc_decode(*addr, *(addr+1));
+}
+#endif
diff --git a/drivers/media/pci/tw68/tw68-video.c b/drivers/media/pci/tw68/tw68-video.c
new file mode 100644
index 0000000..66fae23
--- /dev/null
+++ b/drivers/media/pci/tw68/tw68-video.c
@@ -0,0 +1,1060 @@
+/*
+ * tw68 functions to handle video data
+ *
+ * Much of this code is derived from the cx88 and sa7134 drivers, which
+ * were in turn derived from the bt87x driver. The original work was by
+ * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab,
+ * Hans Verkuil, Andy Walls and many others. Their work is gratefully
+ * acknowledged. Full credit goes to them - any problems within this code
+ * are mine.
+ *
+ * Copyright (C) 2009 William M. Brack
+ *
+ * Refactored and updated to the latest v4l core frameworks:
+ *
+ * Copyright (C) 2014 Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-dma-sg.h>
+
+#include "tw68.h"
+#include "tw68-reg.h"
+
+/* ------------------------------------------------------------------ */
+/* data structs for video */
+/*
+ * FIXME -
+ * Note that the saa7134 has formats, e.g. YUV420, which are classified
+ * as "planar". These affect overlay mode, and are flagged with a field
+ * ".planar" in the format. Do we need to implement this in this driver?
+ */
+static const struct tw68_format formats[] = {
+ {
+ .name = "15 bpp RGB, le",
+ .fourcc = V4L2_PIX_FMT_RGB555,
+ .depth = 16,
+ .twformat = ColorFormatRGB15,
+ }, {
+ .name = "15 bpp RGB, be",
+ .fourcc = V4L2_PIX_FMT_RGB555X,
+ .depth = 16,
+ .twformat = ColorFormatRGB15 | ColorFormatBSWAP,
+ }, {
+ .name = "16 bpp RGB, le",
+ .fourcc = V4L2_PIX_FMT_RGB565,
+ .depth = 16,
+ .twformat = ColorFormatRGB16,
+ }, {
+ .name = "16 bpp RGB, be",
+ .fourcc = V4L2_PIX_FMT_RGB565X,
+ .depth = 16,
+ .twformat = ColorFormatRGB16 | ColorFormatBSWAP,
+ }, {
+ .name = "24 bpp RGB, le",
+ .fourcc = V4L2_PIX_FMT_BGR24,
+ .depth = 24,
+ .twformat = ColorFormatRGB24,
+ }, {
+ .name = "24 bpp RGB, be",
+ .fourcc = V4L2_PIX_FMT_RGB24,
+ .depth = 24,
+ .twformat = ColorFormatRGB24 | ColorFormatBSWAP,
+ }, {
+ .name = "32 bpp RGB, le",
+ .fourcc = V4L2_PIX_FMT_BGR32,
+ .depth = 32,
+ .twformat = ColorFormatRGB32,
+ }, {
+ .name = "32 bpp RGB, be",
+ .fourcc = V4L2_PIX_FMT_RGB32,
+ .depth = 32,
+ .twformat = ColorFormatRGB32 | ColorFormatBSWAP |
+ ColorFormatWSWAP,
+ }, {
+ .name = "4:2:2 packed, YUYV",
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .depth = 16,
+ .twformat = ColorFormatYUY2,
+ }, {
+ .name = "4:2:2 packed, UYVY",
+ .fourcc = V4L2_PIX_FMT_UYVY,
+ .depth = 16,
+ .twformat = ColorFormatYUY2 | ColorFormatBSWAP,
+ }
+};
+#define FORMATS ARRAY_SIZE(formats)
+
+#define NORM_625_50 \
+ .h_delay = 3, \
+ .h_delay0 = 133, \
+ .h_start = 0, \
+ .h_stop = 719, \
+ .v_delay = 24, \
+ .vbi_v_start_0 = 7, \
+ .vbi_v_stop_0 = 22, \
+ .video_v_start = 24, \
+ .video_v_stop = 311, \
+ .vbi_v_start_1 = 319
+
+#define NORM_525_60 \
+ .h_delay = 8, \
+ .h_delay0 = 138, \
+ .h_start = 0, \
+ .h_stop = 719, \
+ .v_delay = 22, \
+ .vbi_v_start_0 = 10, \
+ .vbi_v_stop_0 = 21, \
+ .video_v_start = 22, \
+ .video_v_stop = 262, \
+ .vbi_v_start_1 = 273
+
+/*
+ * The following table is searched by tw68_s_std, first for a specific
+ * match, then for an entry which contains the desired id. The table
+ * entries should therefore be ordered in ascending order of specificity.
+ */
+static const struct tw68_tvnorm tvnorms[] = {
+ {
+ .name = "PAL", /* autodetect */
+ .id = V4L2_STD_PAL,
+ NORM_625_50,
+
+ .sync_control = 0x18,
+ .luma_control = 0x40,
+ .chroma_ctrl1 = 0x81,
+ .chroma_gain = 0x2a,
+ .chroma_ctrl2 = 0x06,
+ .vgate_misc = 0x1c,
+ .format = VideoFormatPALBDGHI,
+ }, {
+ .name = "NTSC",
+ .id = V4L2_STD_NTSC,
+ NORM_525_60,
+
+ .sync_control = 0x59,
+ .luma_control = 0x40,
+ .chroma_ctrl1 = 0x89,
+ .chroma_gain = 0x2a,
+ .chroma_ctrl2 = 0x0e,
+ .vgate_misc = 0x18,
+ .format = VideoFormatNTSC,
+ }, {
+ .name = "SECAM",
+ .id = V4L2_STD_SECAM,
+ NORM_625_50,
+
+ .sync_control = 0x18,
+ .luma_control = 0x1b,
+ .chroma_ctrl1 = 0xd1,
+ .chroma_gain = 0x80,
+ .chroma_ctrl2 = 0x00,
+ .vgate_misc = 0x1c,
+ .format = VideoFormatSECAM,
+ }, {
+ .name = "PAL-M",
+ .id = V4L2_STD_PAL_M,
+ NORM_525_60,
+
+ .sync_control = 0x59,
+ .luma_control = 0x40,
+ .chroma_ctrl1 = 0xb9,
+ .chroma_gain = 0x2a,
+ .chroma_ctrl2 = 0x0e,
+ .vgate_misc = 0x18,
+ .format = VideoFormatPALM,
+ }, {
+ .name = "PAL-Nc",
+ .id = V4L2_STD_PAL_Nc,
+ NORM_625_50,
+
+ .sync_control = 0x18,
+ .luma_control = 0x40,
+ .chroma_ctrl1 = 0xa1,
+ .chroma_gain = 0x2a,
+ .chroma_ctrl2 = 0x06,
+ .vgate_misc = 0x1c,
+ .format = VideoFormatPALNC,
+ }, {
+ .name = "PAL-60",
+ .id = V4L2_STD_PAL_60,
+ .h_delay = 186,
+ .h_start = 0,
+ .h_stop = 719,
+ .v_delay = 26,
+ .video_v_start = 23,
+ .video_v_stop = 262,
+ .vbi_v_start_0 = 10,
+ .vbi_v_stop_0 = 21,
+ .vbi_v_start_1 = 273,
+
+ .sync_control = 0x18,
+ .luma_control = 0x40,
+ .chroma_ctrl1 = 0x81,
+ .chroma_gain = 0x2a,
+ .chroma_ctrl2 = 0x06,
+ .vgate_misc = 0x1c,
+ .format = VideoFormatPAL60,
+ }
+};
+#define TVNORMS ARRAY_SIZE(tvnorms)
+
+static const struct tw68_format *format_by_fourcc(unsigned int fourcc)
+{
+ unsigned int i;
+
+ for (i = 0; i < FORMATS; i++)
+ if (formats[i].fourcc == fourcc)
+ return formats+i;
+ return NULL;
+}
+
+
+/* ------------------------------------------------------------------ */
+/*
+ * Note that the cropping rectangles are described in terms of a single
+ * frame, i.e. line positions are only 1/2 the interlaced equivalent
+ */
+static void set_tvnorm(struct tw68_dev *dev, const struct tw68_tvnorm *norm)
+{
+ if (norm != dev->tvnorm) {
+ dev->width = 720;
+ dev->height = (norm->id & V4L2_STD_525_60) ? 480 : 576;
+ dev->tvnorm = norm;
+ tw68_set_tvnorm_hw(dev);
+ }
+}
+
+/*
+ * tw68_set_scale
+ *
+ * Scaling and Cropping for video decoding
+ *
+ * We are working with 3 values for horizontal and vertical - scale,
+ * delay and active.
+ *
+ * HACTIVE represent the actual number of pixels in the "usable" image,
+ * before scaling. HDELAY represents the number of pixels skipped
+ * between the start of the horizontal sync and the start of the image.
+ * HSCALE is calculated using the formula
+ * HSCALE = (HACTIVE / (#pixels desired)) * 256
+ *
+ * The vertical registers are similar, except based upon the total number
+ * of lines in the image, and the first line of the image (i.e. ignoring
+ * vertical sync and VBI).
+ *
+ * Note that the number of bytes reaching the FIFO (and hence needing
+ * to be processed by the DMAP program) is completely dependent upon
+ * these values, especially HSCALE.
+ *
+ * Parameters:
+ * @dev pointer to the device structure, needed for
+ * getting current norm (as well as debug print)
+ * @width actual image width (from user buffer)
+ * @height actual image height
+ * @field indicates Top, Bottom or Interlaced
+ */
+static int tw68_set_scale(struct tw68_dev *dev, unsigned int width,
+ unsigned int height, enum v4l2_field field)
+{
+ const struct tw68_tvnorm *norm = dev->tvnorm;
+ /* set individually for debugging clarity */
+ int hactive, hdelay, hscale;
+ int vactive, vdelay, vscale;
+ int comb;
+
+ if (V4L2_FIELD_HAS_BOTH(field)) /* if field is interlaced */
+ height /= 2; /* we must set for 1-frame */
+
+ pr_debug("%s: width=%d, height=%d, both=%d\n"
+ " tvnorm h_delay=%d, h_start=%d, h_stop=%d, "
+ "v_delay=%d, v_start=%d, v_stop=%d\n" , __func__,
+ width, height, V4L2_FIELD_HAS_BOTH(field),
+ norm->h_delay, norm->h_start, norm->h_stop,
+ norm->v_delay, norm->video_v_start,
+ norm->video_v_stop);
+
+ switch (dev->vdecoder) {
+ case TW6800:
+ hdelay = norm->h_delay0;
+ break;
+ default:
+ hdelay = norm->h_delay;
+ break;
+ }
+
+ hdelay += norm->h_start;
+ hactive = norm->h_stop - norm->h_start + 1;
+
+ hscale = (hactive * 256) / (width);
+
+ vdelay = norm->v_delay;
+ vactive = ((norm->id & V4L2_STD_525_60) ? 524 : 624) / 2 - norm->video_v_start;
+ vscale = (vactive * 256) / height;
+
+ pr_debug("%s: %dx%d [%s%s,%s]\n", __func__,
+ width, height,
+ V4L2_FIELD_HAS_TOP(field) ? "T" : "",
+ V4L2_FIELD_HAS_BOTTOM(field) ? "B" : "",
+ v4l2_norm_to_name(dev->tvnorm->id));
+ pr_debug("%s: hactive=%d, hdelay=%d, hscale=%d; "
+ "vactive=%d, vdelay=%d, vscale=%d\n", __func__,
+ hactive, hdelay, hscale, vactive, vdelay, vscale);
+
+ comb = ((vdelay & 0x300) >> 2) |
+ ((vactive & 0x300) >> 4) |
+ ((hdelay & 0x300) >> 6) |
+ ((hactive & 0x300) >> 8);
+ pr_debug("%s: setting CROP_HI=%02x, VDELAY_LO=%02x, "
+ "VACTIVE_LO=%02x, HDELAY_LO=%02x, HACTIVE_LO=%02x\n",
+ __func__, comb, vdelay, vactive, hdelay, hactive);
+ tw_writeb(TW68_CROP_HI, comb);
+ tw_writeb(TW68_VDELAY_LO, vdelay & 0xff);
+ tw_writeb(TW68_VACTIVE_LO, vactive & 0xff);
+ tw_writeb(TW68_HDELAY_LO, hdelay & 0xff);
+ tw_writeb(TW68_HACTIVE_LO, hactive & 0xff);
+
+ comb = ((vscale & 0xf00) >> 4) | ((hscale & 0xf00) >> 8);
+ pr_debug("%s: setting SCALE_HI=%02x, VSCALE_LO=%02x, "
+ "HSCALE_LO=%02x\n", __func__, comb, vscale, hscale);
+ tw_writeb(TW68_SCALE_HI, comb);
+ tw_writeb(TW68_VSCALE_LO, vscale);
+ tw_writeb(TW68_HSCALE_LO, hscale);
+
+ return 0;
+}
+
+/* ------------------------------------------------------------------ */
+
+int tw68_video_start_dma(struct tw68_dev *dev, struct tw68_buf *buf)
+{
+ /* Set cropping and scaling */
+ tw68_set_scale(dev, dev->width, dev->height, dev->field);
+ /*
+ * Set start address for RISC program. Note that if the DMAP
+ * processor is currently running, it must be stopped before
+ * a new address can be set.
+ */
+ tw_clearl(TW68_DMAC, TW68_DMAP_EN);
+ tw_writel(TW68_DMAP_SA, cpu_to_le32(buf->dma));
+ /* Clear any pending interrupts */
+ tw_writel(TW68_INTSTAT, dev->board_virqmask);
+ /* Enable the risc engine and the fifo */
+ tw_andorl(TW68_DMAC, 0xff, dev->fmt->twformat |
+ ColorFormatGamma | TW68_DMAP_EN | TW68_FIFO_EN);
+ dev->pci_irqmask |= dev->board_virqmask;
+ tw_setl(TW68_INTMASK, dev->pci_irqmask);
+ return 0;
+}
+
+/* ------------------------------------------------------------------ */
+
+/* nr of (tw68-)pages for the given buffer size */
+static int tw68_buffer_pages(int size)
+{
+ size = PAGE_ALIGN(size);
+ size += PAGE_SIZE; /* for non-page-aligned buffers */
+ size /= 4096;
+ return size;
+}
+
+/* calc max # of buffers from size (must not exceed the 4MB virtual
+ * address space per DMA channel) */
+static int tw68_buffer_count(unsigned int size, unsigned int count)
+{
+ unsigned int maxcount;
+
+ maxcount = 1024 / tw68_buffer_pages(size);
+ if (count > maxcount)
+ count = maxcount;
+ return count;
+}
+
+/* ------------------------------------------------------------- */
+/* vb2 queue operations */
+
+static int tw68_queue_setup(struct vb2_queue *q, const struct v4l2_format *fmt,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], void *alloc_ctxs[])
+{
+ struct tw68_dev *dev = vb2_get_drv_priv(q);
+ unsigned tot_bufs = q->num_buffers + *num_buffers;
+
+ sizes[0] = (dev->fmt->depth * dev->width * dev->height) >> 3;
+ /*
+ * We allow create_bufs, but only if the sizeimage is the same as the
+ * current sizeimage. The tw68_buffer_count calculation becomes quite
+ * difficult otherwise.
+ */
+ if (fmt && fmt->fmt.pix.sizeimage < sizes[0])
+ return -EINVAL;
+ *num_planes = 1;
+ if (tot_bufs < 2)
+ tot_bufs = 2;
+ tot_bufs = tw68_buffer_count(sizes[0], tot_bufs);
+ *num_buffers = tot_bufs - q->num_buffers;
+
+ return 0;
+}
+
+/*
+ * The risc program for each buffers works as follows: it starts with a simple
+ * 'JUMP to addr + 8', which is effectively a NOP. Then the program to DMA the
+ * buffer follows and at the end we have a JUMP back to the start + 8 (skipping
+ * the initial JUMP).
+ *
+ * This is the program of the first buffer to be queued if the active list is
+ * empty and it just keeps DMAing this buffer without generating any interrupts.
+ *
+ * If a new buffer is added then the initial JUMP in the program generates an
+ * interrupt as well which signals that the previous buffer has been DMAed
+ * successfully and that it can be returned to userspace.
+ *
+ * It also sets the final jump of the previous buffer to the start of the new
+ * buffer, thus chaining the new buffer into the DMA chain. This is a single
+ * atomic u32 write, so there is no race condition.
+ *
+ * The end-result of all this that you only get an interrupt when a buffer
+ * is ready, so the control flow is very easy.
+ */
+static void tw68_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct tw68_dev *dev = vb2_get_drv_priv(vq);
+ struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb);
+ struct tw68_buf *prev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->slock, flags);
+
+ /* append a 'JUMP to start of buffer' to the buffer risc program */
+ buf->jmp[0] = cpu_to_le32(RISC_JUMP);
+ buf->jmp[1] = cpu_to_le32(buf->dma + 8);
+
+ if (!list_empty(&dev->active)) {
+ prev = list_entry(dev->active.prev, struct tw68_buf, list);
+ buf->cpu[0] |= cpu_to_le32(RISC_INT_BIT);
+ prev->jmp[1] = cpu_to_le32(buf->dma);
+ }
+ list_add_tail(&buf->list, &dev->active);
+ spin_unlock_irqrestore(&dev->slock, flags);
+}
+
+/*
+ * buffer_prepare
+ *
+ * Set the ancilliary information into the buffer structure. This
+ * includes generating the necessary risc program if it hasn't already
+ * been done for the current buffer format.
+ * The structure fh contains the details of the format requested by the
+ * user - type, width, height and #fields. This is compared with the
+ * last format set for the current buffer. If they differ, the risc
+ * code (which controls the filling of the buffer) is (re-)generated.
+ */
+static int tw68_buf_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct tw68_dev *dev = vb2_get_drv_priv(vq);
+ struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb);
+ struct sg_table *dma = vb2_dma_sg_plane_desc(vb, 0);
+ unsigned size, bpl;
+ int rc;
+
+ size = (dev->width * dev->height * dev->fmt->depth) >> 3;
+ if (vb2_plane_size(vb, 0) < size)
+ return -EINVAL;
+ vb2_set_plane_payload(vb, 0, size);
+
+ rc = dma_map_sg(&dev->pci->dev, dma->sgl, dma->nents, DMA_FROM_DEVICE);
+ if (!rc)
+ return -EIO;
+
+ bpl = (dev->width * dev->fmt->depth) >> 3;
+ switch (dev->field) {
+ case V4L2_FIELD_TOP:
+ tw68_risc_buffer(dev->pci, buf, dma->sgl,
+ 0, UNSET, bpl, 0, dev->height);
+ break;
+ case V4L2_FIELD_BOTTOM:
+ tw68_risc_buffer(dev->pci, buf, dma->sgl,
+ UNSET, 0, bpl, 0, dev->height);
+ break;
+ case V4L2_FIELD_SEQ_TB:
+ tw68_risc_buffer(dev->pci, buf, dma->sgl,
+ 0, bpl * (dev->height >> 1),
+ bpl, 0, dev->height >> 1);
+ break;
+ case V4L2_FIELD_SEQ_BT:
+ tw68_risc_buffer(dev->pci, buf, dma->sgl,
+ bpl * (dev->height >> 1), 0,
+ bpl, 0, dev->height >> 1);
+ break;
+ case V4L2_FIELD_INTERLACED:
+ default:
+ tw68_risc_buffer(dev->pci, buf, dma->sgl,
+ 0, bpl, bpl, bpl, dev->height >> 1);
+ break;
+ }
+ return 0;
+}
+
+static void tw68_buf_finish(struct vb2_buffer *vb)
+{
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct tw68_dev *dev = vb2_get_drv_priv(vq);
+ struct sg_table *dma = vb2_dma_sg_plane_desc(vb, 0);
+ struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb);
+
+ dma_unmap_sg(&dev->pci->dev, dma->sgl, dma->nents, DMA_FROM_DEVICE);
+
+ pci_free_consistent(dev->pci, buf->size, buf->cpu, buf->dma);
+}
+
+static int tw68_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct tw68_dev *dev = vb2_get_drv_priv(q);
+ struct tw68_buf *buf =
+ container_of(dev->active.next, struct tw68_buf, list);
+
+ dev->seqnr = 0;
+ tw68_video_start_dma(dev, buf);
+ return 0;
+}
+
+static void tw68_stop_streaming(struct vb2_queue *q)
+{
+ struct tw68_dev *dev = vb2_get_drv_priv(q);
+
+ /* Stop risc & fifo */
+ tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN);
+ while (!list_empty(&dev->active)) {
+ struct tw68_buf *buf =
+ container_of(dev->active.next, struct tw68_buf, list);
+
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
+ }
+}
+
+static struct vb2_ops tw68_video_qops = {
+ .queue_setup = tw68_queue_setup,
+ .buf_queue = tw68_buf_queue,
+ .buf_prepare = tw68_buf_prepare,
+ .buf_finish = tw68_buf_finish,
+ .start_streaming = tw68_start_streaming,
+ .stop_streaming = tw68_stop_streaming,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+/* ------------------------------------------------------------------ */
+
+static int tw68_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct tw68_dev *dev =
+ container_of(ctrl->handler, struct tw68_dev, hdl);
+
+ switch (ctrl->id) {
+ case V4L2_CID_BRIGHTNESS:
+ tw_writeb(TW68_BRIGHT, ctrl->val);
+ break;
+ case V4L2_CID_HUE:
+ tw_writeb(TW68_HUE, ctrl->val);
+ break;
+ case V4L2_CID_CONTRAST:
+ tw_writeb(TW68_CONTRAST, ctrl->val);
+ break;
+ case V4L2_CID_SATURATION:
+ tw_writeb(TW68_SAT_U, ctrl->val);
+ tw_writeb(TW68_SAT_V, ctrl->val);
+ break;
+ case V4L2_CID_COLOR_KILLER:
+ if (ctrl->val)
+ tw_andorb(TW68_MISC2, 0xe0, 0xe0);
+ else
+ tw_andorb(TW68_MISC2, 0xe0, 0x00);
+ break;
+ case V4L2_CID_CHROMA_AGC:
+ if (ctrl->val)
+ tw_andorb(TW68_LOOP, 0x30, 0x20);
+ else
+ tw_andorb(TW68_LOOP, 0x30, 0x00);
+ break;
+ }
+ return 0;
+}
+
+/* ------------------------------------------------------------------ */
+
+/*
+ * Note that this routine returns what is stored in the fh structure, and
+ * does not interrogate any of the device registers.
+ */
+static int tw68_g_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct tw68_dev *dev = video_drvdata(file);
+
+ f->fmt.pix.width = dev->width;
+ f->fmt.pix.height = dev->height;
+ f->fmt.pix.field = dev->field;
+ f->fmt.pix.pixelformat = dev->fmt->fourcc;
+ f->fmt.pix.bytesperline =
+ (f->fmt.pix.width * (dev->fmt->depth)) >> 3;
+ f->fmt.pix.sizeimage =
+ f->fmt.pix.height * f->fmt.pix.bytesperline;
+ f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+ f->fmt.pix.priv = 0;
+ return 0;
+}
+
+static int tw68_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct tw68_dev *dev = video_drvdata(file);
+ const struct tw68_format *fmt;
+ enum v4l2_field field;
+ unsigned int maxh;
+
+ fmt = format_by_fourcc(f->fmt.pix.pixelformat);
+ if (NULL == fmt)
+ return -EINVAL;
+
+ field = f->fmt.pix.field;
+ maxh = (dev->tvnorm->id & V4L2_STD_525_60) ? 480 : 576;
+
+ switch (field) {
+ case V4L2_FIELD_TOP:
+ case V4L2_FIELD_BOTTOM:
+ break;
+ case V4L2_FIELD_INTERLACED:
+ case V4L2_FIELD_SEQ_BT:
+ case V4L2_FIELD_SEQ_TB:
+ maxh = maxh * 2;
+ break;
+ default:
+ field = (f->fmt.pix.height > maxh / 2)
+ ? V4L2_FIELD_INTERLACED
+ : V4L2_FIELD_BOTTOM;
+ break;
+ }
+
+ f->fmt.pix.field = field;
+ if (f->fmt.pix.width < 48)
+ f->fmt.pix.width = 48;
+ if (f->fmt.pix.height < 32)
+ f->fmt.pix.height = 32;
+ if (f->fmt.pix.width > 720)
+ f->fmt.pix.width = 720;
+ if (f->fmt.pix.height > maxh)
+ f->fmt.pix.height = maxh;
+ f->fmt.pix.width &= ~0x03;
+ f->fmt.pix.bytesperline =
+ (f->fmt.pix.width * (fmt->depth)) >> 3;
+ f->fmt.pix.sizeimage =
+ f->fmt.pix.height * f->fmt.pix.bytesperline;
+ f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+ return 0;
+}
+
+/*
+ * Note that tw68_s_fmt_vid_cap sets the information into the fh structure,
+ * and it will be used for all future new buffers. However, there could be
+ * some number of buffers on the "active" chain which will be filled before
+ * the change takes place.
+ */
+static int tw68_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct tw68_dev *dev = video_drvdata(file);
+ int err;
+
+ err = tw68_try_fmt_vid_cap(file, priv, f);
+ if (0 != err)
+ return err;
+
+ dev->fmt = format_by_fourcc(f->fmt.pix.pixelformat);
+ dev->width = f->fmt.pix.width;
+ dev->height = f->fmt.pix.height;
+ dev->field = f->fmt.pix.field;
+ return 0;
+}
+
+static int tw68_enum_input(struct file *file, void *priv,
+ struct v4l2_input *i)
+{
+ struct tw68_dev *dev = video_drvdata(file);
+ unsigned int n;
+
+ n = i->index;
+ if (n >= TW68_INPUT_MAX)
+ return -EINVAL;
+ i->index = n;
+ i->type = V4L2_INPUT_TYPE_CAMERA;
+ snprintf(i->name, sizeof(i->name), "Composite %d", n);
+
+ /* If the query is for the current input, get live data */
+ if (n == dev->input) {
+ int v1 = tw_readb(TW68_STATUS1);
+ int v2 = tw_readb(TW68_MVSN);
+
+ if (0 != (v1 & (1 << 7)))
+ i->status |= V4L2_IN_ST_NO_SYNC;
+ if (0 != (v1 & (1 << 6)))
+ i->status |= V4L2_IN_ST_NO_H_LOCK;
+ if (0 != (v1 & (1 << 2)))
+ i->status |= V4L2_IN_ST_NO_SIGNAL;
+ if (0 != (v1 & 1 << 1))
+ i->status |= V4L2_IN_ST_NO_COLOR;
+ if (0 != (v2 & (1 << 2)))
+ i->status |= V4L2_IN_ST_MACROVISION;
+ }
+ i->std = video_devdata(file)->tvnorms;
+ return 0;
+}
+
+static int tw68_g_input(struct file *file, void *priv, unsigned int *i)
+{
+ struct tw68_dev *dev = video_drvdata(file);
+
+ *i = dev->input;
+ return 0;
+}
+
+static int tw68_s_input(struct file *file, void *priv, unsigned int i)
+{
+ struct tw68_dev *dev = video_drvdata(file);
+
+ if (i >= TW68_INPUT_MAX)
+ return -EINVAL;
+ dev->input = i;
+ tw_andorb(TW68_INFORM, 0x03 << 2, dev->input << 2);
+ return 0;
+}
+
+static int tw68_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct tw68_dev *dev = video_drvdata(file);
+
+ strcpy(cap->driver, "tw68");
+ strlcpy(cap->card, "Techwell Capture Card",
+ sizeof(cap->card));
+ sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci));
+ cap->device_caps =
+ V4L2_CAP_VIDEO_CAPTURE |
+ V4L2_CAP_READWRITE |
+ V4L2_CAP_STREAMING;
+
+ cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+ return 0;
+}
+
+static int tw68_s_std(struct file *file, void *priv, v4l2_std_id id)
+{
+ struct tw68_dev *dev = video_drvdata(file);
+ unsigned int i;
+
+ if (vb2_is_busy(&dev->vidq))
+ return -EBUSY;
+
+ /* Look for match on complete norm id (may have mult bits) */
+ for (i = 0; i < TVNORMS; i++) {
+ if (id == tvnorms[i].id)
+ break;
+ }
+
+ /* If no exact match, look for norm which contains this one */
+ if (i == TVNORMS) {
+ for (i = 0; i < TVNORMS; i++)
+ if (id & tvnorms[i].id)
+ break;
+ }
+ /* If still not matched, give up */
+ if (i == TVNORMS)
+ return -EINVAL;
+
+ set_tvnorm(dev, &tvnorms[i]); /* do the actual setting */
+ return 0;
+}
+
+static int tw68_g_std(struct file *file, void *priv, v4l2_std_id *id)
+{
+ struct tw68_dev *dev = video_drvdata(file);
+
+ *id = dev->tvnorm->id;
+ return 0;
+}
+
+static int tw68_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ if (f->index >= FORMATS)
+ return -EINVAL;
+
+ strlcpy(f->description, formats[f->index].name,
+ sizeof(f->description));
+
+ f->pixelformat = formats[f->index].fourcc;
+
+ return 0;
+}
+
+/*
+ * Used strictly for internal development and debugging, this routine
+ * prints out the current register contents for the tw68xx device.
+ */
+static void tw68_dump_regs(struct tw68_dev *dev)
+{
+ unsigned char line[80];
+ int i, j, k;
+ unsigned char *cptr;
+
+ pr_info("Full dump of TW68 registers:\n");
+ /* First we do the PCI regs, 8 4-byte regs per line */
+ for (i = 0; i < 0x100; i += 32) {
+ cptr = line;
+ cptr += sprintf(cptr, "%03x ", i);
+ /* j steps through the next 4 words */
+ for (j = i; j < i + 16; j += 4)
+ cptr += sprintf(cptr, "%08x ", tw_readl(j));
+ *cptr++ = ' ';
+ for (; j < i + 32; j += 4)
+ cptr += sprintf(cptr, "%08x ", tw_readl(j));
+ *cptr++ = '\n';
+ *cptr = 0;
+ pr_info("%s", line);
+ }
+ /* Next the control regs, which are single-byte, address mod 4 */
+ while (i < 0x400) {
+ cptr = line;
+ cptr += sprintf(cptr, "%03x ", i);
+ /* Print out 4 groups of 4 bytes */
+ for (j = 0; j < 4; j++) {
+ for (k = 0; k < 4; k++) {
+ cptr += sprintf(cptr, "%02x ",
+ tw_readb(i));
+ i += 4;
+ }
+ *cptr++ = ' ';
+ }
+ *cptr++ = '\n';
+ *cptr = 0;
+ pr_info("%s", line);
+ }
+}
+
+static int vidioc_log_status(struct file *file, void *priv)
+{
+ struct tw68_dev *dev = video_drvdata(file);
+
+ tw68_dump_regs(dev);
+ return v4l2_ctrl_log_status(file, priv);
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int vidioc_g_register(struct file *file, void *priv,
+ struct v4l2_dbg_register *reg)
+{
+ struct tw68_dev *dev = video_drvdata(file);
+
+ if (reg->size == 1)
+ reg->val = tw_readb(reg->reg);
+ else
+ reg->val = tw_readl(reg->reg);
+ return 0;
+}
+
+static int vidioc_s_register(struct file *file, void *priv,
+ const struct v4l2_dbg_register *reg)
+{
+ struct tw68_dev *dev = video_drvdata(file);
+
+ if (reg->size == 1)
+ tw_writeb(reg->reg, reg->val);
+ else
+ tw_writel(reg->reg & 0xffff, reg->val);
+ return 0;
+}
+#endif
+
+static const struct v4l2_ctrl_ops tw68_ctrl_ops = {
+ .s_ctrl = tw68_s_ctrl,
+};
+
+static const struct v4l2_file_operations video_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .read = vb2_fop_read,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+ .unlocked_ioctl = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops video_ioctl_ops = {
+ .vidioc_querycap = tw68_querycap,
+ .vidioc_enum_fmt_vid_cap = tw68_enum_fmt_vid_cap,
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_s_std = tw68_s_std,
+ .vidioc_g_std = tw68_g_std,
+ .vidioc_enum_input = tw68_enum_input,
+ .vidioc_g_input = tw68_g_input,
+ .vidioc_s_input = tw68_s_input,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+ .vidioc_g_fmt_vid_cap = tw68_g_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = tw68_try_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = tw68_s_fmt_vid_cap,
+ .vidioc_log_status = vidioc_log_status,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ .vidioc_g_register = vidioc_g_register,
+ .vidioc_s_register = vidioc_s_register,
+#endif
+};
+
+static struct video_device tw68_video_template = {
+ .name = "tw68_video",
+ .fops = &video_fops,
+ .ioctl_ops = &video_ioctl_ops,
+ .release = video_device_release_empty,
+ .tvnorms = TW68_NORMS,
+};
+
+/* ------------------------------------------------------------------ */
+/* exported stuff */
+void tw68_set_tvnorm_hw(struct tw68_dev *dev)
+{
+ tw_andorb(TW68_SDT, 0x07, dev->tvnorm->format);
+}
+
+int tw68_video_init1(struct tw68_dev *dev)
+{
+ struct v4l2_ctrl_handler *hdl = &dev->hdl;
+
+ v4l2_ctrl_handler_init(hdl, 6);
+ v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
+ V4L2_CID_BRIGHTNESS, -128, 127, 1, 20);
+ v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
+ V4L2_CID_CONTRAST, 0, 255, 1, 100);
+ v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
+ V4L2_CID_SATURATION, 0, 255, 1, 128);
+ /* NTSC only */
+ v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
+ V4L2_CID_HUE, -128, 127, 1, 0);
+ v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
+ V4L2_CID_COLOR_KILLER, 0, 1, 1, 0);
+ v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
+ V4L2_CID_CHROMA_AGC, 0, 1, 1, 1);
+ if (hdl->error) {
+ v4l2_ctrl_handler_free(hdl);
+ return hdl->error;
+ }
+ dev->v4l2_dev.ctrl_handler = hdl;
+ v4l2_ctrl_handler_setup(hdl);
+ return 0;
+}
+
+int tw68_video_init2(struct tw68_dev *dev, int video_nr)
+{
+ int ret;
+
+ set_tvnorm(dev, &tvnorms[0]);
+
+ dev->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24);
+ dev->width = 720;
+ dev->height = 576;
+ dev->field = V4L2_FIELD_INTERLACED;
+
+ INIT_LIST_HEAD(&dev->active);
+ dev->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ dev->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ dev->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ | VB2_DMABUF;
+ dev->vidq.ops = &tw68_video_qops;
+ dev->vidq.mem_ops = &vb2_dma_sg_memops;
+ dev->vidq.drv_priv = dev;
+ dev->vidq.gfp_flags = __GFP_DMA32;
+ dev->vidq.buf_struct_size = sizeof(struct tw68_buf);
+ dev->vidq.lock = &dev->lock;
+ dev->vidq.min_buffers_needed = 2;
+ ret = vb2_queue_init(&dev->vidq);
+ if (ret)
+ return ret;
+ dev->vdev = tw68_video_template;
+ dev->vdev.v4l2_dev = &dev->v4l2_dev;
+ dev->vdev.lock = &dev->lock;
+ dev->vdev.queue = &dev->vidq;
+ video_set_drvdata(&dev->vdev, dev);
+ return video_register_device(&dev->vdev, VFL_TYPE_GRABBER, video_nr);
+}
+
+/*
+ * tw68_irq_video_done
+ */
+void tw68_irq_video_done(struct tw68_dev *dev, unsigned long status)
+{
+ __u32 reg;
+
+ /* reset interrupts handled by this routine */
+ tw_writel(TW68_INTSTAT, status);
+ /*
+ * Check most likely first
+ *
+ * DMAPI shows we have reached the end of the risc code
+ * for the current buffer.
+ */
+ if (status & TW68_DMAPI) {
+ struct tw68_buf *buf;
+
+ spin_lock(&dev->slock);
+ buf = list_entry(dev->active.next, struct tw68_buf, list);
+ list_del(&buf->list);
+ spin_unlock(&dev->slock);
+ v4l2_get_timestamp(&buf->vb.v4l2_buf.timestamp);
+ buf->vb.v4l2_buf.field = dev->field;
+ buf->vb.v4l2_buf.sequence = dev->seqnr++;
+ vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);
+ status &= ~(TW68_DMAPI);
+ if (0 == status)
+ return;
+ }
+ if (status & (TW68_VLOCK | TW68_HLOCK))
+ dev_dbg(&dev->pci->dev, "Lost sync\n");
+ if (status & TW68_PABORT)
+ dev_err(&dev->pci->dev, "PABORT interrupt\n");
+ if (status & TW68_DMAPERR)
+ dev_err(&dev->pci->dev, "DMAPERR interrupt\n");
+ /*
+ * On TW6800, FDMIS is apparently generated if video input is switched
+ * during operation. Therefore, it is not enabled for that chip.
+ */
+ if (status & TW68_FDMIS)
+ dev_dbg(&dev->pci->dev, "FDMIS interrupt\n");
+ if (status & TW68_FFOF) {
+ /* probably a logic error */
+ reg = tw_readl(TW68_DMAC) & TW68_FIFO_EN;
+ tw_clearl(TW68_DMAC, TW68_FIFO_EN);
+ dev_dbg(&dev->pci->dev, "FFOF interrupt\n");
+ tw_setl(TW68_DMAC, reg);
+ }
+ if (status & TW68_FFERR)
+ dev_dbg(&dev->pci->dev, "FFERR interrupt\n");
+}
diff --git a/drivers/media/pci/tw68/tw68.h b/drivers/media/pci/tw68/tw68.h
new file mode 100644
index 0000000..2c8abe2
--- /dev/null
+++ b/drivers/media/pci/tw68/tw68.h
@@ -0,0 +1,231 @@
+/*
+ * tw68 driver common header file
+ *
+ * Much of this code is derived from the cx88 and sa7134 drivers, which
+ * were in turn derived from the bt87x driver. The original work was by
+ * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab,
+ * Hans Verkuil, Andy Walls and many others. Their work is gratefully
+ * acknowledged. Full credit goes to them - any problems within this code
+ * are mine.
+ *
+ * Copyright (C) 2009 William M. Brack
+ *
+ * Refactored and updated to the latest v4l core frameworks:
+ *
+ * Copyright (C) 2014 Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/version.h>
+#include <linux/pci.h>
+#include <linux/videodev2.h>
+#include <linux/notifier.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/io.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/videobuf2-dma-sg.h>
+
+#include "tw68-reg.h"
+
+#define UNSET (-1U)
+
+/* system vendor and device ID's */
+#define PCI_VENDOR_ID_TECHWELL 0x1797
+#define PCI_DEVICE_ID_6800 0x6800
+#define PCI_DEVICE_ID_6801 0x6801
+#define PCI_DEVICE_ID_AUDIO2 0x6802
+#define PCI_DEVICE_ID_TS3 0x6803
+#define PCI_DEVICE_ID_6804 0x6804
+#define PCI_DEVICE_ID_AUDIO5 0x6805
+#define PCI_DEVICE_ID_TS6 0x6806
+
+/* tw6816 based cards */
+#define PCI_DEVICE_ID_6816_1 0x6810
+#define PCI_DEVICE_ID_6816_2 0x6811
+#define PCI_DEVICE_ID_6816_3 0x6812
+#define PCI_DEVICE_ID_6816_4 0x6813
+
+#define TW68_NORMS ( \
+ V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM | \
+ V4L2_STD_PAL_M | V4L2_STD_PAL_Nc | V4L2_STD_PAL_60)
+
+#define TW68_VID_INTS (TW68_FFERR | TW68_PABORT | TW68_DMAPERR | \
+ TW68_FFOF | TW68_DMAPI)
+/* TW6800 chips have trouble with these, so we don't set them for that chip */
+#define TW68_VID_INTSX (TW68_FDMIS | TW68_HLOCK | TW68_VLOCK)
+
+#define TW68_I2C_INTS (TW68_SBERR | TW68_SBDONE | TW68_SBERR2 | \
+ TW68_SBDONE2)
+
+enum tw68_decoder_type {
+ TW6800,
+ TW6801,
+ TW6804,
+ TWXXXX,
+};
+
+/* ----------------------------------------------------------- */
+/* static data */
+
+struct tw68_tvnorm {
+ char *name;
+ v4l2_std_id id;
+
+ /* video decoder */
+ u32 sync_control;
+ u32 luma_control;
+ u32 chroma_ctrl1;
+ u32 chroma_gain;
+ u32 chroma_ctrl2;
+ u32 vgate_misc;
+
+ /* video scaler */
+ u32 h_delay;
+ u32 h_delay0; /* for TW6800 */
+ u32 h_start;
+ u32 h_stop;
+ u32 v_delay;
+ u32 video_v_start;
+ u32 video_v_stop;
+ u32 vbi_v_start_0;
+ u32 vbi_v_stop_0;
+ u32 vbi_v_start_1;
+
+ /* Techwell specific */
+ u32 format;
+};
+
+struct tw68_format {
+ char *name;
+ u32 fourcc;
+ u32 depth;
+ u32 twformat;
+};
+
+/* ----------------------------------------------------------- */
+/* card configuration */
+
+#define TW68_BOARD_NOAUTO UNSET
+#define TW68_BOARD_UNKNOWN 0
+#define TW68_BOARD_GENERIC_6802 1
+
+#define TW68_MAXBOARDS 16
+#define TW68_INPUT_MAX 4
+
+/* ----------------------------------------------------------- */
+/* device / file handle status */
+
+#define BUFFER_TIMEOUT msecs_to_jiffies(500) /* 0.5 seconds */
+
+struct tw68_dev; /* forward delclaration */
+
+/* buffer for one video/vbi/ts frame */
+struct tw68_buf {
+ struct vb2_buffer vb;
+ struct list_head list;
+
+ unsigned int size;
+ __le32 *cpu;
+ __le32 *jmp;
+ dma_addr_t dma;
+};
+
+struct tw68_fmt {
+ char *name;
+ u32 fourcc; /* v4l2 format id */
+ int depth;
+ int flags;
+ u32 twformat;
+};
+
+/* global device status */
+struct tw68_dev {
+ struct mutex lock;
+ spinlock_t slock;
+ u16 instance;
+ struct v4l2_device v4l2_dev;
+
+ /* various device info */
+ enum tw68_decoder_type vdecoder;
+ struct video_device vdev;
+ struct v4l2_ctrl_handler hdl;
+
+ /* pci i/o */
+ char *name;
+ struct pci_dev *pci;
+ unsigned char pci_rev, pci_lat;
+ u32 __iomem *lmmio;
+ u8 __iomem *bmmio;
+ u32 pci_irqmask;
+ /* The irq mask to be used will depend upon the chip type */
+ u32 board_virqmask;
+
+ /* video capture */
+ const struct tw68_format *fmt;
+ unsigned width, height;
+ unsigned seqnr;
+ unsigned field;
+ struct vb2_queue vidq;
+ struct list_head active;
+
+ /* various v4l controls */
+ const struct tw68_tvnorm *tvnorm; /* video */
+
+ int input;
+};
+
+/* ----------------------------------------------------------- */
+
+#define tw_readl(reg) readl(dev->lmmio + ((reg) >> 2))
+#define tw_readb(reg) readb(dev->bmmio + (reg))
+#define tw_writel(reg, value) writel((value), dev->lmmio + ((reg) >> 2))
+#define tw_writeb(reg, value) writeb((value), dev->bmmio + (reg))
+
+#define tw_andorl(reg, mask, value) \
+ writel((readl(dev->lmmio+((reg)>>2)) & ~(mask)) |\
+ ((value) & (mask)), dev->lmmio+((reg)>>2))
+#define tw_andorb(reg, mask, value) \
+ writeb((readb(dev->bmmio + (reg)) & ~(mask)) |\
+ ((value) & (mask)), dev->bmmio+(reg))
+#define tw_setl(reg, bit) tw_andorl((reg), (bit), (bit))
+#define tw_setb(reg, bit) tw_andorb((reg), (bit), (bit))
+#define tw_clearl(reg, bit) \
+ writel((readl(dev->lmmio + ((reg) >> 2)) & ~(bit)), \
+ dev->lmmio + ((reg) >> 2))
+#define tw_clearb(reg, bit) \
+ writeb((readb(dev->bmmio+(reg)) & ~(bit)), \
+ dev->bmmio + (reg))
+
+#define tw_wait(us) { udelay(us); }
+
+/* ----------------------------------------------------------- */
+/* tw68-video.c */
+
+void tw68_set_tvnorm_hw(struct tw68_dev *dev);
+
+int tw68_video_init1(struct tw68_dev *dev);
+int tw68_video_init2(struct tw68_dev *dev, int video_nr);
+void tw68_irq_video_done(struct tw68_dev *dev, unsigned long status);
+int tw68_video_start_dma(struct tw68_dev *dev, struct tw68_buf *buf);
+
+/* ----------------------------------------------------------- */
+/* tw68-risc.c */
+
+int tw68_risc_buffer(struct pci_dev *pci, struct tw68_buf *buf,
+ struct scatterlist *sglist, unsigned int top_offset,
+ unsigned int bottom_offset, unsigned int bpl,
+ unsigned int padding, unsigned int lines);
--
2.1.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCHv3 2/2] MAINTAINERS: add tw68 entry
2014-08-26 6:33 [PATCHv3 0/2] Add driver for tw68xx PCI grabber boards Hans Verkuil
2014-08-26 6:33 ` [PATCHv3 1/2] tw68: add support for Techwell " Hans Verkuil
@ 2014-08-26 6:33 ` Hans Verkuil
1 sibling, 0 replies; 7+ messages in thread
From: Hans Verkuil @ 2014-08-26 6:33 UTC (permalink / raw)
To: linux-media; +Cc: Hans Verkuil
From: Hans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
---
MAINTAINERS | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index aefa948..78b38e9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9227,6 +9227,14 @@ T: git git://linuxtv.org/media_tree.git
S: Odd fixes
F: drivers/media/usb/tm6000/
+TW68 VIDEO4LINUX DRIVER
+M: Hans Verkuil <hverkuil@xs4all.nl>
+L: linux-media@vger.kernel.org
+T: git git://linuxtv.org/media_tree.git
+W: http://linuxtv.org
+S: Odd Fixes
+F: drivers/media/pci/tw68/
+
TPM DEVICE DRIVER
M: Peter Huewe <peterhuewe@gmx.de>
M: Ashley Lai <ashley@ashleylai.com>
--
2.1.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCHv3 1/2] tw68: add support for Techwell tw68xx PCI grabber boards
2014-08-26 6:33 ` [PATCHv3 1/2] tw68: add support for Techwell " Hans Verkuil
@ 2014-09-02 19:21 ` Mauro Carvalho Chehab
2014-09-02 19:54 ` Hans Verkuil
0 siblings, 1 reply; 7+ messages in thread
From: Mauro Carvalho Chehab @ 2014-09-02 19:21 UTC (permalink / raw)
To: Hans Verkuil; +Cc: linux-media, Hans Verkuil
Em Tue, 26 Aug 2014 08:33:12 +0200
Hans Verkuil <hverkuil@xs4all.nl> escreveu:
> From: Hans Verkuil <hans.verkuil@cisco.com>
>
> Add support for the tw68 driver. The driver has been out-of-tree for many
> years on gitorious: https://gitorious.org/tw68/tw68-v2
>
> I have refactored and ported that driver to the latest V4L2 core frameworks.
>
> Tested with my Techwell tw6805a and tw6816 grabber boards.
>
> Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
I would be expecting here the William M. Brack's SOB too.
Also, the best is to add his original work on one patch (without Kbuild
stuff) and your changes on a separate patch. That helps us to identify
what are your contributions to his code, and what was his original
copyright wording.
> ---
> drivers/media/pci/Kconfig | 1 +
> drivers/media/pci/Makefile | 1 +
> drivers/media/pci/tw68/Kconfig | 10 +
> drivers/media/pci/tw68/Makefile | 3 +
> drivers/media/pci/tw68/tw68-core.c | 434 ++++++++++++++
> drivers/media/pci/tw68/tw68-reg.h | 195 +++++++
> drivers/media/pci/tw68/tw68-risc.c | 230 ++++++++
> drivers/media/pci/tw68/tw68-video.c | 1060 +++++++++++++++++++++++++++++++++++
> drivers/media/pci/tw68/tw68.h | 231 ++++++++
> 9 files changed, 2165 insertions(+)
> create mode 100644 drivers/media/pci/tw68/Kconfig
> create mode 100644 drivers/media/pci/tw68/Makefile
> create mode 100644 drivers/media/pci/tw68/tw68-core.c
> create mode 100644 drivers/media/pci/tw68/tw68-reg.h
> create mode 100644 drivers/media/pci/tw68/tw68-risc.c
> create mode 100644 drivers/media/pci/tw68/tw68-video.c
> create mode 100644 drivers/media/pci/tw68/tw68.h
>
> diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
> index 5c16c9c..9332807 100644
> --- a/drivers/media/pci/Kconfig
> +++ b/drivers/media/pci/Kconfig
> @@ -20,6 +20,7 @@ source "drivers/media/pci/ivtv/Kconfig"
> source "drivers/media/pci/zoran/Kconfig"
> source "drivers/media/pci/saa7146/Kconfig"
> source "drivers/media/pci/solo6x10/Kconfig"
> +source "drivers/media/pci/tw68/Kconfig"
> endif
>
> if MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT
> diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
> index dc2ebbe..73d9c0f 100644
> --- a/drivers/media/pci/Makefile
> +++ b/drivers/media/pci/Makefile
> @@ -21,6 +21,7 @@ obj-$(CONFIG_VIDEO_CX88) += cx88/
> obj-$(CONFIG_VIDEO_BT848) += bt8xx/
> obj-$(CONFIG_VIDEO_SAA7134) += saa7134/
> obj-$(CONFIG_VIDEO_SAA7164) += saa7164/
> +obj-$(CONFIG_VIDEO_TW68) += tw68/
> obj-$(CONFIG_VIDEO_MEYE) += meye/
> obj-$(CONFIG_STA2X11_VIP) += sta2x11/
> obj-$(CONFIG_VIDEO_SOLO6X10) += solo6x10/
> diff --git a/drivers/media/pci/tw68/Kconfig b/drivers/media/pci/tw68/Kconfig
> new file mode 100644
> index 0000000..5425ba1
> --- /dev/null
> +++ b/drivers/media/pci/tw68/Kconfig
> @@ -0,0 +1,10 @@
> +config VIDEO_TW68
> + tristate "Techwell tw68x Video For Linux"
> + depends on VIDEO_DEV && PCI && VIDEO_V4L2
> + select I2C_ALGOBIT
> + select VIDEOBUF2_DMA_SG
> + ---help---
> + Support for Techwell tw68xx based frame grabber boards.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called tw68.
> diff --git a/drivers/media/pci/tw68/Makefile b/drivers/media/pci/tw68/Makefile
> new file mode 100644
> index 0000000..3d02f28
> --- /dev/null
> +++ b/drivers/media/pci/tw68/Makefile
> @@ -0,0 +1,3 @@
> +tw68-objs := tw68-core.o tw68-video.o tw68-risc.o
> +
> +obj-$(CONFIG_VIDEO_TW68) += tw68.o
> diff --git a/drivers/media/pci/tw68/tw68-core.c b/drivers/media/pci/tw68/tw68-core.c
> new file mode 100644
> index 0000000..baf93af
> --- /dev/null
> +++ b/drivers/media/pci/tw68/tw68-core.c
> @@ -0,0 +1,434 @@
> +/*
> + * tw68-core.c
> + * Core functions for the Techwell 68xx driver
> + *
> + * Much of this code is derived from the cx88 and sa7134 drivers, which
> + * were in turn derived from the bt87x driver. The original work was by
> + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab,
> + * Hans Verkuil, Andy Walls and many others. Their work is gratefully
> + * acknowledged. Full credit goes to them - any problems within this code
> + * are mine.
> + *
> + * Copyright (C) 2009 William M. Brack
> + *
> + * Refactored and updated to the latest v4l core frameworks:
> + *
> + * Copyright (C) 2014 Hans Verkuil <hverkuil@xs4all.nl>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/list.h>
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/kmod.h>
> +#include <linux/sound.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/mutex.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/pm.h>
> +
> +#include <media/v4l2-dev.h>
> +#include "tw68.h"
> +#include "tw68-reg.h"
> +
> +MODULE_DESCRIPTION("v4l2 driver module for tw6800 based video capture cards");
> +MODULE_AUTHOR("William M. Brack");
> +MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>");
> +MODULE_LICENSE("GPL");
> +
> +static unsigned int latency = UNSET;
> +module_param(latency, int, 0444);
> +MODULE_PARM_DESC(latency, "pci latency timer");
> +
> +static unsigned int video_nr[] = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET };
> +module_param_array(video_nr, int, NULL, 0444);
> +MODULE_PARM_DESC(video_nr, "video device number");
> +
> +static unsigned int card[] = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET };
> +module_param_array(card, int, NULL, 0444);
> +MODULE_PARM_DESC(card, "card type");
> +
> +static atomic_t tw68_instance = ATOMIC_INIT(0);
> +
> +/* ------------------------------------------------------------------ */
> +
> +/*
> + * Please add any new PCI IDs to: http://pci-ids.ucw.cz. This keeps
> + * the PCI ID database up to date. Note that the entries must be
> + * added under vendor 0x1797 (Techwell Inc.) as subsystem IDs.
> + */
> +struct pci_device_id tw68_pci_tbl[] = {
> + {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6800)},
> + {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6801)},
> + {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6804)},
> + {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6816_1)},
> + {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6816_2)},
> + {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6816_3)},
> + {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6816_4)},
> + {0,}
> +};
> +
> +/* ------------------------------------------------------------------ */
> +
> +
> +/*
> + * The device is given a "soft reset". According to the specifications,
> + * after this "all register content remain unchanged", so we also write
> + * to all specified registers manually as well (mostly to manufacturer's
> + * specified reset values)
> + */
> +static int tw68_hw_init1(struct tw68_dev *dev)
> +{
> + /* Assure all interrupts are disabled */
> + tw_writel(TW68_INTMASK, 0); /* 020 */
> + /* Clear any pending interrupts */
> + tw_writel(TW68_INTSTAT, 0xffffffff); /* 01C */
> + /* Stop risc processor, set default buffer level */
> + tw_writel(TW68_DMAC, 0x1600);
> +
> + tw_writeb(TW68_ACNTL, 0x80); /* 218 soft reset */
> + msleep(100);
> +
> + tw_writeb(TW68_INFORM, 0x40); /* 208 mux0, 27mhz xtal */
> + tw_writeb(TW68_OPFORM, 0x04); /* 20C analog line-lock */
> + tw_writeb(TW68_HSYNC, 0); /* 210 color-killer high sens */
> + tw_writeb(TW68_ACNTL, 0x42); /* 218 int vref #2, chroma adc off */
> +
> + tw_writeb(TW68_CROP_HI, 0x02); /* 21C Hactive m.s. bits */
> + tw_writeb(TW68_VDELAY_LO, 0x12);/* 220 Mfg specified reset value */
> + tw_writeb(TW68_VACTIVE_LO, 0xf0);
> + tw_writeb(TW68_HDELAY_LO, 0x0f);
> + tw_writeb(TW68_HACTIVE_LO, 0xd0);
> +
> + tw_writeb(TW68_CNTRL1, 0xcd); /* 230 Wide Chroma BPF B/W
> + * Secam reduction, Adap comb for
> + * NTSC, Op Mode 1 */
> +
> + tw_writeb(TW68_VSCALE_LO, 0); /* 234 */
> + tw_writeb(TW68_SCALE_HI, 0x11); /* 238 */
> + tw_writeb(TW68_HSCALE_LO, 0); /* 23c */
> + tw_writeb(TW68_BRIGHT, 0); /* 240 */
> + tw_writeb(TW68_CONTRAST, 0x5c); /* 244 */
> + tw_writeb(TW68_SHARPNESS, 0x51);/* 248 */
> + tw_writeb(TW68_SAT_U, 0x80); /* 24C */
> + tw_writeb(TW68_SAT_V, 0x80); /* 250 */
> + tw_writeb(TW68_HUE, 0x00); /* 254 */
> +
> + /* TODO - Check that none of these are set by control defaults */
> + tw_writeb(TW68_SHARP2, 0x53); /* 258 Mfg specified reset val */
> + tw_writeb(TW68_VSHARP, 0x80); /* 25C Sharpness Coring val 8 */
> + tw_writeb(TW68_CORING, 0x44); /* 260 CTI and Vert Peak coring */
> + tw_writeb(TW68_CNTRL2, 0x00); /* 268 No power saving enabled */
> + tw_writeb(TW68_SDT, 0x07); /* 270 Enable shadow reg, auto-det */
> + tw_writeb(TW68_SDTR, 0x7f); /* 274 All stds recog, don't start */
> + tw_writeb(TW68_CLMPG, 0x50); /* 280 Clamp end at 40 sys clocks */
> + tw_writeb(TW68_IAGC, 0x22); /* 284 Mfg specified reset val */
> + tw_writeb(TW68_AGCGAIN, 0xf0); /* 288 AGC gain when loop disabled */
> + tw_writeb(TW68_PEAKWT, 0xd8); /* 28C White peak threshold */
> + tw_writeb(TW68_CLMPL, 0x3c); /* 290 Y channel clamp level */
> +/* tw_writeb(TW68_SYNCT, 0x38);*/ /* 294 Sync amplitude */
> + tw_writeb(TW68_SYNCT, 0x30); /* 294 Sync amplitude */
> + tw_writeb(TW68_MISSCNT, 0x44); /* 298 Horiz sync, VCR detect sens */
> + tw_writeb(TW68_PCLAMP, 0x28); /* 29C Clamp pos from PLL sync */
> + /* Bit DETV of VCNTL1 helps sync multi cams/chip board */
> + tw_writeb(TW68_VCNTL1, 0x04); /* 2A0 */
> + tw_writeb(TW68_VCNTL2, 0); /* 2A4 */
> + tw_writeb(TW68_CKILL, 0x68); /* 2A8 Mfg specified reset val */
> + tw_writeb(TW68_COMB, 0x44); /* 2AC Mfg specified reset val */
> + tw_writeb(TW68_LDLY, 0x30); /* 2B0 Max positive luma delay */
> + tw_writeb(TW68_MISC1, 0x14); /* 2B4 Mfg specified reset val */
> + tw_writeb(TW68_LOOP, 0xa5); /* 2B8 Mfg specified reset val */
> + tw_writeb(TW68_MISC2, 0xe0); /* 2BC Enable colour killer */
> + tw_writeb(TW68_MVSN, 0); /* 2C0 */
> + tw_writeb(TW68_CLMD, 0x05); /* 2CC slice level auto, clamp med. */
> + tw_writeb(TW68_IDCNTL, 0); /* 2D0 Writing zero to this register
> + * selects NTSC ID detection,
> + * but doesn't change the
> + * sensitivity (which has a reset
> + * value of 1E). Since we are
> + * not doing auto-detection, it
> + * has no real effect */
> + tw_writeb(TW68_CLCNTL1, 0); /* 2D4 */
> + tw_writel(TW68_VBIC, 0x03); /* 010 */
> + tw_writel(TW68_CAP_CTL, 0x03); /* 040 Enable both even & odd flds */
> + tw_writel(TW68_DMAC, 0x2000); /* patch set had 0x2080 */
> + tw_writel(TW68_TESTREG, 0); /* 02C */
> +
> + /*
> + * Some common boards, especially inexpensive single-chip models,
> + * use the GPIO bits 0-3 to control an on-board video-output mux.
> + * For these boards, we need to set up the GPIO register into
> + * "normal" mode, set bits 0-3 as output, and then set those bits
> + * zero.
> + *
> + * Eventually, it would be nice if we could identify these boards
> + * uniquely, and only do this initialisation if the board has been
> + * identify. For the moment, however, it shouldn't hurt anything
> + * to do these steps.
> + */
> + tw_writel(TW68_GPIOC, 0); /* Set the GPIO to "normal", no ints */
> + tw_writel(TW68_GPOE, 0x0f); /* Set bits 0-3 to "output" */
> + tw_writel(TW68_GPDATA, 0); /* Set all bits to low state */
> +
> + /* Initialize the device control structures */
> + mutex_init(&dev->lock);
> + spin_lock_init(&dev->slock);
> +
> + /* Initialize any subsystems */
> + tw68_video_init1(dev);
> + return 0;
> +}
> +
> +static irqreturn_t tw68_irq(int irq, void *dev_id)
> +{
> + struct tw68_dev *dev = dev_id;
> + u32 status, orig;
> + int loop;
> +
> + status = orig = tw_readl(TW68_INTSTAT) & dev->pci_irqmask;
> + /* Check if anything to do */
> + if (0 == status)
> + return IRQ_NONE; /* Nope - return */
> + for (loop = 0; loop < 10; loop++) {
> + if (status & dev->board_virqmask) /* video interrupt */
> + tw68_irq_video_done(dev, status);
> + status = tw_readl(TW68_INTSTAT) & dev->pci_irqmask;
> + if (0 == status)
> + return IRQ_HANDLED;
> + }
> + dev_dbg(&dev->pci->dev, "%s: **** INTERRUPT NOT HANDLED - clearing mask (orig 0x%08x, cur 0x%08x)",
> + dev->name, orig, tw_readl(TW68_INTSTAT));
> + dev_dbg(&dev->pci->dev, "%s: pci_irqmask 0x%08x; board_virqmask 0x%08x ****\n",
> + dev->name, dev->pci_irqmask, dev->board_virqmask);
> + tw_clearl(TW68_INTMASK, dev->pci_irqmask);
> + return IRQ_HANDLED;
> +}
> +
> +static int tw68_initdev(struct pci_dev *pci_dev,
> + const struct pci_device_id *pci_id)
> +{
> + struct tw68_dev *dev;
> + int vidnr = -1;
> + int err;
> +
> + dev = devm_kzalloc(&pci_dev->dev, sizeof(*dev), GFP_KERNEL);
> + if (NULL == dev)
> + return -ENOMEM;
> +
> + dev->instance = v4l2_device_set_name(&dev->v4l2_dev, "tw68",
> + &tw68_instance);
> +
> + err = v4l2_device_register(&pci_dev->dev, &dev->v4l2_dev);
> + if (err)
> + return err;
> +
> + /* pci init */
> + dev->pci = pci_dev;
> + if (pci_enable_device(pci_dev)) {
> + err = -EIO;
> + goto fail1;
> + }
> +
> + dev->name = dev->v4l2_dev.name;
> +
> + if (UNSET != latency) {
> + pr_info("%s: setting pci latency timer to %d\n",
> + dev->name, latency);
> + pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, latency);
> + }
> +
> + /* print pci info */
> + pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &dev->pci_rev);
> + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat);
> + pr_info("%s: found at %s, rev: %d, irq: %d, latency: %d, mmio: 0x%llx\n",
> + dev->name, pci_name(pci_dev), dev->pci_rev, pci_dev->irq,
> + dev->pci_lat, (u64)pci_resource_start(pci_dev, 0));
> + pci_set_master(pci_dev);
> + if (!pci_dma_supported(pci_dev, DMA_BIT_MASK(32))) {
> + pr_info("%s: Oops: no 32bit PCI DMA ???\n", dev->name);
> + err = -EIO;
> + goto fail1;
> + }
> +
> + switch (pci_id->device) {
> + case PCI_DEVICE_ID_6800: /* TW6800 */
> + dev->vdecoder = TW6800;
> + dev->board_virqmask = TW68_VID_INTS;
> + break;
> + case PCI_DEVICE_ID_6801: /* Video decoder for TW6802 */
> + dev->vdecoder = TW6801;
> + dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX;
> + break;
> + case PCI_DEVICE_ID_6804: /* Video decoder for TW6804 */
> + dev->vdecoder = TW6804;
> + dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX;
> + break;
> + default:
> + dev->vdecoder = TWXXXX; /* To be announced */
> + dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX;
> + break;
> + }
> +
> + /* get mmio */
> + if (!request_mem_region(pci_resource_start(pci_dev, 0),
> + pci_resource_len(pci_dev, 0),
> + dev->name)) {
> + err = -EBUSY;
> + pr_err("%s: can't get MMIO memory @ 0x%llx\n",
> + dev->name,
> + (unsigned long long)pci_resource_start(pci_dev, 0));
> + goto fail1;
> + }
> + dev->lmmio = ioremap(pci_resource_start(pci_dev, 0),
> + pci_resource_len(pci_dev, 0));
> + dev->bmmio = (__u8 __iomem *)dev->lmmio;
> + if (NULL == dev->lmmio) {
> + err = -EIO;
> + pr_err("%s: can't ioremap() MMIO memory\n",
> + dev->name);
> + goto fail2;
> + }
> + /* initialize hardware #1 */
> + /* Then do any initialisation wanted before interrupts are on */
> + tw68_hw_init1(dev);
> +
> + /* get irq */
> + err = devm_request_irq(&pci_dev->dev, pci_dev->irq, tw68_irq,
> + IRQF_SHARED | IRQF_DISABLED, dev->name, dev);
> + if (err < 0) {
> + pr_err("%s: can't get IRQ %d\n",
> + dev->name, pci_dev->irq);
> + goto fail3;
> + }
> +
> + /*
> + * Now do remainder of initialisation, first for
> + * things unique for this card, then for general board
> + */
> + if (dev->instance < TW68_MAXBOARDS)
> + vidnr = video_nr[dev->instance];
> + /* initialise video function first */
> + err = tw68_video_init2(dev, vidnr);
> + if (err < 0) {
> + pr_err("%s: can't register video device\n",
> + dev->name);
> + goto fail4;
> + }
> + tw_setl(TW68_INTMASK, dev->pci_irqmask);
> +
> + pr_info("%s: registered device %s\n",
> + dev->name, video_device_node_name(&dev->vdev));
> +
> + return 0;
> +
> +fail4:
> + video_unregister_device(&dev->vdev);
> +fail3:
> + iounmap(dev->lmmio);
> +fail2:
> + release_mem_region(pci_resource_start(pci_dev, 0),
> + pci_resource_len(pci_dev, 0));
> +fail1:
> + v4l2_device_unregister(&dev->v4l2_dev);
> + return err;
> +}
> +
> +static void tw68_finidev(struct pci_dev *pci_dev)
> +{
> + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
> + struct tw68_dev *dev =
> + container_of(v4l2_dev, struct tw68_dev, v4l2_dev);
> +
> + /* shutdown subsystems */
> + tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN);
> + tw_writel(TW68_INTMASK, 0);
> +
> + /* unregister */
> + video_unregister_device(&dev->vdev);
> + v4l2_ctrl_handler_free(&dev->hdl);
> +
> + /* release resources */
> + iounmap(dev->lmmio);
> + release_mem_region(pci_resource_start(pci_dev, 0),
> + pci_resource_len(pci_dev, 0));
> +
> + v4l2_device_unregister(&dev->v4l2_dev);
> +}
> +
> +#ifdef CONFIG_PM
> +
> +static int tw68_suspend(struct pci_dev *pci_dev , pm_message_t state)
> +{
> + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
> + struct tw68_dev *dev = container_of(v4l2_dev,
> + struct tw68_dev, v4l2_dev);
> +
> + tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN);
> + dev->pci_irqmask &= ~TW68_VID_INTS;
> + tw_writel(TW68_INTMASK, 0);
> +
> + synchronize_irq(pci_dev->irq);
> +
> + pci_save_state(pci_dev);
> + pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state));
> + vb2_discard_done(&dev->vidq);
> +
> + return 0;
> +}
> +
> +static int tw68_resume(struct pci_dev *pci_dev)
> +{
> + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
> + struct tw68_dev *dev = container_of(v4l2_dev,
> + struct tw68_dev, v4l2_dev);
> + struct tw68_buf *buf;
> + unsigned long flags;
> +
> + pci_set_power_state(pci_dev, PCI_D0);
> + pci_restore_state(pci_dev);
> +
> + /* Do things that are done in tw68_initdev ,
> + except of initializing memory structures.*/
> +
> + msleep(100);
> +
> + tw68_set_tvnorm_hw(dev);
> +
> + /*resume unfinished buffer(s)*/
> + spin_lock_irqsave(&dev->slock, flags);
> + buf = container_of(dev->active.next, struct tw68_buf, list);
> +
> + tw68_video_start_dma(dev, buf);
> +
> + spin_unlock_irqrestore(&dev->slock, flags);
> +
> + return 0;
> +}
> +#endif
> +
> +/* ----------------------------------------------------------- */
> +
> +static struct pci_driver tw68_pci_driver = {
> + .name = "tw68",
> + .id_table = tw68_pci_tbl,
> + .probe = tw68_initdev,
> + .remove = tw68_finidev,
> +#ifdef CONFIG_PM
> + .suspend = tw68_suspend,
> + .resume = tw68_resume
> +#endif
> +};
> +
> +module_pci_driver(tw68_pci_driver);
> diff --git a/drivers/media/pci/tw68/tw68-reg.h b/drivers/media/pci/tw68/tw68-reg.h
> new file mode 100644
> index 0000000..f60b3a8
> --- /dev/null
> +++ b/drivers/media/pci/tw68/tw68-reg.h
> @@ -0,0 +1,195 @@
> +/*
> + * tw68-reg.h - TW68xx register offsets
> + *
> + * Much of this code is derived from the cx88 and sa7134 drivers, which
> + * were in turn derived from the bt87x driver. The original work was by
> + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab,
> + * Hans Verkuil, Andy Walls and many others. Their work is gratefully
> + * acknowledged. Full credit goes to them - any problems within this code
> + * are mine.
> + *
> + * Copyright (C) William M. Brack
> + *
> + * Refactored and updated to the latest v4l core frameworks:
> + *
> + * Copyright (C) 2014 Hans Verkuil <hverkuil@xs4all.nl>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> +*/
> +
> +#ifndef _TW68_REG_H_
> +#define _TW68_REG_H_
> +
> +/* ---------------------------------------------------------------------- */
> +#define TW68_DMAC 0x000
> +#define TW68_DMAP_SA 0x004
> +#define TW68_DMAP_EXE 0x008
> +#define TW68_DMAP_PP 0x00c
> +#define TW68_VBIC 0x010
> +#define TW68_SBUSC 0x014
> +#define TW68_SBUSSD 0x018
> +#define TW68_INTSTAT 0x01C
> +#define TW68_INTMASK 0x020
> +#define TW68_GPIOC 0x024
> +#define TW68_GPOE 0x028
> +#define TW68_TESTREG 0x02C
> +#define TW68_SBUSRD 0x030
> +#define TW68_SBUS_TRIG 0x034
> +#define TW68_CAP_CTL 0x040
> +#define TW68_SUBSYS 0x054
> +#define TW68_I2C_RST 0x064
> +#define TW68_VBIINST 0x06C
> +/* define bits in FIFO and DMAP Control reg */
> +#define TW68_DMAP_EN (1 << 0)
> +#define TW68_FIFO_EN (1 << 1)
> +/* define the Interrupt Status Register bits */
> +#define TW68_SBDONE (1 << 0)
> +#define TW68_DMAPI (1 << 1)
> +#define TW68_GPINT (1 << 2)
> +#define TW68_FFOF (1 << 3)
> +#define TW68_FDMIS (1 << 4)
> +#define TW68_DMAPERR (1 << 5)
> +#define TW68_PABORT (1 << 6)
> +#define TW68_SBDONE2 (1 << 12)
> +#define TW68_SBERR2 (1 << 13)
> +#define TW68_PPERR (1 << 14)
> +#define TW68_FFERR (1 << 15)
> +#define TW68_DET50 (1 << 16)
> +#define TW68_FLOCK (1 << 17)
> +#define TW68_CCVALID (1 << 18)
> +#define TW68_VLOCK (1 << 19)
> +#define TW68_FIELD (1 << 20)
> +#define TW68_SLOCK (1 << 21)
> +#define TW68_HLOCK (1 << 22)
> +#define TW68_VDLOSS (1 << 23)
> +#define TW68_SBERR (1 << 24)
> +/* define the i2c control register bits */
> +#define TW68_SBMODE (0)
> +#define TW68_WREN (1)
> +#define TW68_SSCLK (6)
> +#define TW68_SSDAT (7)
> +#define TW68_SBCLK (8)
> +#define TW68_WDLEN (16)
> +#define TW68_RDLEN (20)
> +#define TW68_SBRW (24)
> +#define TW68_SBDEV (25)
> +
> +#define TW68_SBMODE_B (1 << TW68_SBMODE)
> +#define TW68_WREN_B (1 << TW68_WREN)
> +#define TW68_SSCLK_B (1 << TW68_SSCLK)
> +#define TW68_SSDAT_B (1 << TW68_SSDAT)
> +#define TW68_SBRW_B (1 << TW68_SBRW)
> +
> +#define TW68_GPDATA 0x100
> +#define TW68_STATUS1 0x204
> +#define TW68_INFORM 0x208
> +#define TW68_OPFORM 0x20C
> +#define TW68_HSYNC 0x210
> +#define TW68_ACNTL 0x218
> +#define TW68_CROP_HI 0x21C
> +#define TW68_VDELAY_LO 0x220
> +#define TW68_VACTIVE_LO 0x224
> +#define TW68_HDELAY_LO 0x228
> +#define TW68_HACTIVE_LO 0x22C
> +#define TW68_CNTRL1 0x230
> +#define TW68_VSCALE_LO 0x234
> +#define TW68_SCALE_HI 0x238
> +#define TW68_HSCALE_LO 0x23C
> +#define TW68_BRIGHT 0x240
> +#define TW68_CONTRAST 0x244
> +#define TW68_SHARPNESS 0x248
> +#define TW68_SAT_U 0x24C
> +#define TW68_SAT_V 0x250
> +#define TW68_HUE 0x254
> +#define TW68_SHARP2 0x258
> +#define TW68_VSHARP 0x25C
> +#define TW68_CORING 0x260
> +#define TW68_VBICNTL 0x264
> +#define TW68_CNTRL2 0x268
> +#define TW68_CC_DATA 0x26C
> +#define TW68_SDT 0x270
> +#define TW68_SDTR 0x274
> +#define TW68_RESERV2 0x278
> +#define TW68_RESERV3 0x27C
> +#define TW68_CLMPG 0x280
> +#define TW68_IAGC 0x284
> +#define TW68_AGCGAIN 0x288
> +#define TW68_PEAKWT 0x28C
> +#define TW68_CLMPL 0x290
> +#define TW68_SYNCT 0x294
> +#define TW68_MISSCNT 0x298
> +#define TW68_PCLAMP 0x29C
> +#define TW68_VCNTL1 0x2A0
> +#define TW68_VCNTL2 0x2A4
> +#define TW68_CKILL 0x2A8
> +#define TW68_COMB 0x2AC
> +#define TW68_LDLY 0x2B0
> +#define TW68_MISC1 0x2B4
> +#define TW68_LOOP 0x2B8
> +#define TW68_MISC2 0x2BC
> +#define TW68_MVSN 0x2C0
> +#define TW68_STATUS2 0x2C4
> +#define TW68_HFREF 0x2C8
> +#define TW68_CLMD 0x2CC
> +#define TW68_IDCNTL 0x2D0
> +#define TW68_CLCNTL1 0x2D4
> +
> +/* Audio */
> +#define TW68_ACKI1 0x300
> +#define TW68_ACKI2 0x304
> +#define TW68_ACKI3 0x308
> +#define TW68_ACKN1 0x30C
> +#define TW68_ACKN2 0x310
> +#define TW68_ACKN3 0x314
> +#define TW68_SDIV 0x318
> +#define TW68_LRDIV 0x31C
> +#define TW68_ACCNTL 0x320
> +
> +#define TW68_VSCTL 0x3B8
> +#define TW68_CHROMAGVAL 0x3BC
> +
> +#define TW68_F2CROP_HI 0x3DC
> +#define TW68_F2VDELAY_LO 0x3E0
> +#define TW68_F2VACTIVE_LO 0x3E4
> +#define TW68_F2HDELAY_LO 0x3E8
> +#define TW68_F2HACTIVE_LO 0x3EC
> +#define TW68_F2CNT 0x3F0
> +#define TW68_F2VSCALE_LO 0x3F4
> +#define TW68_F2SCALE_HI 0x3F8
> +#define TW68_F2HSCALE_LO 0x3FC
> +
> +#define RISC_INT_BIT 0x08000000
> +#define RISC_SYNCO 0xC0000000
> +#define RISC_SYNCE 0xD0000000
> +#define RISC_JUMP 0xB0000000
> +#define RISC_LINESTART 0x90000000
> +#define RISC_INLINE 0xA0000000
> +
> +#define VideoFormatNTSC 0
> +#define VideoFormatNTSCJapan 0
> +#define VideoFormatPALBDGHI 1
> +#define VideoFormatSECAM 2
> +#define VideoFormatNTSC443 3
> +#define VideoFormatPALM 4
> +#define VideoFormatPALN 5
> +#define VideoFormatPALNC 5
> +#define VideoFormatPAL60 6
> +#define VideoFormatAuto 7
> +
> +#define ColorFormatRGB32 0x00
> +#define ColorFormatRGB24 0x10
> +#define ColorFormatRGB16 0x20
> +#define ColorFormatRGB15 0x30
> +#define ColorFormatYUY2 0x40
> +#define ColorFormatBSWAP 0x04
> +#define ColorFormatWSWAP 0x08
> +#define ColorFormatGamma 0x80
> +#endif
> diff --git a/drivers/media/pci/tw68/tw68-risc.c b/drivers/media/pci/tw68/tw68-risc.c
> new file mode 100644
> index 0000000..7439db2
> --- /dev/null
> +++ b/drivers/media/pci/tw68/tw68-risc.c
> @@ -0,0 +1,230 @@
> +/*
> + * tw68_risc.c
> + * Part of the device driver for Techwell 68xx based cards
> + *
> + * Much of this code is derived from the cx88 and sa7134 drivers, which
> + * were in turn derived from the bt87x driver. The original work was by
> + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab,
> + * Hans Verkuil, Andy Walls and many others. Their work is gratefully
> + * acknowledged. Full credit goes to them - any problems within this code
> + * are mine.
> + *
> + * Copyright (C) 2009 William M. Brack
> + *
> + * Refactored and updated to the latest v4l core frameworks:
> + *
> + * Copyright (C) 2014 Hans Verkuil <hverkuil@xs4all.nl>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +#include "tw68.h"
> +
> +/**
> + * @rp pointer to current risc program position
> + * @sglist pointer to "scatter-gather list" of buffer pointers
> + * @offset offset to target memory buffer
> + * @sync_line 0 -> no sync, 1 -> odd sync, 2 -> even sync
> + * @bpl number of bytes per scan line
> + * @padding number of bytes of padding to add
> + * @lines number of lines in field
> + * @jump insert a jump at the start
> + */
> +static __le32 *tw68_risc_field(__le32 *rp, struct scatterlist *sglist,
> + unsigned int offset, u32 sync_line,
> + unsigned int bpl, unsigned int padding,
> + unsigned int lines, bool jump)
> +{
> + struct scatterlist *sg;
> + unsigned int line, todo, done;
> +
> + if (jump) {
> + *(rp++) = cpu_to_le32(RISC_JUMP);
> + *(rp++) = 0;
> + }
> +
> + /* sync instruction */
> + if (sync_line == 1)
> + *(rp++) = cpu_to_le32(RISC_SYNCO);
> + else
> + *(rp++) = cpu_to_le32(RISC_SYNCE);
> + *(rp++) = 0;
> +
> + /* scan lines */
> + sg = sglist;
> + for (line = 0; line < lines; line++) {
> + /* calculate next starting position */
> + while (offset && offset >= sg_dma_len(sg)) {
> + offset -= sg_dma_len(sg);
> + sg = sg_next(sg);
> + }
> + if (bpl <= sg_dma_len(sg) - offset) {
> + /* fits into current chunk */
> + *(rp++) = cpu_to_le32(RISC_LINESTART |
> + /* (offset<<12) |*/ bpl);
> + *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset);
> + offset += bpl;
> + } else {
> + /*
> + * scanline needs to be split. Put the start in
> + * whatever memory remains using RISC_LINESTART,
> + * then the remainder into following addresses
> + * given by the scatter-gather list.
> + */
> + todo = bpl; /* one full line to be done */
> + /* first fragment */
> + done = (sg_dma_len(sg) - offset);
> + *(rp++) = cpu_to_le32(RISC_LINESTART |
> + (7 << 24) |
> + done);
> + *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset);
> + todo -= done;
> + sg = sg_next(sg);
> + /* succeeding fragments have no offset */
> + while (todo > sg_dma_len(sg)) {
> + *(rp++) = cpu_to_le32(RISC_INLINE |
> + (done << 12) |
> + sg_dma_len(sg));
> + *(rp++) = cpu_to_le32(sg_dma_address(sg));
> + todo -= sg_dma_len(sg);
> + sg = sg_next(sg);
> + done += sg_dma_len(sg);
> + }
> + if (todo) {
> + /* final chunk - offset 0, count 'todo' */
> + *(rp++) = cpu_to_le32(RISC_INLINE |
> + (done << 12) |
> + todo);
> + *(rp++) = cpu_to_le32(sg_dma_address(sg));
> + }
> + offset = todo;
> + }
> + offset += padding;
> + }
> +
> + return rp;
> +}
> +
> +/**
> + * tw68_risc_buffer
> + *
> + * This routine is called by tw68-video. It allocates
> + * memory for the dma controller "program" and then fills in that
> + * memory with the appropriate "instructions".
> + *
> + * @pci_dev structure with info about the pci
> + * slot which our device is in.
> + * @risc structure with info about the memory
> + * used for our controller program.
> + * @sglist scatter-gather list entry
> + * @top_offset offset within the risc program area for the
> + * first odd frame line
> + * @bottom_offset offset within the risc program area for the
> + * first even frame line
> + * @bpl number of data bytes per scan line
> + * @padding number of extra bytes to add at end of line
> + * @lines number of scan lines
> + */
> +int tw68_risc_buffer(struct pci_dev *pci,
> + struct tw68_buf *buf,
> + struct scatterlist *sglist,
> + unsigned int top_offset,
> + unsigned int bottom_offset,
> + unsigned int bpl,
> + unsigned int padding,
> + unsigned int lines)
> +{
> + u32 instructions, fields;
> + __le32 *rp;
> +
> + fields = 0;
> + if (UNSET != top_offset)
> + fields++;
> + if (UNSET != bottom_offset)
> + fields++;
> + /*
> + * estimate risc mem: worst case is one write per page border +
> + * one write per scan line + syncs + 2 jumps (all 2 dwords).
> + * Padding can cause next bpl to start close to a page border.
> + * First DMA region may be smaller than PAGE_SIZE
> + */
> + instructions = fields * (1 + (((bpl + padding) * lines) /
> + PAGE_SIZE) + lines) + 4;
> + buf->size = instructions * 8;
> + buf->cpu = pci_alloc_consistent(pci, buf->size, &buf->dma);
> + if (buf->cpu == NULL)
> + return -ENOMEM;
> +
> + /* write risc instructions */
> + rp = buf->cpu;
> + if (UNSET != top_offset) /* generates SYNCO */
> + rp = tw68_risc_field(rp, sglist, top_offset, 1,
> + bpl, padding, lines, true);
> + if (UNSET != bottom_offset) /* generates SYNCE */
> + rp = tw68_risc_field(rp, sglist, bottom_offset, 2,
> + bpl, padding, lines, top_offset == UNSET);
> +
> + /* save pointer to jmp instruction address */
> + buf->jmp = rp;
> + buf->cpu[1] = cpu_to_le32(buf->dma + 8);
> + /* assure risc buffer hasn't overflowed */
> + BUG_ON((buf->jmp - buf->cpu + 2) * sizeof(buf->cpu[0]) > buf->size);
> + return 0;
> +}
> +
> +#if 0
> +/* ------------------------------------------------------------------ */
> +/* debug helper code */
> +
> +static void tw68_risc_decode(u32 risc, u32 addr)
> +{
> +#define RISC_OP(reg) (((reg) >> 28) & 7)
> + static struct instr_details {
> + char *name;
> + u8 has_data_type;
> + u8 has_byte_info;
> + u8 has_addr;
> + } instr[8] = {
> + [RISC_OP(RISC_SYNCO)] = {"syncOdd", 0, 0, 0},
> + [RISC_OP(RISC_SYNCE)] = {"syncEven", 0, 0, 0},
> + [RISC_OP(RISC_JUMP)] = {"jump", 0, 0, 1},
> + [RISC_OP(RISC_LINESTART)] = {"lineStart", 1, 1, 1},
> + [RISC_OP(RISC_INLINE)] = {"inline", 1, 1, 1},
> + };
> + u32 p;
> +
> + p = RISC_OP(risc);
> + if (!(risc & 0x80000000) || !instr[p].name) {
> + pr_debug("0x%08x [ INVALID ]\n", risc);
> + return;
> + }
> + pr_debug("0x%08x %-9s IRQ=%d",
> + risc, instr[p].name, (risc >> 27) & 1);
> + if (instr[p].has_data_type)
> + pr_debug(" Type=%d", (risc >> 24) & 7);
> + if (instr[p].has_byte_info)
> + pr_debug(" Start=0x%03x Count=%03u",
> + (risc >> 12) & 0xfff, risc & 0xfff);
> + if (instr[p].has_addr)
> + pr_debug(" StartAddr=0x%08x", addr);
> + pr_debug("\n");
> +}
> +
> +void tw68_risc_program_dump(struct tw68_core *core, struct tw68_buf *buf)
> +{
> + const __le32 *addr;
> +
> + pr_debug("%s: risc_program_dump: risc=%p, buf->cpu=0x%p, buf->jmp=0x%p\n",
> + core->name, buf, buf->cpu, buf->jmp);
> + for (addr = buf->cpu; addr <= buf->jmp; addr += 2)
> + tw68_risc_decode(*addr, *(addr+1));
> +}
> +#endif
> diff --git a/drivers/media/pci/tw68/tw68-video.c b/drivers/media/pci/tw68/tw68-video.c
> new file mode 100644
> index 0000000..66fae23
> --- /dev/null
> +++ b/drivers/media/pci/tw68/tw68-video.c
> @@ -0,0 +1,1060 @@
> +/*
> + * tw68 functions to handle video data
> + *
> + * Much of this code is derived from the cx88 and sa7134 drivers, which
> + * were in turn derived from the bt87x driver. The original work was by
> + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab,
> + * Hans Verkuil, Andy Walls and many others. Their work is gratefully
> + * acknowledged. Full credit goes to them - any problems within this code
> + * are mine.
> + *
> + * Copyright (C) 2009 William M. Brack
> + *
> + * Refactored and updated to the latest v4l core frameworks:
> + *
> + * Copyright (C) 2014 Hans Verkuil <hverkuil@xs4all.nl>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/module.h>
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-event.h>
> +#include <media/videobuf2-dma-sg.h>
> +
> +#include "tw68.h"
> +#include "tw68-reg.h"
> +
> +/* ------------------------------------------------------------------ */
> +/* data structs for video */
> +/*
> + * FIXME -
> + * Note that the saa7134 has formats, e.g. YUV420, which are classified
> + * as "planar". These affect overlay mode, and are flagged with a field
> + * ".planar" in the format. Do we need to implement this in this driver?
> + */
> +static const struct tw68_format formats[] = {
> + {
> + .name = "15 bpp RGB, le",
> + .fourcc = V4L2_PIX_FMT_RGB555,
> + .depth = 16,
> + .twformat = ColorFormatRGB15,
> + }, {
> + .name = "15 bpp RGB, be",
> + .fourcc = V4L2_PIX_FMT_RGB555X,
> + .depth = 16,
> + .twformat = ColorFormatRGB15 | ColorFormatBSWAP,
> + }, {
> + .name = "16 bpp RGB, le",
> + .fourcc = V4L2_PIX_FMT_RGB565,
> + .depth = 16,
> + .twformat = ColorFormatRGB16,
> + }, {
> + .name = "16 bpp RGB, be",
> + .fourcc = V4L2_PIX_FMT_RGB565X,
> + .depth = 16,
> + .twformat = ColorFormatRGB16 | ColorFormatBSWAP,
> + }, {
> + .name = "24 bpp RGB, le",
> + .fourcc = V4L2_PIX_FMT_BGR24,
> + .depth = 24,
> + .twformat = ColorFormatRGB24,
> + }, {
> + .name = "24 bpp RGB, be",
> + .fourcc = V4L2_PIX_FMT_RGB24,
> + .depth = 24,
> + .twformat = ColorFormatRGB24 | ColorFormatBSWAP,
> + }, {
> + .name = "32 bpp RGB, le",
> + .fourcc = V4L2_PIX_FMT_BGR32,
> + .depth = 32,
> + .twformat = ColorFormatRGB32,
> + }, {
> + .name = "32 bpp RGB, be",
> + .fourcc = V4L2_PIX_FMT_RGB32,
> + .depth = 32,
> + .twformat = ColorFormatRGB32 | ColorFormatBSWAP |
> + ColorFormatWSWAP,
> + }, {
> + .name = "4:2:2 packed, YUYV",
> + .fourcc = V4L2_PIX_FMT_YUYV,
> + .depth = 16,
> + .twformat = ColorFormatYUY2,
> + }, {
> + .name = "4:2:2 packed, UYVY",
> + .fourcc = V4L2_PIX_FMT_UYVY,
> + .depth = 16,
> + .twformat = ColorFormatYUY2 | ColorFormatBSWAP,
> + }
> +};
> +#define FORMATS ARRAY_SIZE(formats)
> +
> +#define NORM_625_50 \
> + .h_delay = 3, \
> + .h_delay0 = 133, \
> + .h_start = 0, \
> + .h_stop = 719, \
> + .v_delay = 24, \
> + .vbi_v_start_0 = 7, \
> + .vbi_v_stop_0 = 22, \
> + .video_v_start = 24, \
> + .video_v_stop = 311, \
> + .vbi_v_start_1 = 319
> +
> +#define NORM_525_60 \
> + .h_delay = 8, \
> + .h_delay0 = 138, \
> + .h_start = 0, \
> + .h_stop = 719, \
> + .v_delay = 22, \
> + .vbi_v_start_0 = 10, \
> + .vbi_v_stop_0 = 21, \
> + .video_v_start = 22, \
> + .video_v_stop = 262, \
> + .vbi_v_start_1 = 273
> +
> +/*
> + * The following table is searched by tw68_s_std, first for a specific
> + * match, then for an entry which contains the desired id. The table
> + * entries should therefore be ordered in ascending order of specificity.
> + */
> +static const struct tw68_tvnorm tvnorms[] = {
> + {
> + .name = "PAL", /* autodetect */
> + .id = V4L2_STD_PAL,
> + NORM_625_50,
> +
> + .sync_control = 0x18,
> + .luma_control = 0x40,
> + .chroma_ctrl1 = 0x81,
> + .chroma_gain = 0x2a,
> + .chroma_ctrl2 = 0x06,
> + .vgate_misc = 0x1c,
> + .format = VideoFormatPALBDGHI,
> + }, {
> + .name = "NTSC",
> + .id = V4L2_STD_NTSC,
> + NORM_525_60,
> +
> + .sync_control = 0x59,
> + .luma_control = 0x40,
> + .chroma_ctrl1 = 0x89,
> + .chroma_gain = 0x2a,
> + .chroma_ctrl2 = 0x0e,
> + .vgate_misc = 0x18,
> + .format = VideoFormatNTSC,
> + }, {
> + .name = "SECAM",
> + .id = V4L2_STD_SECAM,
> + NORM_625_50,
> +
> + .sync_control = 0x18,
> + .luma_control = 0x1b,
> + .chroma_ctrl1 = 0xd1,
> + .chroma_gain = 0x80,
> + .chroma_ctrl2 = 0x00,
> + .vgate_misc = 0x1c,
> + .format = VideoFormatSECAM,
> + }, {
> + .name = "PAL-M",
> + .id = V4L2_STD_PAL_M,
> + NORM_525_60,
> +
> + .sync_control = 0x59,
> + .luma_control = 0x40,
> + .chroma_ctrl1 = 0xb9,
> + .chroma_gain = 0x2a,
> + .chroma_ctrl2 = 0x0e,
> + .vgate_misc = 0x18,
> + .format = VideoFormatPALM,
> + }, {
> + .name = "PAL-Nc",
> + .id = V4L2_STD_PAL_Nc,
> + NORM_625_50,
> +
> + .sync_control = 0x18,
> + .luma_control = 0x40,
> + .chroma_ctrl1 = 0xa1,
> + .chroma_gain = 0x2a,
> + .chroma_ctrl2 = 0x06,
> + .vgate_misc = 0x1c,
> + .format = VideoFormatPALNC,
> + }, {
> + .name = "PAL-60",
> + .id = V4L2_STD_PAL_60,
> + .h_delay = 186,
> + .h_start = 0,
> + .h_stop = 719,
> + .v_delay = 26,
> + .video_v_start = 23,
> + .video_v_stop = 262,
> + .vbi_v_start_0 = 10,
> + .vbi_v_stop_0 = 21,
> + .vbi_v_start_1 = 273,
> +
> + .sync_control = 0x18,
> + .luma_control = 0x40,
> + .chroma_ctrl1 = 0x81,
> + .chroma_gain = 0x2a,
> + .chroma_ctrl2 = 0x06,
> + .vgate_misc = 0x1c,
> + .format = VideoFormatPAL60,
> + }
> +};
> +#define TVNORMS ARRAY_SIZE(tvnorms)
> +
> +static const struct tw68_format *format_by_fourcc(unsigned int fourcc)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < FORMATS; i++)
> + if (formats[i].fourcc == fourcc)
> + return formats+i;
> + return NULL;
> +}
> +
> +
> +/* ------------------------------------------------------------------ */
> +/*
> + * Note that the cropping rectangles are described in terms of a single
> + * frame, i.e. line positions are only 1/2 the interlaced equivalent
> + */
> +static void set_tvnorm(struct tw68_dev *dev, const struct tw68_tvnorm *norm)
> +{
> + if (norm != dev->tvnorm) {
> + dev->width = 720;
> + dev->height = (norm->id & V4L2_STD_525_60) ? 480 : 576;
> + dev->tvnorm = norm;
> + tw68_set_tvnorm_hw(dev);
> + }
> +}
> +
> +/*
> + * tw68_set_scale
> + *
> + * Scaling and Cropping for video decoding
> + *
> + * We are working with 3 values for horizontal and vertical - scale,
> + * delay and active.
> + *
> + * HACTIVE represent the actual number of pixels in the "usable" image,
> + * before scaling. HDELAY represents the number of pixels skipped
> + * between the start of the horizontal sync and the start of the image.
> + * HSCALE is calculated using the formula
> + * HSCALE = (HACTIVE / (#pixels desired)) * 256
> + *
> + * The vertical registers are similar, except based upon the total number
> + * of lines in the image, and the first line of the image (i.e. ignoring
> + * vertical sync and VBI).
> + *
> + * Note that the number of bytes reaching the FIFO (and hence needing
> + * to be processed by the DMAP program) is completely dependent upon
> + * these values, especially HSCALE.
> + *
> + * Parameters:
> + * @dev pointer to the device structure, needed for
> + * getting current norm (as well as debug print)
> + * @width actual image width (from user buffer)
> + * @height actual image height
> + * @field indicates Top, Bottom or Interlaced
> + */
> +static int tw68_set_scale(struct tw68_dev *dev, unsigned int width,
> + unsigned int height, enum v4l2_field field)
> +{
> + const struct tw68_tvnorm *norm = dev->tvnorm;
> + /* set individually for debugging clarity */
> + int hactive, hdelay, hscale;
> + int vactive, vdelay, vscale;
> + int comb;
> +
> + if (V4L2_FIELD_HAS_BOTH(field)) /* if field is interlaced */
> + height /= 2; /* we must set for 1-frame */
> +
> + pr_debug("%s: width=%d, height=%d, both=%d\n"
> + " tvnorm h_delay=%d, h_start=%d, h_stop=%d, "
> + "v_delay=%d, v_start=%d, v_stop=%d\n" , __func__,
> + width, height, V4L2_FIELD_HAS_BOTH(field),
> + norm->h_delay, norm->h_start, norm->h_stop,
> + norm->v_delay, norm->video_v_start,
> + norm->video_v_stop);
> +
> + switch (dev->vdecoder) {
> + case TW6800:
> + hdelay = norm->h_delay0;
> + break;
> + default:
> + hdelay = norm->h_delay;
> + break;
> + }
> +
> + hdelay += norm->h_start;
> + hactive = norm->h_stop - norm->h_start + 1;
> +
> + hscale = (hactive * 256) / (width);
> +
> + vdelay = norm->v_delay;
> + vactive = ((norm->id & V4L2_STD_525_60) ? 524 : 624) / 2 - norm->video_v_start;
> + vscale = (vactive * 256) / height;
> +
> + pr_debug("%s: %dx%d [%s%s,%s]\n", __func__,
> + width, height,
> + V4L2_FIELD_HAS_TOP(field) ? "T" : "",
> + V4L2_FIELD_HAS_BOTTOM(field) ? "B" : "",
> + v4l2_norm_to_name(dev->tvnorm->id));
> + pr_debug("%s: hactive=%d, hdelay=%d, hscale=%d; "
> + "vactive=%d, vdelay=%d, vscale=%d\n", __func__,
> + hactive, hdelay, hscale, vactive, vdelay, vscale);
> +
> + comb = ((vdelay & 0x300) >> 2) |
> + ((vactive & 0x300) >> 4) |
> + ((hdelay & 0x300) >> 6) |
> + ((hactive & 0x300) >> 8);
> + pr_debug("%s: setting CROP_HI=%02x, VDELAY_LO=%02x, "
> + "VACTIVE_LO=%02x, HDELAY_LO=%02x, HACTIVE_LO=%02x\n",
> + __func__, comb, vdelay, vactive, hdelay, hactive);
> + tw_writeb(TW68_CROP_HI, comb);
> + tw_writeb(TW68_VDELAY_LO, vdelay & 0xff);
> + tw_writeb(TW68_VACTIVE_LO, vactive & 0xff);
> + tw_writeb(TW68_HDELAY_LO, hdelay & 0xff);
> + tw_writeb(TW68_HACTIVE_LO, hactive & 0xff);
> +
> + comb = ((vscale & 0xf00) >> 4) | ((hscale & 0xf00) >> 8);
> + pr_debug("%s: setting SCALE_HI=%02x, VSCALE_LO=%02x, "
> + "HSCALE_LO=%02x\n", __func__, comb, vscale, hscale);
> + tw_writeb(TW68_SCALE_HI, comb);
> + tw_writeb(TW68_VSCALE_LO, vscale);
> + tw_writeb(TW68_HSCALE_LO, hscale);
> +
> + return 0;
> +}
> +
> +/* ------------------------------------------------------------------ */
> +
> +int tw68_video_start_dma(struct tw68_dev *dev, struct tw68_buf *buf)
> +{
> + /* Set cropping and scaling */
> + tw68_set_scale(dev, dev->width, dev->height, dev->field);
> + /*
> + * Set start address for RISC program. Note that if the DMAP
> + * processor is currently running, it must be stopped before
> + * a new address can be set.
> + */
> + tw_clearl(TW68_DMAC, TW68_DMAP_EN);
> + tw_writel(TW68_DMAP_SA, cpu_to_le32(buf->dma));
> + /* Clear any pending interrupts */
> + tw_writel(TW68_INTSTAT, dev->board_virqmask);
> + /* Enable the risc engine and the fifo */
> + tw_andorl(TW68_DMAC, 0xff, dev->fmt->twformat |
> + ColorFormatGamma | TW68_DMAP_EN | TW68_FIFO_EN);
> + dev->pci_irqmask |= dev->board_virqmask;
> + tw_setl(TW68_INTMASK, dev->pci_irqmask);
> + return 0;
> +}
> +
> +/* ------------------------------------------------------------------ */
> +
> +/* nr of (tw68-)pages for the given buffer size */
> +static int tw68_buffer_pages(int size)
> +{
> + size = PAGE_ALIGN(size);
> + size += PAGE_SIZE; /* for non-page-aligned buffers */
> + size /= 4096;
The above seems to be wrong, as PAGE_SIZE is not always 4096.
IMHO, the correct would be to do size /= PAGE_SIZE, if the intent is
to return the number of pages.
> + return size;
> +}
> +
> +/* calc max # of buffers from size (must not exceed the 4MB virtual
> + * address space per DMA channel) */
> +static int tw68_buffer_count(unsigned int size, unsigned int count)
> +{
> + unsigned int maxcount;
> +
> + maxcount = 1024 / tw68_buffer_pages(size);
Again, the 1024 here looks weird, as it seems to also be assuming a
4096 page size.
> + if (count > maxcount)
> + count = maxcount;
> + return count;
> +}
> +
> +/* ------------------------------------------------------------- */
> +/* vb2 queue operations */
> +
> +static int tw68_queue_setup(struct vb2_queue *q, const struct v4l2_format *fmt,
> + unsigned int *num_buffers, unsigned int *num_planes,
> + unsigned int sizes[], void *alloc_ctxs[])
> +{
> + struct tw68_dev *dev = vb2_get_drv_priv(q);
> + unsigned tot_bufs = q->num_buffers + *num_buffers;
> +
> + sizes[0] = (dev->fmt->depth * dev->width * dev->height) >> 3;
> + /*
> + * We allow create_bufs, but only if the sizeimage is the same as the
> + * current sizeimage. The tw68_buffer_count calculation becomes quite
> + * difficult otherwise.
> + */
> + if (fmt && fmt->fmt.pix.sizeimage < sizes[0])
> + return -EINVAL;
> + *num_planes = 1;
> + if (tot_bufs < 2)
> + tot_bufs = 2;
> + tot_bufs = tw68_buffer_count(sizes[0], tot_bufs);
> + *num_buffers = tot_bufs - q->num_buffers;
> +
> + return 0;
> +}
> +
> +/*
> + * The risc program for each buffers works as follows: it starts with a simple
> + * 'JUMP to addr + 8', which is effectively a NOP. Then the program to DMA the
> + * buffer follows and at the end we have a JUMP back to the start + 8 (skipping
> + * the initial JUMP).
> + *
> + * This is the program of the first buffer to be queued if the active list is
> + * empty and it just keeps DMAing this buffer without generating any interrupts.
> + *
> + * If a new buffer is added then the initial JUMP in the program generates an
> + * interrupt as well which signals that the previous buffer has been DMAed
> + * successfully and that it can be returned to userspace.
> + *
> + * It also sets the final jump of the previous buffer to the start of the new
> + * buffer, thus chaining the new buffer into the DMA chain. This is a single
> + * atomic u32 write, so there is no race condition.
> + *
> + * The end-result of all this that you only get an interrupt when a buffer
> + * is ready, so the control flow is very easy.
> + */
> +static void tw68_buf_queue(struct vb2_buffer *vb)
> +{
> + struct vb2_queue *vq = vb->vb2_queue;
> + struct tw68_dev *dev = vb2_get_drv_priv(vq);
> + struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb);
> + struct tw68_buf *prev;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&dev->slock, flags);
> +
> + /* append a 'JUMP to start of buffer' to the buffer risc program */
> + buf->jmp[0] = cpu_to_le32(RISC_JUMP);
> + buf->jmp[1] = cpu_to_le32(buf->dma + 8);
> +
> + if (!list_empty(&dev->active)) {
> + prev = list_entry(dev->active.prev, struct tw68_buf, list);
> + buf->cpu[0] |= cpu_to_le32(RISC_INT_BIT);
> + prev->jmp[1] = cpu_to_le32(buf->dma);
> + }
> + list_add_tail(&buf->list, &dev->active);
> + spin_unlock_irqrestore(&dev->slock, flags);
> +}
> +
> +/*
> + * buffer_prepare
> + *
> + * Set the ancilliary information into the buffer structure. This
> + * includes generating the necessary risc program if it hasn't already
> + * been done for the current buffer format.
> + * The structure fh contains the details of the format requested by the
> + * user - type, width, height and #fields. This is compared with the
> + * last format set for the current buffer. If they differ, the risc
> + * code (which controls the filling of the buffer) is (re-)generated.
> + */
> +static int tw68_buf_prepare(struct vb2_buffer *vb)
> +{
> + struct vb2_queue *vq = vb->vb2_queue;
> + struct tw68_dev *dev = vb2_get_drv_priv(vq);
> + struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb);
> + struct sg_table *dma = vb2_dma_sg_plane_desc(vb, 0);
> + unsigned size, bpl;
> + int rc;
> +
> + size = (dev->width * dev->height * dev->fmt->depth) >> 3;
> + if (vb2_plane_size(vb, 0) < size)
> + return -EINVAL;
> + vb2_set_plane_payload(vb, 0, size);
> +
> + rc = dma_map_sg(&dev->pci->dev, dma->sgl, dma->nents, DMA_FROM_DEVICE);
> + if (!rc)
> + return -EIO;
> +
> + bpl = (dev->width * dev->fmt->depth) >> 3;
> + switch (dev->field) {
> + case V4L2_FIELD_TOP:
> + tw68_risc_buffer(dev->pci, buf, dma->sgl,
> + 0, UNSET, bpl, 0, dev->height);
> + break;
> + case V4L2_FIELD_BOTTOM:
> + tw68_risc_buffer(dev->pci, buf, dma->sgl,
> + UNSET, 0, bpl, 0, dev->height);
> + break;
> + case V4L2_FIELD_SEQ_TB:
> + tw68_risc_buffer(dev->pci, buf, dma->sgl,
> + 0, bpl * (dev->height >> 1),
> + bpl, 0, dev->height >> 1);
> + break;
> + case V4L2_FIELD_SEQ_BT:
> + tw68_risc_buffer(dev->pci, buf, dma->sgl,
> + bpl * (dev->height >> 1), 0,
> + bpl, 0, dev->height >> 1);
> + break;
> + case V4L2_FIELD_INTERLACED:
> + default:
> + tw68_risc_buffer(dev->pci, buf, dma->sgl,
> + 0, bpl, bpl, bpl, dev->height >> 1);
> + break;
> + }
> + return 0;
> +}
> +
> +static void tw68_buf_finish(struct vb2_buffer *vb)
> +{
> + struct vb2_queue *vq = vb->vb2_queue;
> + struct tw68_dev *dev = vb2_get_drv_priv(vq);
> + struct sg_table *dma = vb2_dma_sg_plane_desc(vb, 0);
> + struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb);
> +
> + dma_unmap_sg(&dev->pci->dev, dma->sgl, dma->nents, DMA_FROM_DEVICE);
> +
> + pci_free_consistent(dev->pci, buf->size, buf->cpu, buf->dma);
> +}
> +
> +static int tw68_start_streaming(struct vb2_queue *q, unsigned int count)
> +{
> + struct tw68_dev *dev = vb2_get_drv_priv(q);
> + struct tw68_buf *buf =
> + container_of(dev->active.next, struct tw68_buf, list);
Please put the above statement into a single line.
> +
> + dev->seqnr = 0;
> + tw68_video_start_dma(dev, buf);
> + return 0;
> +}
> +
> +static void tw68_stop_streaming(struct vb2_queue *q)
> +{
> + struct tw68_dev *dev = vb2_get_drv_priv(q);
> +
> + /* Stop risc & fifo */
> + tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN);
> + while (!list_empty(&dev->active)) {
> + struct tw68_buf *buf =
> + container_of(dev->active.next, struct tw68_buf, list);
Same here. Or if you want to use multiple lines, do it like:
struct tw68_buf *buf;
buf = container_of(dev->active.next, struct tw68_buf, list);
(personally, I prefer this way)
Same on other similar places.
> +
> + list_del(&buf->list);
> + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
> + }
> +}
> +
> +static struct vb2_ops tw68_video_qops = {
> + .queue_setup = tw68_queue_setup,
> + .buf_queue = tw68_buf_queue,
> + .buf_prepare = tw68_buf_prepare,
> + .buf_finish = tw68_buf_finish,
> + .start_streaming = tw68_start_streaming,
> + .stop_streaming = tw68_stop_streaming,
> + .wait_prepare = vb2_ops_wait_prepare,
> + .wait_finish = vb2_ops_wait_finish,
> +};
> +
> +/* ------------------------------------------------------------------ */
> +
> +static int tw68_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> + struct tw68_dev *dev =
> + container_of(ctrl->handler, struct tw68_dev, hdl);
> +
> + switch (ctrl->id) {
> + case V4L2_CID_BRIGHTNESS:
> + tw_writeb(TW68_BRIGHT, ctrl->val);
> + break;
> + case V4L2_CID_HUE:
> + tw_writeb(TW68_HUE, ctrl->val);
> + break;
> + case V4L2_CID_CONTRAST:
> + tw_writeb(TW68_CONTRAST, ctrl->val);
> + break;
> + case V4L2_CID_SATURATION:
> + tw_writeb(TW68_SAT_U, ctrl->val);
> + tw_writeb(TW68_SAT_V, ctrl->val);
> + break;
> + case V4L2_CID_COLOR_KILLER:
> + if (ctrl->val)
> + tw_andorb(TW68_MISC2, 0xe0, 0xe0);
> + else
> + tw_andorb(TW68_MISC2, 0xe0, 0x00);
> + break;
> + case V4L2_CID_CHROMA_AGC:
> + if (ctrl->val)
> + tw_andorb(TW68_LOOP, 0x30, 0x20);
> + else
> + tw_andorb(TW68_LOOP, 0x30, 0x00);
> + break;
> + }
> + return 0;
> +}
> +
> +/* ------------------------------------------------------------------ */
> +
> +/*
> + * Note that this routine returns what is stored in the fh structure, and
> + * does not interrogate any of the device registers.
> + */
> +static int tw68_g_fmt_vid_cap(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct tw68_dev *dev = video_drvdata(file);
> +
> + f->fmt.pix.width = dev->width;
> + f->fmt.pix.height = dev->height;
> + f->fmt.pix.field = dev->field;
> + f->fmt.pix.pixelformat = dev->fmt->fourcc;
> + f->fmt.pix.bytesperline =
> + (f->fmt.pix.width * (dev->fmt->depth)) >> 3;
> + f->fmt.pix.sizeimage =
> + f->fmt.pix.height * f->fmt.pix.bytesperline;
> + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
> + f->fmt.pix.priv = 0;
> + return 0;
> +}
> +
> +static int tw68_try_fmt_vid_cap(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct tw68_dev *dev = video_drvdata(file);
> + const struct tw68_format *fmt;
> + enum v4l2_field field;
> + unsigned int maxh;
> +
> + fmt = format_by_fourcc(f->fmt.pix.pixelformat);
> + if (NULL == fmt)
> + return -EINVAL;
> +
> + field = f->fmt.pix.field;
> + maxh = (dev->tvnorm->id & V4L2_STD_525_60) ? 480 : 576;
> +
> + switch (field) {
> + case V4L2_FIELD_TOP:
> + case V4L2_FIELD_BOTTOM:
> + break;
> + case V4L2_FIELD_INTERLACED:
> + case V4L2_FIELD_SEQ_BT:
> + case V4L2_FIELD_SEQ_TB:
> + maxh = maxh * 2;
> + break;
> + default:
> + field = (f->fmt.pix.height > maxh / 2)
> + ? V4L2_FIELD_INTERLACED
> + : V4L2_FIELD_BOTTOM;
> + break;
> + }
> +
> + f->fmt.pix.field = field;
> + if (f->fmt.pix.width < 48)
> + f->fmt.pix.width = 48;
> + if (f->fmt.pix.height < 32)
> + f->fmt.pix.height = 32;
> + if (f->fmt.pix.width > 720)
> + f->fmt.pix.width = 720;
> + if (f->fmt.pix.height > maxh)
> + f->fmt.pix.height = maxh;
> + f->fmt.pix.width &= ~0x03;
> + f->fmt.pix.bytesperline =
> + (f->fmt.pix.width * (fmt->depth)) >> 3;
> + f->fmt.pix.sizeimage =
> + f->fmt.pix.height * f->fmt.pix.bytesperline;
> + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
> + return 0;
> +}
> +
> +/*
> + * Note that tw68_s_fmt_vid_cap sets the information into the fh structure,
> + * and it will be used for all future new buffers. However, there could be
> + * some number of buffers on the "active" chain which will be filled before
> + * the change takes place.
> + */
> +static int tw68_s_fmt_vid_cap(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct tw68_dev *dev = video_drvdata(file);
> + int err;
> +
> + err = tw68_try_fmt_vid_cap(file, priv, f);
> + if (0 != err)
> + return err;
> +
> + dev->fmt = format_by_fourcc(f->fmt.pix.pixelformat);
> + dev->width = f->fmt.pix.width;
> + dev->height = f->fmt.pix.height;
> + dev->field = f->fmt.pix.field;
> + return 0;
> +}
> +
> +static int tw68_enum_input(struct file *file, void *priv,
> + struct v4l2_input *i)
> +{
> + struct tw68_dev *dev = video_drvdata(file);
> + unsigned int n;
> +
> + n = i->index;
> + if (n >= TW68_INPUT_MAX)
> + return -EINVAL;
> + i->index = n;
> + i->type = V4L2_INPUT_TYPE_CAMERA;
> + snprintf(i->name, sizeof(i->name), "Composite %d", n);
> +
> + /* If the query is for the current input, get live data */
> + if (n == dev->input) {
> + int v1 = tw_readb(TW68_STATUS1);
> + int v2 = tw_readb(TW68_MVSN);
> +
> + if (0 != (v1 & (1 << 7)))
> + i->status |= V4L2_IN_ST_NO_SYNC;
> + if (0 != (v1 & (1 << 6)))
> + i->status |= V4L2_IN_ST_NO_H_LOCK;
> + if (0 != (v1 & (1 << 2)))
> + i->status |= V4L2_IN_ST_NO_SIGNAL;
> + if (0 != (v1 & 1 << 1))
> + i->status |= V4L2_IN_ST_NO_COLOR;
> + if (0 != (v2 & (1 << 2)))
> + i->status |= V4L2_IN_ST_MACROVISION;
> + }
> + i->std = video_devdata(file)->tvnorms;
> + return 0;
> +}
> +
> +static int tw68_g_input(struct file *file, void *priv, unsigned int *i)
> +{
> + struct tw68_dev *dev = video_drvdata(file);
> +
> + *i = dev->input;
> + return 0;
> +}
> +
> +static int tw68_s_input(struct file *file, void *priv, unsigned int i)
> +{
> + struct tw68_dev *dev = video_drvdata(file);
> +
> + if (i >= TW68_INPUT_MAX)
> + return -EINVAL;
> + dev->input = i;
> + tw_andorb(TW68_INFORM, 0x03 << 2, dev->input << 2);
> + return 0;
> +}
> +
> +static int tw68_querycap(struct file *file, void *priv,
> + struct v4l2_capability *cap)
> +{
> + struct tw68_dev *dev = video_drvdata(file);
> +
> + strcpy(cap->driver, "tw68");
> + strlcpy(cap->card, "Techwell Capture Card",
> + sizeof(cap->card));
> + sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci));
> + cap->device_caps =
> + V4L2_CAP_VIDEO_CAPTURE |
> + V4L2_CAP_READWRITE |
> + V4L2_CAP_STREAMING;
> +
> + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
> + return 0;
> +}
> +
> +static int tw68_s_std(struct file *file, void *priv, v4l2_std_id id)
> +{
> + struct tw68_dev *dev = video_drvdata(file);
> + unsigned int i;
> +
> + if (vb2_is_busy(&dev->vidq))
> + return -EBUSY;
> +
> + /* Look for match on complete norm id (may have mult bits) */
> + for (i = 0; i < TVNORMS; i++) {
> + if (id == tvnorms[i].id)
> + break;
> + }
> +
> + /* If no exact match, look for norm which contains this one */
> + if (i == TVNORMS) {
> + for (i = 0; i < TVNORMS; i++)
> + if (id & tvnorms[i].id)
> + break;
> + }
> + /* If still not matched, give up */
> + if (i == TVNORMS)
> + return -EINVAL;
> +
> + set_tvnorm(dev, &tvnorms[i]); /* do the actual setting */
> + return 0;
> +}
> +
> +static int tw68_g_std(struct file *file, void *priv, v4l2_std_id *id)
> +{
> + struct tw68_dev *dev = video_drvdata(file);
> +
> + *id = dev->tvnorm->id;
> + return 0;
> +}
> +
> +static int tw68_enum_fmt_vid_cap(struct file *file, void *priv,
> + struct v4l2_fmtdesc *f)
> +{
> + if (f->index >= FORMATS)
> + return -EINVAL;
> +
> + strlcpy(f->description, formats[f->index].name,
> + sizeof(f->description));
> +
> + f->pixelformat = formats[f->index].fourcc;
> +
> + return 0;
> +}
> +
> +/*
> + * Used strictly for internal development and debugging, this routine
> + * prints out the current register contents for the tw68xx device.
> + */
> +static void tw68_dump_regs(struct tw68_dev *dev)
> +{
> + unsigned char line[80];
> + int i, j, k;
> + unsigned char *cptr;
> +
> + pr_info("Full dump of TW68 registers:\n");
> + /* First we do the PCI regs, 8 4-byte regs per line */
> + for (i = 0; i < 0x100; i += 32) {
> + cptr = line;
> + cptr += sprintf(cptr, "%03x ", i);
> + /* j steps through the next 4 words */
> + for (j = i; j < i + 16; j += 4)
> + cptr += sprintf(cptr, "%08x ", tw_readl(j));
> + *cptr++ = ' ';
> + for (; j < i + 32; j += 4)
> + cptr += sprintf(cptr, "%08x ", tw_readl(j));
> + *cptr++ = '\n';
> + *cptr = 0;
> + pr_info("%s", line);
> + }
> + /* Next the control regs, which are single-byte, address mod 4 */
> + while (i < 0x400) {
> + cptr = line;
> + cptr += sprintf(cptr, "%03x ", i);
> + /* Print out 4 groups of 4 bytes */
> + for (j = 0; j < 4; j++) {
> + for (k = 0; k < 4; k++) {
> + cptr += sprintf(cptr, "%02x ",
> + tw_readb(i));
> + i += 4;
> + }
> + *cptr++ = ' ';
> + }
> + *cptr++ = '\n';
> + *cptr = 0;
> + pr_info("%s", line);
> + }
> +}
> +
> +static int vidioc_log_status(struct file *file, void *priv)
> +{
> + struct tw68_dev *dev = video_drvdata(file);
> +
> + tw68_dump_regs(dev);
> + return v4l2_ctrl_log_status(file, priv);
> +}
> +
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +static int vidioc_g_register(struct file *file, void *priv,
> + struct v4l2_dbg_register *reg)
> +{
> + struct tw68_dev *dev = video_drvdata(file);
> +
> + if (reg->size == 1)
> + reg->val = tw_readb(reg->reg);
> + else
> + reg->val = tw_readl(reg->reg);
> + return 0;
> +}
> +
> +static int vidioc_s_register(struct file *file, void *priv,
> + const struct v4l2_dbg_register *reg)
> +{
> + struct tw68_dev *dev = video_drvdata(file);
> +
> + if (reg->size == 1)
> + tw_writeb(reg->reg, reg->val);
> + else
> + tw_writel(reg->reg & 0xffff, reg->val);
> + return 0;
> +}
> +#endif
> +
> +static const struct v4l2_ctrl_ops tw68_ctrl_ops = {
> + .s_ctrl = tw68_s_ctrl,
> +};
> +
> +static const struct v4l2_file_operations video_fops = {
> + .owner = THIS_MODULE,
> + .open = v4l2_fh_open,
> + .release = vb2_fop_release,
> + .read = vb2_fop_read,
> + .poll = vb2_fop_poll,
> + .mmap = vb2_fop_mmap,
> + .unlocked_ioctl = video_ioctl2,
> +};
> +
> +static const struct v4l2_ioctl_ops video_ioctl_ops = {
> + .vidioc_querycap = tw68_querycap,
> + .vidioc_enum_fmt_vid_cap = tw68_enum_fmt_vid_cap,
> + .vidioc_reqbufs = vb2_ioctl_reqbufs,
> + .vidioc_create_bufs = vb2_ioctl_create_bufs,
> + .vidioc_querybuf = vb2_ioctl_querybuf,
> + .vidioc_qbuf = vb2_ioctl_qbuf,
> + .vidioc_dqbuf = vb2_ioctl_dqbuf,
> + .vidioc_s_std = tw68_s_std,
> + .vidioc_g_std = tw68_g_std,
> + .vidioc_enum_input = tw68_enum_input,
> + .vidioc_g_input = tw68_g_input,
> + .vidioc_s_input = tw68_s_input,
> + .vidioc_streamon = vb2_ioctl_streamon,
> + .vidioc_streamoff = vb2_ioctl_streamoff,
> + .vidioc_g_fmt_vid_cap = tw68_g_fmt_vid_cap,
> + .vidioc_try_fmt_vid_cap = tw68_try_fmt_vid_cap,
> + .vidioc_s_fmt_vid_cap = tw68_s_fmt_vid_cap,
> + .vidioc_log_status = vidioc_log_status,
> + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
> + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> + .vidioc_g_register = vidioc_g_register,
> + .vidioc_s_register = vidioc_s_register,
> +#endif
> +};
> +
> +static struct video_device tw68_video_template = {
> + .name = "tw68_video",
> + .fops = &video_fops,
> + .ioctl_ops = &video_ioctl_ops,
> + .release = video_device_release_empty,
> + .tvnorms = TW68_NORMS,
> +};
> +
> +/* ------------------------------------------------------------------ */
> +/* exported stuff */
> +void tw68_set_tvnorm_hw(struct tw68_dev *dev)
> +{
> + tw_andorb(TW68_SDT, 0x07, dev->tvnorm->format);
> +}
> +
> +int tw68_video_init1(struct tw68_dev *dev)
> +{
> + struct v4l2_ctrl_handler *hdl = &dev->hdl;
> +
> + v4l2_ctrl_handler_init(hdl, 6);
> + v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
> + V4L2_CID_BRIGHTNESS, -128, 127, 1, 20);
> + v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
> + V4L2_CID_CONTRAST, 0, 255, 1, 100);
> + v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
> + V4L2_CID_SATURATION, 0, 255, 1, 128);
> + /* NTSC only */
> + v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
> + V4L2_CID_HUE, -128, 127, 1, 0);
> + v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
> + V4L2_CID_COLOR_KILLER, 0, 1, 1, 0);
> + v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
> + V4L2_CID_CHROMA_AGC, 0, 1, 1, 1);
> + if (hdl->error) {
> + v4l2_ctrl_handler_free(hdl);
> + return hdl->error;
> + }
> + dev->v4l2_dev.ctrl_handler = hdl;
> + v4l2_ctrl_handler_setup(hdl);
> + return 0;
> +}
> +
> +int tw68_video_init2(struct tw68_dev *dev, int video_nr)
> +{
> + int ret;
> +
> + set_tvnorm(dev, &tvnorms[0]);
> +
> + dev->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24);
> + dev->width = 720;
> + dev->height = 576;
> + dev->field = V4L2_FIELD_INTERLACED;
> +
> + INIT_LIST_HEAD(&dev->active);
> + dev->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> + dev->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> + dev->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ | VB2_DMABUF;
> + dev->vidq.ops = &tw68_video_qops;
> + dev->vidq.mem_ops = &vb2_dma_sg_memops;
> + dev->vidq.drv_priv = dev;
> + dev->vidq.gfp_flags = __GFP_DMA32;
> + dev->vidq.buf_struct_size = sizeof(struct tw68_buf);
> + dev->vidq.lock = &dev->lock;
> + dev->vidq.min_buffers_needed = 2;
> + ret = vb2_queue_init(&dev->vidq);
> + if (ret)
> + return ret;
> + dev->vdev = tw68_video_template;
> + dev->vdev.v4l2_dev = &dev->v4l2_dev;
> + dev->vdev.lock = &dev->lock;
> + dev->vdev.queue = &dev->vidq;
> + video_set_drvdata(&dev->vdev, dev);
> + return video_register_device(&dev->vdev, VFL_TYPE_GRABBER, video_nr);
> +}
> +
> +/*
> + * tw68_irq_video_done
> + */
> +void tw68_irq_video_done(struct tw68_dev *dev, unsigned long status)
> +{
> + __u32 reg;
> +
> + /* reset interrupts handled by this routine */
> + tw_writel(TW68_INTSTAT, status);
> + /*
> + * Check most likely first
> + *
> + * DMAPI shows we have reached the end of the risc code
> + * for the current buffer.
> + */
> + if (status & TW68_DMAPI) {
> + struct tw68_buf *buf;
> +
> + spin_lock(&dev->slock);
> + buf = list_entry(dev->active.next, struct tw68_buf, list);
> + list_del(&buf->list);
> + spin_unlock(&dev->slock);
> + v4l2_get_timestamp(&buf->vb.v4l2_buf.timestamp);
> + buf->vb.v4l2_buf.field = dev->field;
> + buf->vb.v4l2_buf.sequence = dev->seqnr++;
> + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);
> + status &= ~(TW68_DMAPI);
> + if (0 == status)
> + return;
> + }
> + if (status & (TW68_VLOCK | TW68_HLOCK))
> + dev_dbg(&dev->pci->dev, "Lost sync\n");
> + if (status & TW68_PABORT)
> + dev_err(&dev->pci->dev, "PABORT interrupt\n");
> + if (status & TW68_DMAPERR)
> + dev_err(&dev->pci->dev, "DMAPERR interrupt\n");
> + /*
> + * On TW6800, FDMIS is apparently generated if video input is switched
> + * during operation. Therefore, it is not enabled for that chip.
> + */
> + if (status & TW68_FDMIS)
> + dev_dbg(&dev->pci->dev, "FDMIS interrupt\n");
> + if (status & TW68_FFOF) {
> + /* probably a logic error */
> + reg = tw_readl(TW68_DMAC) & TW68_FIFO_EN;
> + tw_clearl(TW68_DMAC, TW68_FIFO_EN);
> + dev_dbg(&dev->pci->dev, "FFOF interrupt\n");
> + tw_setl(TW68_DMAC, reg);
> + }
> + if (status & TW68_FFERR)
> + dev_dbg(&dev->pci->dev, "FFERR interrupt\n");
> +}
> diff --git a/drivers/media/pci/tw68/tw68.h b/drivers/media/pci/tw68/tw68.h
> new file mode 100644
> index 0000000..2c8abe2
> --- /dev/null
> +++ b/drivers/media/pci/tw68/tw68.h
> @@ -0,0 +1,231 @@
> +/*
> + * tw68 driver common header file
> + *
> + * Much of this code is derived from the cx88 and sa7134 drivers, which
> + * were in turn derived from the bt87x driver. The original work was by
> + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab,
> + * Hans Verkuil, Andy Walls and many others. Their work is gratefully
> + * acknowledged. Full credit goes to them - any problems within this code
> + * are mine.
> + *
> + * Copyright (C) 2009 William M. Brack
> + *
> + * Refactored and updated to the latest v4l core frameworks:
> + *
> + * Copyright (C) 2014 Hans Verkuil <hverkuil@xs4all.nl>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/version.h>
> +#include <linux/pci.h>
> +#include <linux/videodev2.h>
> +#include <linux/notifier.h>
> +#include <linux/delay.h>
> +#include <linux/mutex.h>
> +#include <linux/io.h>
> +
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/videobuf2-dma-sg.h>
> +
> +#include "tw68-reg.h"
> +
> +#define UNSET (-1U)
> +
> +/* system vendor and device ID's */
> +#define PCI_VENDOR_ID_TECHWELL 0x1797
> +#define PCI_DEVICE_ID_6800 0x6800
> +#define PCI_DEVICE_ID_6801 0x6801
> +#define PCI_DEVICE_ID_AUDIO2 0x6802
> +#define PCI_DEVICE_ID_TS3 0x6803
> +#define PCI_DEVICE_ID_6804 0x6804
> +#define PCI_DEVICE_ID_AUDIO5 0x6805
> +#define PCI_DEVICE_ID_TS6 0x6806
> +
> +/* tw6816 based cards */
> +#define PCI_DEVICE_ID_6816_1 0x6810
> +#define PCI_DEVICE_ID_6816_2 0x6811
> +#define PCI_DEVICE_ID_6816_3 0x6812
> +#define PCI_DEVICE_ID_6816_4 0x6813
> +
> +#define TW68_NORMS ( \
> + V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM | \
> + V4L2_STD_PAL_M | V4L2_STD_PAL_Nc | V4L2_STD_PAL_60)
> +
> +#define TW68_VID_INTS (TW68_FFERR | TW68_PABORT | TW68_DMAPERR | \
> + TW68_FFOF | TW68_DMAPI)
> +/* TW6800 chips have trouble with these, so we don't set them for that chip */
> +#define TW68_VID_INTSX (TW68_FDMIS | TW68_HLOCK | TW68_VLOCK)
> +
> +#define TW68_I2C_INTS (TW68_SBERR | TW68_SBDONE | TW68_SBERR2 | \
> + TW68_SBDONE2)
> +
> +enum tw68_decoder_type {
> + TW6800,
> + TW6801,
> + TW6804,
> + TWXXXX,
> +};
> +
> +/* ----------------------------------------------------------- */
> +/* static data */
> +
> +struct tw68_tvnorm {
> + char *name;
> + v4l2_std_id id;
> +
> + /* video decoder */
> + u32 sync_control;
> + u32 luma_control;
> + u32 chroma_ctrl1;
> + u32 chroma_gain;
> + u32 chroma_ctrl2;
> + u32 vgate_misc;
> +
> + /* video scaler */
> + u32 h_delay;
> + u32 h_delay0; /* for TW6800 */
> + u32 h_start;
> + u32 h_stop;
> + u32 v_delay;
> + u32 video_v_start;
> + u32 video_v_stop;
> + u32 vbi_v_start_0;
> + u32 vbi_v_stop_0;
> + u32 vbi_v_start_1;
> +
> + /* Techwell specific */
> + u32 format;
> +};
> +
> +struct tw68_format {
> + char *name;
> + u32 fourcc;
> + u32 depth;
> + u32 twformat;
> +};
> +
> +/* ----------------------------------------------------------- */
> +/* card configuration */
> +
> +#define TW68_BOARD_NOAUTO UNSET
> +#define TW68_BOARD_UNKNOWN 0
> +#define TW68_BOARD_GENERIC_6802 1
> +
> +#define TW68_MAXBOARDS 16
> +#define TW68_INPUT_MAX 4
> +
> +/* ----------------------------------------------------------- */
> +/* device / file handle status */
> +
> +#define BUFFER_TIMEOUT msecs_to_jiffies(500) /* 0.5 seconds */
> +
> +struct tw68_dev; /* forward delclaration */
> +
> +/* buffer for one video/vbi/ts frame */
> +struct tw68_buf {
> + struct vb2_buffer vb;
> + struct list_head list;
> +
> + unsigned int size;
> + __le32 *cpu;
> + __le32 *jmp;
> + dma_addr_t dma;
> +};
> +
> +struct tw68_fmt {
> + char *name;
> + u32 fourcc; /* v4l2 format id */
> + int depth;
> + int flags;
> + u32 twformat;
> +};
> +
> +/* global device status */
> +struct tw68_dev {
> + struct mutex lock;
> + spinlock_t slock;
> + u16 instance;
> + struct v4l2_device v4l2_dev;
> +
> + /* various device info */
> + enum tw68_decoder_type vdecoder;
> + struct video_device vdev;
> + struct v4l2_ctrl_handler hdl;
> +
> + /* pci i/o */
> + char *name;
> + struct pci_dev *pci;
> + unsigned char pci_rev, pci_lat;
> + u32 __iomem *lmmio;
> + u8 __iomem *bmmio;
> + u32 pci_irqmask;
> + /* The irq mask to be used will depend upon the chip type */
> + u32 board_virqmask;
> +
> + /* video capture */
> + const struct tw68_format *fmt;
> + unsigned width, height;
> + unsigned seqnr;
> + unsigned field;
> + struct vb2_queue vidq;
> + struct list_head active;
> +
> + /* various v4l controls */
> + const struct tw68_tvnorm *tvnorm; /* video */
> +
> + int input;
> +};
> +
> +/* ----------------------------------------------------------- */
> +
> +#define tw_readl(reg) readl(dev->lmmio + ((reg) >> 2))
> +#define tw_readb(reg) readb(dev->bmmio + (reg))
> +#define tw_writel(reg, value) writel((value), dev->lmmio + ((reg) >> 2))
> +#define tw_writeb(reg, value) writeb((value), dev->bmmio + (reg))
> +
> +#define tw_andorl(reg, mask, value) \
> + writel((readl(dev->lmmio+((reg)>>2)) & ~(mask)) |\
> + ((value) & (mask)), dev->lmmio+((reg)>>2))
> +#define tw_andorb(reg, mask, value) \
> + writeb((readb(dev->bmmio + (reg)) & ~(mask)) |\
> + ((value) & (mask)), dev->bmmio+(reg))
> +#define tw_setl(reg, bit) tw_andorl((reg), (bit), (bit))
> +#define tw_setb(reg, bit) tw_andorb((reg), (bit), (bit))
> +#define tw_clearl(reg, bit) \
> + writel((readl(dev->lmmio + ((reg) >> 2)) & ~(bit)), \
> + dev->lmmio + ((reg) >> 2))
> +#define tw_clearb(reg, bit) \
> + writeb((readb(dev->bmmio+(reg)) & ~(bit)), \
> + dev->bmmio + (reg))
> +
> +#define tw_wait(us) { udelay(us); }
> +
> +/* ----------------------------------------------------------- */
> +/* tw68-video.c */
> +
> +void tw68_set_tvnorm_hw(struct tw68_dev *dev);
> +
> +int tw68_video_init1(struct tw68_dev *dev);
> +int tw68_video_init2(struct tw68_dev *dev, int video_nr);
> +void tw68_irq_video_done(struct tw68_dev *dev, unsigned long status);
> +int tw68_video_start_dma(struct tw68_dev *dev, struct tw68_buf *buf);
> +
> +/* ----------------------------------------------------------- */
> +/* tw68-risc.c */
> +
> +int tw68_risc_buffer(struct pci_dev *pci, struct tw68_buf *buf,
> + struct scatterlist *sglist, unsigned int top_offset,
> + unsigned int bottom_offset, unsigned int bpl,
> + unsigned int padding, unsigned int lines);
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCHv3 1/2] tw68: add support for Techwell tw68xx PCI grabber boards
2014-09-02 19:21 ` Mauro Carvalho Chehab
@ 2014-09-02 19:54 ` Hans Verkuil
2014-09-02 20:23 ` Mauro Carvalho Chehab
0 siblings, 1 reply; 7+ messages in thread
From: Hans Verkuil @ 2014-09-02 19:54 UTC (permalink / raw)
To: Mauro Carvalho Chehab; +Cc: linux-media, Hans Verkuil
On 09/02/2014 09:21 PM, Mauro Carvalho Chehab wrote:
> Em Tue, 26 Aug 2014 08:33:12 +0200
> Hans Verkuil <hverkuil@xs4all.nl> escreveu:
>
>> From: Hans Verkuil <hans.verkuil@cisco.com>
>>
>> Add support for the tw68 driver. The driver has been out-of-tree for many
>> years on gitorious: https://gitorious.org/tw68/tw68-v2
>>
>> I have refactored and ported that driver to the latest V4L2 core frameworks.
>>
>> Tested with my Techwell tw6805a and tw6816 grabber boards.
>>
>> Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
>
> I would be expecting here the William M. Brack's SOB too.
Sorry, that's not possible. The only email I have no longer works. I googled
to see if I could find another email without success. I met him years
ago during an ELC and he was a retired engineer, so he may not even be
around anymore. His gitorious.org repo has been inactive for over two years.
>
> Also, the best is to add his original work on one patch (without Kbuild
> stuff) and your changes on a separate patch. That helps us to identify
> what are your contributions to his code, and what was his original
> copyright wording.
Are you sure you want that in the mainline kernel? His code is in gitorious,
a link to that is in the commit log. I'm not too keen to add code to the
kernel which is promptly being overwritten by another version. An alternative
might be to add a link to that repo to tw68-core.c as well so that it is
not just in the commit log but also in the source code.
Regards,
Hans
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCHv3 1/2] tw68: add support for Techwell tw68xx PCI grabber boards
2014-09-02 19:54 ` Hans Verkuil
@ 2014-09-02 20:23 ` Mauro Carvalho Chehab
2014-09-02 22:04 ` Hans Verkuil
0 siblings, 1 reply; 7+ messages in thread
From: Mauro Carvalho Chehab @ 2014-09-02 20:23 UTC (permalink / raw)
To: Hans Verkuil; +Cc: linux-media, Hans Verkuil
Em Tue, 02 Sep 2014 21:54:24 +0200
Hans Verkuil <hverkuil@xs4all.nl> escreveu:
> On 09/02/2014 09:21 PM, Mauro Carvalho Chehab wrote:
> > Em Tue, 26 Aug 2014 08:33:12 +0200
> > Hans Verkuil <hverkuil@xs4all.nl> escreveu:
> >
> >> From: Hans Verkuil <hans.verkuil@cisco.com>
> >>
> >> Add support for the tw68 driver. The driver has been out-of-tree for many
> >> years on gitorious: https://gitorious.org/tw68/tw68-v2
> >>
> >> I have refactored and ported that driver to the latest V4L2 core frameworks.
> >>
> >> Tested with my Techwell tw6805a and tw6816 grabber boards.
> >>
> >> Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
> >
> > I would be expecting here the William M. Brack's SOB too.
>
> Sorry, that's not possible. The only email I have no longer works. I googled
> to see if I could find another email without success. I met him years
> ago during an ELC and he was a retired engineer, so he may not even be
> around anymore. His gitorious.org repo has been inactive for over two years.
>
> >
> > Also, the best is to add his original work on one patch (without Kbuild
> > stuff) and your changes on a separate patch. That helps us to identify
> > what are your contributions to his code, and what was his original
> > copyright wording.
>
> Are you sure you want that in the mainline kernel? His code is in gitorious,
> a link to that is in the commit log. I'm not too keen to add code to the
> kernel which is promptly being overwritten by another version. An alternative
> might be to add a link to that repo to tw68-core.c as well so that it is
> not just in the commit log but also in the source code.
There's no way to warrant that his gitorious repository will stay there
forever. We need to have the reference code somewhere, if something ever
complain about copyrights, especially since you're not able to contact
the author of the driver.
>
> Regards,
>
> Hans
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCHv3 1/2] tw68: add support for Techwell tw68xx PCI grabber boards
2014-09-02 20:23 ` Mauro Carvalho Chehab
@ 2014-09-02 22:04 ` Hans Verkuil
0 siblings, 0 replies; 7+ messages in thread
From: Hans Verkuil @ 2014-09-02 22:04 UTC (permalink / raw)
To: Mauro Carvalho Chehab; +Cc: linux-media, Hans Verkuil
On 09/02/2014 10:23 PM, Mauro Carvalho Chehab wrote:
> Em Tue, 02 Sep 2014 21:54:24 +0200
> Hans Verkuil <hverkuil@xs4all.nl> escreveu:
>
>> On 09/02/2014 09:21 PM, Mauro Carvalho Chehab wrote:
>>> Em Tue, 26 Aug 2014 08:33:12 +0200
>>> Hans Verkuil <hverkuil@xs4all.nl> escreveu:
>>>
>>>> From: Hans Verkuil <hans.verkuil@cisco.com>
>>>>
>>>> Add support for the tw68 driver. The driver has been out-of-tree for many
>>>> years on gitorious: https://gitorious.org/tw68/tw68-v2
>>>>
>>>> I have refactored and ported that driver to the latest V4L2 core frameworks.
>>>>
>>>> Tested with my Techwell tw6805a and tw6816 grabber boards.
>>>>
>>>> Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
>>>
>>> I would be expecting here the William M. Brack's SOB too.
>>
>> Sorry, that's not possible. The only email I have no longer works. I googled
>> to see if I could find another email without success. I met him years
>> ago during an ELC and he was a retired engineer, so he may not even be
>> around anymore. His gitorious.org repo has been inactive for over two years.
>>
>>>
>>> Also, the best is to add his original work on one patch (without Kbuild
>>> stuff) and your changes on a separate patch. That helps us to identify
>>> what are your contributions to his code, and what was his original
>>> copyright wording.
>>
>> Are you sure you want that in the mainline kernel? His code is in gitorious,
>> a link to that is in the commit log. I'm not too keen to add code to the
>> kernel which is promptly being overwritten by another version. An alternative
>> might be to add a link to that repo to tw68-core.c as well so that it is
>> not just in the commit log but also in the source code.
>
> There's no way to warrant that his gitorious repository will stay there
> forever. We need to have the reference code somewhere, if something ever
> complain about copyrights, especially since you're not able to contact
> the author of the driver.
OK, I'll prepare a new pull request tomorrow.
Regards,
Hans
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2014-09-02 22:04 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-08-26 6:33 [PATCHv3 0/2] Add driver for tw68xx PCI grabber boards Hans Verkuil
2014-08-26 6:33 ` [PATCHv3 1/2] tw68: add support for Techwell " Hans Verkuil
2014-09-02 19:21 ` Mauro Carvalho Chehab
2014-09-02 19:54 ` Hans Verkuil
2014-09-02 20:23 ` Mauro Carvalho Chehab
2014-09-02 22:04 ` Hans Verkuil
2014-08-26 6:33 ` [PATCHv3 2/2] MAINTAINERS: add tw68 entry 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).