diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/media/pci/tw68 | |
parent | Initial commit. (diff) | |
download | linux-upstream.tar.xz linux-upstream.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | drivers/media/pci/tw68/Kconfig | 10 | ||||
-rw-r--r-- | drivers/media/pci/tw68/Makefile | 4 | ||||
-rw-r--r-- | drivers/media/pci/tw68/tw68-core.c | 418 | ||||
-rw-r--r-- | drivers/media/pci/tw68/tw68-reg.h | 186 | ||||
-rw-r--r-- | drivers/media/pci/tw68/tw68-risc.c | 223 | ||||
-rw-r--r-- | drivers/media/pci/tw68/tw68-video.c | 1018 | ||||
-rw-r--r-- | drivers/media/pci/tw68/tw68.h | 205 | ||||
-rw-r--r-- | drivers/media/pci/tw686x/Kconfig | 20 | ||||
-rw-r--r-- | drivers/media/pci/tw686x/Makefile | 4 | ||||
-rw-r--r-- | drivers/media/pci/tw686x/tw686x-audio.c | 413 | ||||
-rw-r--r-- | drivers/media/pci/tw686x/tw686x-core.c | 452 | ||||
-rw-r--r-- | drivers/media/pci/tw686x/tw686x-regs.h | 132 | ||||
-rw-r--r-- | drivers/media/pci/tw686x/tw686x-video.c | 1308 | ||||
-rw-r--r-- | drivers/media/pci/tw686x/tw686x.h | 179 |
14 files changed, 4572 insertions, 0 deletions
diff --git a/drivers/media/pci/tw68/Kconfig b/drivers/media/pci/tw68/Kconfig new file mode 100644 index 000000000..ef9c0e886 --- /dev/null +++ b/drivers/media/pci/tw68/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_TW68 + tristate "Techwell tw68x Video For Linux" + depends on VIDEO_DEV && PCI + 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 000000000..d1aec257e --- /dev/null +++ b/drivers/media/pci/tw68/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +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 000000000..35dd19b24 --- /dev/null +++ b/drivers/media/pci/tw68/tw68-core.c @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 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> + */ + +#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/pci_ids.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: https://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. + */ +static const struct pci_device_id tw68_pci_tbl[] = { + {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_TECHWELL_6800)}, + {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_TECHWELL_6801)}, + {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_TECHWELL_6804)}, + {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_TECHWELL_6816_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_TECHWELL_6816_2)}, + {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_TECHWELL_6816_3)}, + {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_TECHWELL_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); + err = dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(32)); + if (err) { + pr_info("%s: Oops: no 32bit PCI DMA ???\n", dev->name); + goto fail1; + } + + switch (pci_id->device) { + case PCI_DEVICE_ID_TECHWELL_6800: /* TW6800 */ + dev->vdecoder = TW6800; + dev->board_virqmask = TW68_VID_INTS; + break; + case PCI_DEVICE_ID_TECHWELL_6801: /* Video decoder for TW6802 */ + dev->vdecoder = TW6801; + dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX; + break; + case PCI_DEVICE_ID_TECHWELL_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, 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); +} + +static int __maybe_unused tw68_suspend(struct device *dev_d) +{ + struct pci_dev *pci_dev = to_pci_dev(dev_d); + 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); + + vb2_discard_done(&dev->vidq); + + return 0; +} + +static int __maybe_unused tw68_resume(struct device *dev_d) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev_d); + struct tw68_dev *dev = container_of(v4l2_dev, + struct tw68_dev, v4l2_dev); + struct tw68_buf *buf; + unsigned long flags; + + /* 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; +} + +/* ----------------------------------------------------------- */ + +static SIMPLE_DEV_PM_OPS(tw68_pm_ops, tw68_suspend, tw68_resume); + +static struct pci_driver tw68_pci_driver = { + .name = "tw68", + .id_table = tw68_pci_tbl, + .probe = tw68_initdev, + .remove = tw68_finidev, + .driver.pm = &tw68_pm_ops, +}; + +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 000000000..dcd9931b2 --- /dev/null +++ b/drivers/media/pci/tw68/tw68-reg.h @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * 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> +*/ + +#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 000000000..dacb136c4 --- /dev/null +++ b/drivers/media/pci/tw68/tw68-risc.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 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> + */ + +#include "tw68.h" + +/** + * tw68_risc_field + * @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: structure with info about the pci + * slot which our device is in. + * @buf: 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 = dma_alloc_coherent(&pci->dev, buf->size, &buf->dma, + GFP_KERNEL); + 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 000000000..773a18702 --- /dev/null +++ b/drivers/media/pci/tw68/tw68-video.c @@ -0,0 +1,1018 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 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> + */ + +#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[] = { + { + .fourcc = V4L2_PIX_FMT_RGB555, + .depth = 16, + .twformat = ColorFormatRGB15, + }, { + .fourcc = V4L2_PIX_FMT_RGB555X, + .depth = 16, + .twformat = ColorFormatRGB15 | ColorFormatBSWAP, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .twformat = ColorFormatRGB16, + }, { + .fourcc = V4L2_PIX_FMT_RGB565X, + .depth = 16, + .twformat = ColorFormatRGB16 | ColorFormatBSWAP, + }, { + .fourcc = V4L2_PIX_FMT_BGR24, + .depth = 24, + .twformat = ColorFormatRGB24, + }, { + .fourcc = V4L2_PIX_FMT_RGB24, + .depth = 24, + .twformat = ColorFormatRGB24 | ColorFormatBSWAP, + }, { + .fourcc = V4L2_PIX_FMT_BGR32, + .depth = 32, + .twformat = ColorFormatRGB32, + }, { + .fourcc = V4L2_PIX_FMT_RGB32, + .depth = 32, + .twformat = ColorFormatRGB32 | ColorFormatBSWAP | + ColorFormatWSWAP, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .twformat = ColorFormatYUY2, + }, { + .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, 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; +} + +/* ------------------------------------------------------------------ */ + +/* 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 = (4 * 1024 * 1024) / roundup(size, PAGE_SIZE); + if (count > maxcount) + count = maxcount; + return count; +} + +/* ------------------------------------------------------------- */ +/* vb2 queue operations */ + +static int tw68_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct tw68_dev *dev = vb2_get_drv_priv(q); + unsigned tot_bufs = q->num_buffers + *num_buffers; + unsigned size = (dev->fmt->depth * dev->width * dev->height) >> 3; + + if (tot_bufs < 2) + tot_bufs = 2; + tot_bufs = tw68_buffer_count(size, tot_bufs); + *num_buffers = tot_bufs - q->num_buffers; + /* + * We allow create_bufs, but only if the sizeimage is >= as the + * current sizeimage. The tw68_buffer_count calculation becomes quite + * difficult otherwise. + */ + if (*num_planes) + return sizes[0] < size ? -EINVAL : 0; + *num_planes = 1; + sizes[0] = size; + + 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_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vb2_queue *vq = vb->vb2_queue; + struct tw68_dev *dev = vb2_get_drv_priv(vq); + struct tw68_buf *buf = container_of(vbuf, 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 ancillary 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) +{ + int ret; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vb2_queue *vq = vb->vb2_queue; + struct tw68_dev *dev = vb2_get_drv_priv(vq); + struct tw68_buf *buf = container_of(vbuf, struct tw68_buf, vb); + struct sg_table *dma = vb2_dma_sg_plane_desc(vb, 0); + unsigned size, bpl; + + 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); + + bpl = (dev->width * dev->fmt->depth) >> 3; + switch (dev->field) { + case V4L2_FIELD_TOP: + ret = tw68_risc_buffer(dev->pci, buf, dma->sgl, + 0, UNSET, bpl, 0, dev->height); + break; + case V4L2_FIELD_BOTTOM: + ret = tw68_risc_buffer(dev->pci, buf, dma->sgl, + UNSET, 0, bpl, 0, dev->height); + break; + case V4L2_FIELD_SEQ_TB: + ret = tw68_risc_buffer(dev->pci, buf, dma->sgl, + 0, bpl * (dev->height >> 1), + bpl, 0, dev->height >> 1); + break; + case V4L2_FIELD_SEQ_BT: + ret = tw68_risc_buffer(dev->pci, buf, dma->sgl, + bpl * (dev->height >> 1), 0, + bpl, 0, dev->height >> 1); + break; + case V4L2_FIELD_INTERLACED: + default: + ret = tw68_risc_buffer(dev->pci, buf, dma->sgl, + 0, bpl, bpl, bpl, dev->height >> 1); + break; + } + return ret; +} + +static void tw68_buf_finish(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vb2_queue *vq = vb->vb2_queue; + struct tw68_dev *dev = vb2_get_drv_priv(vq); + struct tw68_buf *buf = container_of(vbuf, struct tw68_buf, vb); + + if (buf->cpu) + dma_free_coherent(&dev->pci->dev, 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, VB2_BUF_STATE_ERROR); + } +} + +static const 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; + 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) +{ + strscpy(cap->driver, "tw68", sizeof(cap->driver)); + strscpy(cap->card, "Techwell Capture Card", + sizeof(cap->card)); + 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; + + 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 const 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, + .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING, +}; + +/* ------------------------------------------------------------------ */ +/* 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 | __GFP_KSWAPD_RECLAIM; + dev->vidq.buf_struct_size = sizeof(struct tw68_buf); + dev->vidq.lock = &dev->lock; + dev->vidq.min_buffers_needed = 2; + dev->vidq.dev = &dev->pci->dev; + 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_VIDEO, 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); + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + buf->vb.field = dev->field; + buf->vb.sequence = dev->seqnr++; + vb2_buffer_done(&buf->vb.vb2_buf, 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 000000000..a1f422d6e --- /dev/null +++ b/drivers/media/pci/tw68/tw68.h @@ -0,0 +1,205 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * 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> + */ + +#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-v4l2.h> +#include <media/videobuf2-dma-sg.h> + +#include "tw68-reg.h" + +#define UNSET (-1U) + +#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 { + 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_v4l2_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); diff --git a/drivers/media/pci/tw686x/Kconfig b/drivers/media/pci/tw686x/Kconfig new file mode 100644 index 000000000..a4edad6aa --- /dev/null +++ b/drivers/media/pci/tw686x/Kconfig @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_TW686X + tristate "Intersil/Techwell TW686x video capture cards" + depends on PCI && VIDEO_DEV && SND + select VIDEOBUF2_VMALLOC + select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_DMA_SG + select SND_PCM + help + Support for Intersil/Techwell TW686x-based frame grabber cards. + + Currently supported chips: + - TW6864 (4 video channels), + - TW6865 (4 video channels, not tested, second generation chip), + - TW6868 (8 video channels but only 4 first channels using + built-in video decoder are supported, not tested), + - TW6869 (8 video channels, second generation chip). + + To compile this driver as a module, choose M here: the module + will be named tw686x. diff --git a/drivers/media/pci/tw686x/Makefile b/drivers/media/pci/tw686x/Makefile new file mode 100644 index 000000000..1795dffb5 --- /dev/null +++ b/drivers/media/pci/tw686x/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +tw686x-objs := tw686x-core.o tw686x-video.o tw686x-audio.o + +obj-$(CONFIG_VIDEO_TW686X) += tw686x.o diff --git a/drivers/media/pci/tw686x/tw686x-audio.c b/drivers/media/pci/tw686x/tw686x-audio.c new file mode 100644 index 000000000..74cba1368 --- /dev/null +++ b/drivers/media/pci/tw686x/tw686x-audio.c @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 VanguardiaSur - www.vanguardiasur.com.ar + * + * Based on the audio support from the tw6869 driver: + * Copyright 2015 www.starterkit.ru <info@starterkit.ru> + * + * Based on: + * Driver for Intersil|Techwell TW6869 based DVR cards + * (c) 2011-12 liran <jli11@intersil.com> [Intersil|Techwell China] + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kmod.h> +#include <linux/mutex.h> +#include <linux/pci.h> +#include <linux/delay.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/control.h> +#include "tw686x.h" +#include "tw686x-regs.h" + +#define AUDIO_CHANNEL_OFFSET 8 + +void tw686x_audio_irq(struct tw686x_dev *dev, unsigned long requests, + unsigned int pb_status) +{ + unsigned long flags; + unsigned int ch, pb; + + for_each_set_bit(ch, &requests, max_channels(dev)) { + struct tw686x_audio_channel *ac = &dev->audio_channels[ch]; + struct tw686x_audio_buf *done = NULL; + struct tw686x_audio_buf *next = NULL; + struct tw686x_dma_desc *desc; + + pb = !!(pb_status & BIT(AUDIO_CHANNEL_OFFSET + ch)); + + spin_lock_irqsave(&ac->lock, flags); + + /* Sanity check */ + if (!ac->ss || !ac->curr_bufs[0] || !ac->curr_bufs[1]) { + spin_unlock_irqrestore(&ac->lock, flags); + continue; + } + + if (!list_empty(&ac->buf_list)) { + next = list_first_entry(&ac->buf_list, + struct tw686x_audio_buf, list); + list_move_tail(&next->list, &ac->buf_list); + done = ac->curr_bufs[!pb]; + ac->curr_bufs[pb] = next; + } + spin_unlock_irqrestore(&ac->lock, flags); + + if (!done || !next) + continue; + /* + * Checking for a non-nil dma_desc[pb]->virt buffer is + * the same as checking for memcpy DMA mode. + */ + desc = &ac->dma_descs[pb]; + if (desc->virt) { + memcpy(done->virt, desc->virt, + dev->period_size); + } else { + u32 reg = pb ? ADMA_B_ADDR[ch] : ADMA_P_ADDR[ch]; + reg_write(dev, reg, next->dma); + } + ac->ptr = done->dma - ac->buf[0].dma; + snd_pcm_period_elapsed(ac->ss); + } +} + +/* + * Audio parameters are global and shared among all + * capture channels. The driver prevents changes to + * the parameters if any audio channel is capturing. + */ +static const struct snd_pcm_hardware tw686x_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = TW686X_AUDIO_PAGE_MAX * AUDIO_DMA_SIZE_MAX, + .period_bytes_min = AUDIO_DMA_SIZE_MIN, + .period_bytes_max = AUDIO_DMA_SIZE_MAX, + .periods_min = TW686X_AUDIO_PERIODS_MIN, + .periods_max = TW686X_AUDIO_PERIODS_MAX, +}; + +static int tw686x_pcm_open(struct snd_pcm_substream *ss) +{ + struct tw686x_dev *dev = snd_pcm_substream_chip(ss); + struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number]; + struct snd_pcm_runtime *rt = ss->runtime; + int err; + + ac->ss = ss; + rt->hw = tw686x_capture_hw; + + err = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + + return 0; +} + +static int tw686x_pcm_close(struct snd_pcm_substream *ss) +{ + struct tw686x_dev *dev = snd_pcm_substream_chip(ss); + struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number]; + + ac->ss = NULL; + return 0; +} + +static int tw686x_pcm_prepare(struct snd_pcm_substream *ss) +{ + struct tw686x_dev *dev = snd_pcm_substream_chip(ss); + struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number]; + struct snd_pcm_runtime *rt = ss->runtime; + unsigned int period_size = snd_pcm_lib_period_bytes(ss); + struct tw686x_audio_buf *p_buf, *b_buf; + unsigned long flags; + int i; + + spin_lock_irqsave(&dev->lock, flags); + /* + * Given the audio parameters are global (i.e. shared across + * DMA channels), we need to check new params are allowed. + */ + if (((dev->audio_rate != rt->rate) || + (dev->period_size != period_size)) && dev->audio_enabled) + goto err_audio_busy; + + tw686x_disable_channel(dev, AUDIO_CHANNEL_OFFSET + ac->ch); + spin_unlock_irqrestore(&dev->lock, flags); + + if (dev->audio_rate != rt->rate) { + u32 reg; + + dev->audio_rate = rt->rate; + reg = ((125000000 / rt->rate) << 16) + + ((125000000 % rt->rate) << 16) / rt->rate; + + reg_write(dev, AUDIO_CONTROL2, reg); + } + + if (dev->period_size != period_size) { + u32 reg; + + dev->period_size = period_size; + reg = reg_read(dev, AUDIO_CONTROL1); + reg &= ~(AUDIO_DMA_SIZE_MASK << AUDIO_DMA_SIZE_SHIFT); + reg |= period_size << AUDIO_DMA_SIZE_SHIFT; + + reg_write(dev, AUDIO_CONTROL1, reg); + } + + if (rt->periods < TW686X_AUDIO_PERIODS_MIN || + rt->periods > TW686X_AUDIO_PERIODS_MAX) + return -EINVAL; + + spin_lock_irqsave(&ac->lock, flags); + INIT_LIST_HEAD(&ac->buf_list); + + for (i = 0; i < rt->periods; i++) { + ac->buf[i].dma = rt->dma_addr + period_size * i; + ac->buf[i].virt = rt->dma_area + period_size * i; + INIT_LIST_HEAD(&ac->buf[i].list); + list_add_tail(&ac->buf[i].list, &ac->buf_list); + } + + p_buf = list_first_entry(&ac->buf_list, struct tw686x_audio_buf, list); + list_move_tail(&p_buf->list, &ac->buf_list); + + b_buf = list_first_entry(&ac->buf_list, struct tw686x_audio_buf, list); + list_move_tail(&b_buf->list, &ac->buf_list); + + ac->curr_bufs[0] = p_buf; + ac->curr_bufs[1] = b_buf; + ac->ptr = 0; + + if (dev->dma_mode != TW686X_DMA_MODE_MEMCPY) { + reg_write(dev, ADMA_P_ADDR[ac->ch], p_buf->dma); + reg_write(dev, ADMA_B_ADDR[ac->ch], b_buf->dma); + } + + spin_unlock_irqrestore(&ac->lock, flags); + + return 0; + +err_audio_busy: + spin_unlock_irqrestore(&dev->lock, flags); + return -EBUSY; +} + +static int tw686x_pcm_trigger(struct snd_pcm_substream *ss, int cmd) +{ + struct tw686x_dev *dev = snd_pcm_substream_chip(ss); + struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number]; + unsigned long flags; + int err = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (ac->curr_bufs[0] && ac->curr_bufs[1]) { + spin_lock_irqsave(&dev->lock, flags); + dev->audio_enabled = 1; + tw686x_enable_channel(dev, + AUDIO_CHANNEL_OFFSET + ac->ch); + spin_unlock_irqrestore(&dev->lock, flags); + + mod_timer(&dev->dma_delay_timer, + jiffies + msecs_to_jiffies(100)); + } else { + err = -EIO; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + spin_lock_irqsave(&dev->lock, flags); + dev->audio_enabled = 0; + tw686x_disable_channel(dev, AUDIO_CHANNEL_OFFSET + ac->ch); + spin_unlock_irqrestore(&dev->lock, flags); + + spin_lock_irqsave(&ac->lock, flags); + ac->curr_bufs[0] = NULL; + ac->curr_bufs[1] = NULL; + spin_unlock_irqrestore(&ac->lock, flags); + break; + default: + err = -EINVAL; + } + return err; +} + +static snd_pcm_uframes_t tw686x_pcm_pointer(struct snd_pcm_substream *ss) +{ + struct tw686x_dev *dev = snd_pcm_substream_chip(ss); + struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number]; + + return bytes_to_frames(ss->runtime, ac->ptr); +} + +static const struct snd_pcm_ops tw686x_pcm_ops = { + .open = tw686x_pcm_open, + .close = tw686x_pcm_close, + .prepare = tw686x_pcm_prepare, + .trigger = tw686x_pcm_trigger, + .pointer = tw686x_pcm_pointer, +}; + +static int tw686x_snd_pcm_init(struct tw686x_dev *dev) +{ + struct snd_card *card = dev->snd_card; + struct snd_pcm *pcm; + struct snd_pcm_substream *ss; + unsigned int i; + int err; + + err = snd_pcm_new(card, card->driver, 0, 0, max_channels(dev), &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &tw686x_pcm_ops); + snd_pcm_chip(pcm) = dev; + pcm->info_flags = 0; + strscpy(pcm->name, "tw686x PCM", sizeof(pcm->name)); + + for (i = 0, ss = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + ss; ss = ss->next, i++) + snprintf(ss->name, sizeof(ss->name), "vch%u audio", i); + + snd_pcm_set_managed_buffer_all(pcm, + SNDRV_DMA_TYPE_DEV, + &dev->pci_dev->dev, + TW686X_AUDIO_PAGE_MAX * AUDIO_DMA_SIZE_MAX, + TW686X_AUDIO_PAGE_MAX * AUDIO_DMA_SIZE_MAX); + return 0; +} + +static void tw686x_audio_dma_free(struct tw686x_dev *dev, + struct tw686x_audio_channel *ac) +{ + int pb; + + for (pb = 0; pb < 2; pb++) { + if (!ac->dma_descs[pb].virt) + continue; + dma_free_coherent(&dev->pci_dev->dev, ac->dma_descs[pb].size, + ac->dma_descs[pb].virt, + ac->dma_descs[pb].phys); + ac->dma_descs[pb].virt = NULL; + } +} + +static int tw686x_audio_dma_alloc(struct tw686x_dev *dev, + struct tw686x_audio_channel *ac) +{ + int pb; + + /* + * In the memcpy DMA mode we allocate a coherent buffer + * and use it for the DMA capture. Otherwise, DMA + * acts on the ALSA buffers as received in pcm_prepare. + */ + if (dev->dma_mode != TW686X_DMA_MODE_MEMCPY) + return 0; + + for (pb = 0; pb < 2; pb++) { + u32 reg = pb ? ADMA_B_ADDR[ac->ch] : ADMA_P_ADDR[ac->ch]; + void *virt; + + virt = dma_alloc_coherent(&dev->pci_dev->dev, + AUDIO_DMA_SIZE_MAX, + &ac->dma_descs[pb].phys, GFP_KERNEL); + if (!virt) { + dev_err(&dev->pci_dev->dev, + "dma%d: unable to allocate audio DMA %s-buffer\n", + ac->ch, pb ? "B" : "P"); + return -ENOMEM; + } + ac->dma_descs[pb].virt = virt; + ac->dma_descs[pb].size = AUDIO_DMA_SIZE_MAX; + reg_write(dev, reg, ac->dma_descs[pb].phys); + } + return 0; +} + +void tw686x_audio_free(struct tw686x_dev *dev) +{ + unsigned long flags; + u32 dma_ch_mask; + u32 dma_cmd; + + spin_lock_irqsave(&dev->lock, flags); + dma_cmd = reg_read(dev, DMA_CMD); + dma_ch_mask = reg_read(dev, DMA_CHANNEL_ENABLE); + reg_write(dev, DMA_CMD, dma_cmd & ~0xff00); + reg_write(dev, DMA_CHANNEL_ENABLE, dma_ch_mask & ~0xff00); + spin_unlock_irqrestore(&dev->lock, flags); + + if (!dev->snd_card) + return; + snd_card_free(dev->snd_card); + dev->snd_card = NULL; +} + +int tw686x_audio_init(struct tw686x_dev *dev) +{ + struct pci_dev *pci_dev = dev->pci_dev; + struct snd_card *card; + int err, ch; + + /* Enable external audio */ + reg_write(dev, AUDIO_CONTROL1, BIT(0)); + + err = snd_card_new(&pci_dev->dev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1, + THIS_MODULE, 0, &card); + if (err < 0) + return err; + + dev->snd_card = card; + strscpy(card->driver, "tw686x", sizeof(card->driver)); + strscpy(card->shortname, "tw686x", sizeof(card->shortname)); + strscpy(card->longname, pci_name(pci_dev), sizeof(card->longname)); + snd_card_set_dev(card, &pci_dev->dev); + + for (ch = 0; ch < max_channels(dev); ch++) { + struct tw686x_audio_channel *ac; + + ac = &dev->audio_channels[ch]; + spin_lock_init(&ac->lock); + ac->dev = dev; + ac->ch = ch; + + err = tw686x_audio_dma_alloc(dev, ac); + if (err < 0) + goto err_cleanup; + } + + err = tw686x_snd_pcm_init(dev); + if (err < 0) + goto err_cleanup; + + err = snd_card_register(card); + if (!err) + return 0; + +err_cleanup: + for (ch = 0; ch < max_channels(dev); ch++) { + if (!dev->audio_channels[ch].dev) + continue; + tw686x_audio_dma_free(dev, &dev->audio_channels[ch]); + } + snd_card_free(card); + dev->snd_card = NULL; + return err; +} diff --git a/drivers/media/pci/tw686x/tw686x-core.c b/drivers/media/pci/tw686x/tw686x-core.c new file mode 100644 index 000000000..c53099c95 --- /dev/null +++ b/drivers/media/pci/tw686x/tw686x-core.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 VanguardiaSur - www.vanguardiasur.com.ar + * + * Based on original driver by Krzysztof Ha?asa: + * Copyright (C) 2015 Industrial Research Institute for Automation + * and Measurements PIAP + * + * Notes + * ----- + * + * 1. Under stress-testing, it has been observed that the PCIe link + * goes down, without reason. Therefore, the driver takes special care + * to allow device hot-unplugging. + * + * 2. TW686X devices are capable of setting a few different DMA modes, + * including: scatter-gather, field and frame modes. However, + * under stress testings it has been found that the machine can + * freeze completely if DMA registers are programmed while streaming + * is active. + * + * Therefore, driver implements a dma_mode called 'memcpy' which + * avoids cycling the DMA buffers, and insteads allocates extra DMA buffers + * and then copies into vmalloc'ed user buffers. + * + * In addition to this, when streaming is on, the driver tries to access + * hardware registers as infrequently as possible. This is done by using + * a timer to limit the rate at which DMA is reset on DMA channels error. + */ + +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci_ids.h> +#include <linux/slab.h> +#include <linux/timer.h> + +#include "tw686x.h" +#include "tw686x-regs.h" + +/* + * This module parameter allows to control the DMA_TIMER_INTERVAL value. + * The DMA_TIMER_INTERVAL register controls the minimum DMA interrupt + * time span (iow, the maximum DMA interrupt rate) thus allowing for + * IRQ coalescing. + * + * The chip datasheet does not mention a time unit for this value, so + * users wanting fine-grain control over the interrupt rate should + * determine the desired value through testing. + */ +static u32 dma_interval = 0x00098968; +module_param(dma_interval, int, 0444); +MODULE_PARM_DESC(dma_interval, "Minimum time span for DMA interrupting host"); + +static unsigned int dma_mode = TW686X_DMA_MODE_MEMCPY; +static const char *dma_mode_name(unsigned int mode) +{ + switch (mode) { + case TW686X_DMA_MODE_MEMCPY: + return "memcpy"; + case TW686X_DMA_MODE_CONTIG: + return "contig"; + case TW686X_DMA_MODE_SG: + return "sg"; + default: + return "unknown"; + } +} + +static int tw686x_dma_mode_get(char *buffer, const struct kernel_param *kp) +{ + return sprintf(buffer, "%s", dma_mode_name(dma_mode)); +} + +static int tw686x_dma_mode_set(const char *val, const struct kernel_param *kp) +{ + if (!strcasecmp(val, dma_mode_name(TW686X_DMA_MODE_MEMCPY))) + dma_mode = TW686X_DMA_MODE_MEMCPY; + else if (!strcasecmp(val, dma_mode_name(TW686X_DMA_MODE_CONTIG))) + dma_mode = TW686X_DMA_MODE_CONTIG; + else if (!strcasecmp(val, dma_mode_name(TW686X_DMA_MODE_SG))) + dma_mode = TW686X_DMA_MODE_SG; + else + return -EINVAL; + return 0; +} +module_param_call(dma_mode, tw686x_dma_mode_set, tw686x_dma_mode_get, + &dma_mode, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(dma_mode, "DMA operation mode (memcpy/contig/sg, default=memcpy)"); + +void tw686x_disable_channel(struct tw686x_dev *dev, unsigned int channel) +{ + u32 dma_en = reg_read(dev, DMA_CHANNEL_ENABLE); + u32 dma_cmd = reg_read(dev, DMA_CMD); + + dma_en &= ~BIT(channel); + dma_cmd &= ~BIT(channel); + + /* Must remove it from pending too */ + dev->pending_dma_en &= ~BIT(channel); + dev->pending_dma_cmd &= ~BIT(channel); + + /* Stop DMA if no channels are enabled */ + if (!dma_en) + dma_cmd = 0; + reg_write(dev, DMA_CHANNEL_ENABLE, dma_en); + reg_write(dev, DMA_CMD, dma_cmd); +} + +void tw686x_enable_channel(struct tw686x_dev *dev, unsigned int channel) +{ + u32 dma_en = reg_read(dev, DMA_CHANNEL_ENABLE); + u32 dma_cmd = reg_read(dev, DMA_CMD); + + dev->pending_dma_en |= dma_en | BIT(channel); + dev->pending_dma_cmd |= dma_cmd | DMA_CMD_ENABLE | BIT(channel); +} + +/* + * The purpose of this awful hack is to avoid enabling the DMA + * channels "too fast" which makes some TW686x devices very + * angry and freeze the CPU (see note 1). + */ +static void tw686x_dma_delay(struct timer_list *t) +{ + struct tw686x_dev *dev = from_timer(dev, t, dma_delay_timer); + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + + reg_write(dev, DMA_CHANNEL_ENABLE, dev->pending_dma_en); + reg_write(dev, DMA_CMD, dev->pending_dma_cmd); + dev->pending_dma_en = 0; + dev->pending_dma_cmd = 0; + + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void tw686x_reset_channels(struct tw686x_dev *dev, unsigned int ch_mask) +{ + u32 dma_en, dma_cmd; + + dma_en = reg_read(dev, DMA_CHANNEL_ENABLE); + dma_cmd = reg_read(dev, DMA_CMD); + + /* + * Save pending register status, the timer will + * restore them. + */ + dev->pending_dma_en |= dma_en; + dev->pending_dma_cmd |= dma_cmd; + + /* Disable the reset channels */ + reg_write(dev, DMA_CHANNEL_ENABLE, dma_en & ~ch_mask); + + if ((dma_en & ~ch_mask) == 0) { + dev_dbg(&dev->pci_dev->dev, "reset: stopping DMA\n"); + dma_cmd &= ~DMA_CMD_ENABLE; + } + reg_write(dev, DMA_CMD, dma_cmd & ~ch_mask); +} + +static irqreturn_t tw686x_irq(int irq, void *dev_id) +{ + struct tw686x_dev *dev = (struct tw686x_dev *)dev_id; + unsigned int video_requests, audio_requests, reset_ch; + u32 fifo_status, fifo_signal, fifo_ov, fifo_bad, fifo_errors; + u32 int_status, dma_en, video_en, pb_status; + unsigned long flags; + + int_status = reg_read(dev, INT_STATUS); /* cleared on read */ + fifo_status = reg_read(dev, VIDEO_FIFO_STATUS); + + /* INT_STATUS does not include FIFO_STATUS errors! */ + if (!int_status && !TW686X_FIFO_ERROR(fifo_status)) + return IRQ_NONE; + + if (int_status & INT_STATUS_DMA_TOUT) { + dev_dbg(&dev->pci_dev->dev, + "DMA timeout. Resetting DMA for all channels\n"); + reset_ch = ~0; + goto reset_channels; + } + + spin_lock_irqsave(&dev->lock, flags); + dma_en = reg_read(dev, DMA_CHANNEL_ENABLE); + spin_unlock_irqrestore(&dev->lock, flags); + + video_en = dma_en & 0xff; + fifo_signal = ~(fifo_status & 0xff) & video_en; + fifo_ov = fifo_status >> 24; + fifo_bad = fifo_status >> 16; + + /* Mask of channels with signal and FIFO errors */ + fifo_errors = fifo_signal & (fifo_ov | fifo_bad); + + reset_ch = 0; + pb_status = reg_read(dev, PB_STATUS); + + /* Coalesce video frame/error events */ + video_requests = (int_status & video_en) | fifo_errors; + audio_requests = (int_status & dma_en) >> 8; + + if (video_requests) + tw686x_video_irq(dev, video_requests, pb_status, + fifo_status, &reset_ch); + if (audio_requests) + tw686x_audio_irq(dev, audio_requests, pb_status); + +reset_channels: + if (reset_ch) { + spin_lock_irqsave(&dev->lock, flags); + tw686x_reset_channels(dev, reset_ch); + spin_unlock_irqrestore(&dev->lock, flags); + mod_timer(&dev->dma_delay_timer, + jiffies + msecs_to_jiffies(100)); + } + + return IRQ_HANDLED; +} + +static void tw686x_dev_release(struct v4l2_device *v4l2_dev) +{ + struct tw686x_dev *dev = container_of(v4l2_dev, struct tw686x_dev, + v4l2_dev); + unsigned int ch; + + for (ch = 0; ch < max_channels(dev); ch++) + v4l2_ctrl_handler_free(&dev->video_channels[ch].ctrl_handler); + + v4l2_device_unregister(&dev->v4l2_dev); + + kfree(dev->audio_channels); + kfree(dev->video_channels); + kfree(dev); +} + +static int tw686x_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct tw686x_dev *dev; + int err; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + dev->type = pci_id->driver_data; + dev->dma_mode = dma_mode; + sprintf(dev->name, "tw%04X", pci_dev->device); + + dev->video_channels = kcalloc(max_channels(dev), + sizeof(*dev->video_channels), GFP_KERNEL); + if (!dev->video_channels) { + err = -ENOMEM; + goto free_dev; + } + + dev->audio_channels = kcalloc(max_channels(dev), + sizeof(*dev->audio_channels), GFP_KERNEL); + if (!dev->audio_channels) { + err = -ENOMEM; + goto free_video; + } + + pr_info("%s: PCI %s, IRQ %d, MMIO 0x%lx (%s mode)\n", dev->name, + pci_name(pci_dev), pci_dev->irq, + (unsigned long)pci_resource_start(pci_dev, 0), + dma_mode_name(dma_mode)); + + dev->pci_dev = pci_dev; + if (pci_enable_device(pci_dev)) { + err = -EIO; + goto free_audio; + } + + pci_set_master(pci_dev); + err = dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(32)); + if (err) { + dev_err(&pci_dev->dev, "32-bit PCI DMA not supported\n"); + err = -EIO; + goto disable_pci; + } + + err = pci_request_regions(pci_dev, dev->name); + if (err) { + dev_err(&pci_dev->dev, "unable to request PCI region\n"); + goto disable_pci; + } + + dev->mmio = pci_ioremap_bar(pci_dev, 0); + if (!dev->mmio) { + dev_err(&pci_dev->dev, "unable to remap PCI region\n"); + err = -ENOMEM; + goto free_region; + } + + /* Reset all subsystems */ + reg_write(dev, SYS_SOFT_RST, 0x0f); + mdelay(1); + + reg_write(dev, SRST[0], 0x3f); + if (max_channels(dev) > 4) + reg_write(dev, SRST[1], 0x3f); + + /* Disable the DMA engine */ + reg_write(dev, DMA_CMD, 0); + reg_write(dev, DMA_CHANNEL_ENABLE, 0); + + /* Enable DMA FIFO overflow and pointer check */ + reg_write(dev, DMA_CONFIG, 0xffffff04); + reg_write(dev, DMA_CHANNEL_TIMEOUT, 0x140c8584); + reg_write(dev, DMA_TIMER_INTERVAL, dma_interval); + + spin_lock_init(&dev->lock); + + timer_setup(&dev->dma_delay_timer, tw686x_dma_delay, 0); + + /* + * This must be set right before initializing v4l2_dev. + * It's used to release resources after the last handle + * held is released. + */ + dev->v4l2_dev.release = tw686x_dev_release; + err = tw686x_video_init(dev); + if (err) { + dev_err(&pci_dev->dev, "can't register video\n"); + goto iounmap; + } + + err = tw686x_audio_init(dev); + if (err) + dev_warn(&pci_dev->dev, "can't register audio\n"); + + err = request_irq(pci_dev->irq, tw686x_irq, IRQF_SHARED, + dev->name, dev); + if (err < 0) { + dev_err(&pci_dev->dev, "unable to request interrupt\n"); + goto tw686x_free; + } + + pci_set_drvdata(pci_dev, dev); + return 0; + +tw686x_free: + tw686x_video_free(dev); + tw686x_audio_free(dev); +iounmap: + pci_iounmap(pci_dev, dev->mmio); +free_region: + pci_release_regions(pci_dev); +disable_pci: + pci_disable_device(pci_dev); +free_audio: + kfree(dev->audio_channels); +free_video: + kfree(dev->video_channels); +free_dev: + kfree(dev); + return err; +} + +static void tw686x_remove(struct pci_dev *pci_dev) +{ + struct tw686x_dev *dev = pci_get_drvdata(pci_dev); + unsigned long flags; + + /* This guarantees the IRQ handler is no longer running, + * which means we can kiss good-bye some resources. + */ + free_irq(pci_dev->irq, dev); + + tw686x_video_free(dev); + tw686x_audio_free(dev); + del_timer_sync(&dev->dma_delay_timer); + + pci_iounmap(pci_dev, dev->mmio); + pci_release_regions(pci_dev); + pci_disable_device(pci_dev); + + /* + * Setting pci_dev to NULL allows to detect hardware is no longer + * available and will be used by vb2_ops. This is required because + * the device sometimes hot-unplugs itself as the result of a PCIe + * link down. + * The lock is really important here. + */ + spin_lock_irqsave(&dev->lock, flags); + dev->pci_dev = NULL; + spin_unlock_irqrestore(&dev->lock, flags); + + /* + * This calls tw686x_dev_release if it's the last reference. + * Otherwise, release is postponed until there are no users left. + */ + v4l2_device_put(&dev->v4l2_dev); +} + +/* + * On TW6864 and TW6868, all channels share the pair of video DMA SG tables, + * with 10-bit start_idx and end_idx determining start and end of frame buffer + * for particular channel. + * TW6868 with all its 8 channels would be problematic (only 127 SG entries per + * channel) but we support only 4 channels on this chip anyway (the first + * 4 channels are driven with internal video decoder, the other 4 would require + * an external TW286x part). + * + * On TW6865 and TW6869, each channel has its own DMA SG table, with indexes + * starting with 0. Both chips have complete sets of internal video decoders + * (respectively 4 or 8-channel). + * + * All chips have separate SG tables for two video frames. + */ + +/* driver_data is number of A/V channels */ +static const struct pci_device_id tw686x_pci_tbl[] = { + { + PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, 0x6864), + .driver_data = 4 + }, + { + PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, 0x6865), /* not tested */ + .driver_data = 4 | TYPE_SECOND_GEN + }, + /* + * TW6868 supports 8 A/V channels with an external TW2865 chip; + * not supported by the driver. + */ + { + PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, 0x6868), /* not tested */ + .driver_data = 4 + }, + { + PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, 0x6869), + .driver_data = 8 | TYPE_SECOND_GEN}, + {} +}; +MODULE_DEVICE_TABLE(pci, tw686x_pci_tbl); + +static struct pci_driver tw686x_pci_driver = { + .name = "tw686x", + .id_table = tw686x_pci_tbl, + .probe = tw686x_probe, + .remove = tw686x_remove, +}; +module_pci_driver(tw686x_pci_driver); + +MODULE_DESCRIPTION("Driver for video frame grabber cards based on Intersil/Techwell TW686[4589]"); +MODULE_AUTHOR("Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>"); +MODULE_AUTHOR("Krzysztof Ha?asa <khalasa@piap.pl>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/pci/tw686x/tw686x-regs.h b/drivers/media/pci/tw686x/tw686x-regs.h new file mode 100644 index 000000000..8adacc928 --- /dev/null +++ b/drivers/media/pci/tw686x/tw686x-regs.h @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* DMA controller registers */ +#define REG8_1(a0) ((const u16[8]) { a0, a0 + 1, a0 + 2, a0 + 3, \ + a0 + 4, a0 + 5, a0 + 6, a0 + 7}) +#define REG8_2(a0) ((const u16[8]) { a0, a0 + 2, a0 + 4, a0 + 6, \ + a0 + 8, a0 + 0xa, a0 + 0xc, a0 + 0xe}) +#define REG8_8(a0) ((const u16[8]) { a0, a0 + 8, a0 + 0x10, a0 + 0x18, \ + a0 + 0x20, a0 + 0x28, a0 + 0x30, \ + a0 + 0x38}) +#define INT_STATUS 0x00 +#define PB_STATUS 0x01 +#define DMA_CMD 0x02 +#define VIDEO_FIFO_STATUS 0x03 +#define VIDEO_CHANNEL_ID 0x04 +#define VIDEO_PARSER_STATUS 0x05 +#define SYS_SOFT_RST 0x06 +#define DMA_PAGE_TABLE0_ADDR ((const u16[8]) { 0x08, 0xd0, 0xd2, 0xd4, \ + 0xd6, 0xd8, 0xda, 0xdc }) +#define DMA_PAGE_TABLE1_ADDR ((const u16[8]) { 0x09, 0xd1, 0xd3, 0xd5, \ + 0xd7, 0xd9, 0xdb, 0xdd }) +#define DMA_CHANNEL_ENABLE 0x0a +#define DMA_CONFIG 0x0b +#define DMA_TIMER_INTERVAL 0x0c +#define DMA_CHANNEL_TIMEOUT 0x0d +#define VDMA_CHANNEL_CONFIG REG8_1(0x10) +#define ADMA_P_ADDR REG8_2(0x18) +#define ADMA_B_ADDR REG8_2(0x19) +#define DMA10_P_ADDR 0x28 +#define DMA10_B_ADDR 0x29 +#define VIDEO_CONTROL1 0x2a +#define VIDEO_CONTROL2 0x2b +#define AUDIO_CONTROL1 0x2c +#define AUDIO_CONTROL2 0x2d +#define PHASE_REF 0x2e +#define GPIO_REG 0x2f +#define INTL_HBAR_CTRL REG8_1(0x30) +#define AUDIO_CONTROL3 0x38 +#define VIDEO_FIELD_CTRL REG8_1(0x39) +#define HSCALER_CTRL REG8_1(0x42) +#define VIDEO_SIZE REG8_1(0x4A) +#define VIDEO_SIZE_F2 REG8_1(0x52) +#define MD_CONF REG8_1(0x60) +#define MD_INIT REG8_1(0x68) +#define MD_MAP0 REG8_1(0x70) +#define VDMA_P_ADDR REG8_8(0x80) /* not used in DMA SG mode */ +#define VDMA_WHP REG8_8(0x81) +#define VDMA_B_ADDR REG8_8(0x82) +#define VDMA_F2_P_ADDR REG8_8(0x84) +#define VDMA_F2_WHP REG8_8(0x85) +#define VDMA_F2_B_ADDR REG8_8(0x86) +#define EP_REG_ADDR 0xfe +#define EP_REG_DATA 0xff + +/* Video decoder registers */ +#define VDREG8(a0) ((const u16[8]) { \ + a0 + 0x000, a0 + 0x010, a0 + 0x020, a0 + 0x030, \ + a0 + 0x100, a0 + 0x110, a0 + 0x120, a0 + 0x130}) +#define VIDSTAT VDREG8(0x100) +#define BRIGHT VDREG8(0x101) +#define CONTRAST VDREG8(0x102) +#define SHARPNESS VDREG8(0x103) +#define SAT_U VDREG8(0x104) +#define SAT_V VDREG8(0x105) +#define HUE VDREG8(0x106) +#define CROP_HI VDREG8(0x107) +#define VDELAY_LO VDREG8(0x108) +#define VACTIVE_LO VDREG8(0x109) +#define HDELAY_LO VDREG8(0x10a) +#define HACTIVE_LO VDREG8(0x10b) +#define MVSN VDREG8(0x10c) +#define STATUS2 VDREG8(0x10d) +#define SDT VDREG8(0x10e) +#define SDT_EN VDREG8(0x10f) + +#define VSCALE_LO VDREG8(0x144) +#define SCALE_HI VDREG8(0x145) +#define HSCALE_LO VDREG8(0x146) +#define F2CROP_HI VDREG8(0x147) +#define F2VDELAY_LO VDREG8(0x148) +#define F2VACTIVE_LO VDREG8(0x149) +#define F2HDELAY_LO VDREG8(0x14a) +#define F2HACTIVE_LO VDREG8(0x14b) +#define F2VSCALE_LO VDREG8(0x14c) +#define F2SCALE_HI VDREG8(0x14d) +#define F2HSCALE_LO VDREG8(0x14e) +#define F2CNT VDREG8(0x14f) + +#define VDREG2(a0) ((const u16[2]) { a0, a0 + 0x100 }) +#define SRST VDREG2(0x180) +#define ACNTL VDREG2(0x181) +#define ACNTL2 VDREG2(0x182) +#define CNTRL1 VDREG2(0x183) +#define CKHY VDREG2(0x184) +#define SHCOR VDREG2(0x185) +#define CORING VDREG2(0x186) +#define CLMPG VDREG2(0x187) +#define IAGC VDREG2(0x188) +#define VCTRL1 VDREG2(0x18f) +#define MISC1 VDREG2(0x194) +#define LOOP VDREG2(0x195) +#define MISC2 VDREG2(0x196) + +#define CLMD VDREG2(0x197) +#define ANPWRDOWN VDREG2(0x1ce) +#define AIGAIN ((const u16[8]) { 0x1d0, 0x1d1, 0x1d2, 0x1d3, \ + 0x2d0, 0x2d1, 0x2d2, 0x2d3 }) + +#define SYS_MODE_DMA_SHIFT 13 +#define AUDIO_DMA_SIZE_SHIFT 19 +#define AUDIO_DMA_SIZE_MIN SZ_512 +#define AUDIO_DMA_SIZE_MAX SZ_4K +#define AUDIO_DMA_SIZE_MASK (SZ_8K - 1) + +#define DMA_CMD_ENABLE BIT(31) +#define INT_STATUS_DMA_TOUT BIT(17) +#define TW686X_VIDSTAT_HLOCK BIT(6) +#define TW686X_VIDSTAT_VDLOSS BIT(7) + +#define TW686X_STD_NTSC_M 0 +#define TW686X_STD_PAL 1 +#define TW686X_STD_SECAM 2 +#define TW686X_STD_NTSC_443 3 +#define TW686X_STD_PAL_M 4 +#define TW686X_STD_PAL_CN 5 +#define TW686X_STD_PAL_60 6 + +#define TW686X_FIELD_MODE 0x3 +#define TW686X_FRAME_MODE 0x2 +/* 0x1 is reserved */ +#define TW686X_SG_MODE 0x0 + +#define TW686X_FIFO_ERROR(x) (x & ~(0xff)) diff --git a/drivers/media/pci/tw686x/tw686x-video.c b/drivers/media/pci/tw686x/tw686x-video.c new file mode 100644 index 000000000..3ebf7a2c9 --- /dev/null +++ b/drivers/media/pci/tw686x/tw686x-video.c @@ -0,0 +1,1308 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 VanguardiaSur - www.vanguardiasur.com.ar + * + * Based on original driver by Krzysztof Ha?asa: + * Copyright (C) 2015 Industrial Research Institute for Automation + * and Measurements PIAP + */ + +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <media/v4l2-common.h> +#include <media/v4l2-event.h> +#include <media/videobuf2-dma-contig.h> +#include <media/videobuf2-dma-sg.h> +#include <media/videobuf2-vmalloc.h> +#include "tw686x.h" +#include "tw686x-regs.h" + +#define TW686X_INPUTS_PER_CH 4 +#define TW686X_VIDEO_WIDTH 720 +#define TW686X_VIDEO_HEIGHT(id) ((id & V4L2_STD_525_60) ? 480 : 576) +#define TW686X_MAX_FPS(id) ((id & V4L2_STD_525_60) ? 30 : 25) + +#define TW686X_MAX_SG_ENTRY_SIZE 4096 +#define TW686X_MAX_SG_DESC_COUNT 256 /* PAL 720x576 needs 203 4-KB pages */ +#define TW686X_SG_TABLE_SIZE (TW686X_MAX_SG_DESC_COUNT * sizeof(struct tw686x_sg_desc)) + +static const struct tw686x_format formats[] = { + { + .fourcc = V4L2_PIX_FMT_UYVY, + .mode = 0, + .depth = 16, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .mode = 5, + .depth = 16, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .mode = 6, + .depth = 16, + } +}; + +static void tw686x_buf_done(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; + struct tw686x_dev *dev = vc->dev; + struct vb2_v4l2_buffer *vb; + struct vb2_buffer *vb2_buf; + + if (vc->curr_bufs[pb]) { + vb = &vc->curr_bufs[pb]->vb; + + vb->field = dev->dma_ops->field; + vb->sequence = vc->sequence++; + vb2_buf = &vb->vb2_buf; + + if (dev->dma_mode == TW686X_DMA_MODE_MEMCPY) + memcpy(vb2_plane_vaddr(vb2_buf, 0), desc->virt, + desc->size); + vb2_buf->timestamp = ktime_get_ns(); + vb2_buffer_done(vb2_buf, VB2_BUF_STATE_DONE); + } + + vc->pb = !pb; +} + +/* + * We can call this even when alloc_dma failed for the given channel + */ +static void tw686x_memcpy_dma_free(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; + struct tw686x_dev *dev = vc->dev; + struct pci_dev *pci_dev; + unsigned long flags; + + /* Check device presence. Shouldn't really happen! */ + spin_lock_irqsave(&dev->lock, flags); + pci_dev = dev->pci_dev; + spin_unlock_irqrestore(&dev->lock, flags); + if (!pci_dev) { + WARN(1, "trying to deallocate on missing device\n"); + return; + } + + if (desc->virt) { + dma_free_coherent(&dev->pci_dev->dev, desc->size, desc->virt, + desc->phys); + desc->virt = NULL; + } +} + +static int tw686x_memcpy_dma_alloc(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dev *dev = vc->dev; + u32 reg = pb ? VDMA_B_ADDR[vc->ch] : VDMA_P_ADDR[vc->ch]; + unsigned int len; + void *virt; + + WARN(vc->dma_descs[pb].virt, + "Allocating buffer but previous still here\n"); + + len = (vc->width * vc->height * vc->format->depth) >> 3; + virt = dma_alloc_coherent(&dev->pci_dev->dev, len, + &vc->dma_descs[pb].phys, GFP_KERNEL); + if (!virt) { + v4l2_err(&dev->v4l2_dev, + "dma%d: unable to allocate %s-buffer\n", + vc->ch, pb ? "B" : "P"); + return -ENOMEM; + } + vc->dma_descs[pb].size = len; + vc->dma_descs[pb].virt = virt; + reg_write(dev, reg, vc->dma_descs[pb].phys); + + return 0; +} + +static void tw686x_memcpy_buf_refill(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_v4l2_buf *buf; + + while (!list_empty(&vc->vidq_queued)) { + + buf = list_first_entry(&vc->vidq_queued, + struct tw686x_v4l2_buf, list); + list_del(&buf->list); + + vc->curr_bufs[pb] = buf; + return; + } + vc->curr_bufs[pb] = NULL; +} + +static const struct tw686x_dma_ops memcpy_dma_ops = { + .alloc = tw686x_memcpy_dma_alloc, + .free = tw686x_memcpy_dma_free, + .buf_refill = tw686x_memcpy_buf_refill, + .mem_ops = &vb2_vmalloc_memops, + .hw_dma_mode = TW686X_FRAME_MODE, + .field = V4L2_FIELD_INTERLACED, +}; + +static void tw686x_contig_buf_refill(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_v4l2_buf *buf; + + while (!list_empty(&vc->vidq_queued)) { + u32 reg = pb ? VDMA_B_ADDR[vc->ch] : VDMA_P_ADDR[vc->ch]; + dma_addr_t phys; + + buf = list_first_entry(&vc->vidq_queued, + struct tw686x_v4l2_buf, list); + list_del(&buf->list); + + phys = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + reg_write(vc->dev, reg, phys); + + buf->vb.vb2_buf.state = VB2_BUF_STATE_ACTIVE; + vc->curr_bufs[pb] = buf; + return; + } + vc->curr_bufs[pb] = NULL; +} + +static const struct tw686x_dma_ops contig_dma_ops = { + .buf_refill = tw686x_contig_buf_refill, + .mem_ops = &vb2_dma_contig_memops, + .hw_dma_mode = TW686X_FRAME_MODE, + .field = V4L2_FIELD_INTERLACED, +}; + +static int tw686x_sg_desc_fill(struct tw686x_sg_desc *descs, + struct tw686x_v4l2_buf *buf, + unsigned int buf_len) +{ + struct sg_table *vbuf = vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0); + unsigned int len, entry_len; + struct scatterlist *sg; + int i, count; + + /* Clear the scatter-gather table */ + memset(descs, 0, TW686X_SG_TABLE_SIZE); + + count = 0; + for_each_sg(vbuf->sgl, sg, vbuf->nents, i) { + dma_addr_t phys = sg_dma_address(sg); + len = sg_dma_len(sg); + + while (len && buf_len) { + + if (count == TW686X_MAX_SG_DESC_COUNT) + return -ENOMEM; + + entry_len = min_t(unsigned int, len, + TW686X_MAX_SG_ENTRY_SIZE); + entry_len = min_t(unsigned int, entry_len, buf_len); + descs[count].phys = cpu_to_le32(phys); + descs[count++].flags_length = + cpu_to_le32(BIT(30) | entry_len); + phys += entry_len; + len -= entry_len; + buf_len -= entry_len; + } + + if (!buf_len) + return 0; + } + + return -ENOMEM; +} + +static void tw686x_sg_buf_refill(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dev *dev = vc->dev; + struct tw686x_v4l2_buf *buf; + + while (!list_empty(&vc->vidq_queued)) { + unsigned int buf_len; + + buf = list_first_entry(&vc->vidq_queued, + struct tw686x_v4l2_buf, list); + list_del(&buf->list); + + buf_len = (vc->width * vc->height * vc->format->depth) >> 3; + if (tw686x_sg_desc_fill(vc->sg_descs[pb], buf, buf_len)) { + v4l2_err(&dev->v4l2_dev, + "dma%d: unable to fill %s-buffer\n", + vc->ch, pb ? "B" : "P"); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + continue; + } + + buf->vb.vb2_buf.state = VB2_BUF_STATE_ACTIVE; + vc->curr_bufs[pb] = buf; + return; + } + + vc->curr_bufs[pb] = NULL; +} + +static void tw686x_sg_dma_free(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; + struct tw686x_dev *dev = vc->dev; + + if (desc->size) { + dma_free_coherent(&dev->pci_dev->dev, desc->size, desc->virt, + desc->phys); + desc->virt = NULL; + } + + vc->sg_descs[pb] = NULL; +} + +static int tw686x_sg_dma_alloc(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; + struct tw686x_dev *dev = vc->dev; + u32 reg = pb ? DMA_PAGE_TABLE1_ADDR[vc->ch] : + DMA_PAGE_TABLE0_ADDR[vc->ch]; + void *virt; + + if (desc->size) { + virt = dma_alloc_coherent(&dev->pci_dev->dev, desc->size, + &desc->phys, GFP_KERNEL); + if (!virt) { + v4l2_err(&dev->v4l2_dev, + "dma%d: unable to allocate %s-buffer\n", + vc->ch, pb ? "B" : "P"); + return -ENOMEM; + } + desc->virt = virt; + reg_write(dev, reg, desc->phys); + } else { + virt = dev->video_channels[0].dma_descs[pb].virt + + vc->ch * TW686X_SG_TABLE_SIZE; + } + + vc->sg_descs[pb] = virt; + return 0; +} + +static int tw686x_sg_setup(struct tw686x_dev *dev) +{ + unsigned int sg_table_size, pb, ch, channels; + + if (is_second_gen(dev)) { + /* + * TW6865/TW6869: each channel needs a pair of + * P-B descriptor tables. + */ + channels = max_channels(dev); + sg_table_size = TW686X_SG_TABLE_SIZE; + } else { + /* + * TW6864/TW6868: we need to allocate a pair of + * P-B descriptor tables, common for all channels. + * Each table will be bigger than 4 KB. + */ + channels = 1; + sg_table_size = max_channels(dev) * TW686X_SG_TABLE_SIZE; + } + + for (ch = 0; ch < channels; ch++) { + struct tw686x_video_channel *vc = &dev->video_channels[ch]; + + for (pb = 0; pb < 2; pb++) + vc->dma_descs[pb].size = sg_table_size; + } + + return 0; +} + +static const struct tw686x_dma_ops sg_dma_ops = { + .setup = tw686x_sg_setup, + .alloc = tw686x_sg_dma_alloc, + .free = tw686x_sg_dma_free, + .buf_refill = tw686x_sg_buf_refill, + .mem_ops = &vb2_dma_sg_memops, + .hw_dma_mode = TW686X_SG_MODE, + .field = V4L2_FIELD_SEQ_TB, +}; + +static const unsigned int fps_map[15] = { + /* + * bit 31 enables selecting the field control register + * bits 0-29 are a bitmask with fields that will be output. + * For NTSC (and PAL-M, PAL-60), all 30 bits are used. + * For other PAL standards, only the first 25 bits are used. + */ + 0x00000000, /* output all fields */ + 0x80000006, /* 2 fps (60Hz), 2 fps (50Hz) */ + 0x80018006, /* 4 fps (60Hz), 4 fps (50Hz) */ + 0x80618006, /* 6 fps (60Hz), 6 fps (50Hz) */ + 0x81818186, /* 8 fps (60Hz), 8 fps (50Hz) */ + 0x86186186, /* 10 fps (60Hz), 8 fps (50Hz) */ + 0x86619866, /* 12 fps (60Hz), 10 fps (50Hz) */ + 0x86666666, /* 14 fps (60Hz), 12 fps (50Hz) */ + 0x9999999e, /* 16 fps (60Hz), 14 fps (50Hz) */ + 0x99e6799e, /* 18 fps (60Hz), 16 fps (50Hz) */ + 0x9e79e79e, /* 20 fps (60Hz), 16 fps (50Hz) */ + 0x9e7e7e7e, /* 22 fps (60Hz), 18 fps (50Hz) */ + 0x9fe7f9fe, /* 24 fps (60Hz), 20 fps (50Hz) */ + 0x9ffe7ffe, /* 26 fps (60Hz), 22 fps (50Hz) */ + 0x9ffffffe, /* 28 fps (60Hz), 24 fps (50Hz) */ +}; + +static unsigned int tw686x_real_fps(unsigned int index, unsigned int max_fps) +{ + unsigned long mask; + + if (!index || index >= ARRAY_SIZE(fps_map)) + return max_fps; + + mask = GENMASK(max_fps - 1, 0); + return hweight_long(fps_map[index] & mask); +} + +static unsigned int tw686x_fps_idx(unsigned int fps, unsigned int max_fps) +{ + unsigned int idx, real_fps; + int delta; + + /* First guess */ + idx = (12 + 15 * fps) / max_fps; + + /* Minimal possible framerate is 2 frames per second */ + if (!idx) + return 1; + + /* Check if the difference is bigger than abs(1) and adjust */ + real_fps = tw686x_real_fps(idx, max_fps); + delta = real_fps - fps; + if (delta < -1) + idx++; + else if (delta > 1) + idx--; + + /* Max framerate */ + if (idx >= 15) + return 0; + + return idx; +} + +static void tw686x_set_framerate(struct tw686x_video_channel *vc, + unsigned int fps) +{ + unsigned int i; + + i = tw686x_fps_idx(fps, TW686X_MAX_FPS(vc->video_standard)); + reg_write(vc->dev, VIDEO_FIELD_CTRL[vc->ch], fps_map[i]); + vc->fps = tw686x_real_fps(i, TW686X_MAX_FPS(vc->video_standard)); +} + +static const struct tw686x_format *format_by_fourcc(unsigned int fourcc) +{ + unsigned int cnt; + + for (cnt = 0; cnt < ARRAY_SIZE(formats); cnt++) + if (formats[cnt].fourcc == fourcc) + return &formats[cnt]; + return NULL; +} + +static int tw686x_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct tw686x_video_channel *vc = vb2_get_drv_priv(vq); + unsigned int szimage = + (vc->width * vc->height * vc->format->depth) >> 3; + + /* + * Let's request at least three buffers: two for the + * DMA engine and one for userspace. + */ + if (vq->num_buffers + *nbuffers < 3) + *nbuffers = 3 - vq->num_buffers; + + if (*nplanes) { + if (*nplanes != 1 || sizes[0] < szimage) + return -EINVAL; + return 0; + } + + sizes[0] = szimage; + *nplanes = 1; + return 0; +} + +static void tw686x_buf_queue(struct vb2_buffer *vb) +{ + struct tw686x_video_channel *vc = vb2_get_drv_priv(vb->vb2_queue); + struct tw686x_dev *dev = vc->dev; + struct pci_dev *pci_dev; + unsigned long flags; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct tw686x_v4l2_buf *buf = + container_of(vbuf, struct tw686x_v4l2_buf, vb); + + /* Check device presence */ + spin_lock_irqsave(&dev->lock, flags); + pci_dev = dev->pci_dev; + spin_unlock_irqrestore(&dev->lock, flags); + if (!pci_dev) { + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + return; + } + + spin_lock_irqsave(&vc->qlock, flags); + list_add_tail(&buf->list, &vc->vidq_queued); + spin_unlock_irqrestore(&vc->qlock, flags); +} + +static void tw686x_clear_queue(struct tw686x_video_channel *vc, + enum vb2_buffer_state state) +{ + unsigned int pb; + + while (!list_empty(&vc->vidq_queued)) { + struct tw686x_v4l2_buf *buf; + + buf = list_first_entry(&vc->vidq_queued, + struct tw686x_v4l2_buf, list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, state); + } + + for (pb = 0; pb < 2; pb++) { + if (vc->curr_bufs[pb]) + vb2_buffer_done(&vc->curr_bufs[pb]->vb.vb2_buf, state); + vc->curr_bufs[pb] = NULL; + } +} + +static int tw686x_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct tw686x_video_channel *vc = vb2_get_drv_priv(vq); + struct tw686x_dev *dev = vc->dev; + struct pci_dev *pci_dev; + unsigned long flags; + int pb, err; + + /* Check device presence */ + spin_lock_irqsave(&dev->lock, flags); + pci_dev = dev->pci_dev; + spin_unlock_irqrestore(&dev->lock, flags); + if (!pci_dev) { + err = -ENODEV; + goto err_clear_queue; + } + + spin_lock_irqsave(&vc->qlock, flags); + + /* Sanity check */ + if (dev->dma_mode == TW686X_DMA_MODE_MEMCPY && + (!vc->dma_descs[0].virt || !vc->dma_descs[1].virt)) { + spin_unlock_irqrestore(&vc->qlock, flags); + v4l2_err(&dev->v4l2_dev, + "video%d: refusing to start without DMA buffers\n", + vc->num); + err = -ENOMEM; + goto err_clear_queue; + } + + for (pb = 0; pb < 2; pb++) + dev->dma_ops->buf_refill(vc, pb); + spin_unlock_irqrestore(&vc->qlock, flags); + + vc->sequence = 0; + vc->pb = 0; + + spin_lock_irqsave(&dev->lock, flags); + tw686x_enable_channel(dev, vc->ch); + spin_unlock_irqrestore(&dev->lock, flags); + + mod_timer(&dev->dma_delay_timer, jiffies + msecs_to_jiffies(100)); + + return 0; + +err_clear_queue: + spin_lock_irqsave(&vc->qlock, flags); + tw686x_clear_queue(vc, VB2_BUF_STATE_QUEUED); + spin_unlock_irqrestore(&vc->qlock, flags); + return err; +} + +static void tw686x_stop_streaming(struct vb2_queue *vq) +{ + struct tw686x_video_channel *vc = vb2_get_drv_priv(vq); + struct tw686x_dev *dev = vc->dev; + struct pci_dev *pci_dev; + unsigned long flags; + + /* Check device presence */ + spin_lock_irqsave(&dev->lock, flags); + pci_dev = dev->pci_dev; + spin_unlock_irqrestore(&dev->lock, flags); + if (pci_dev) + tw686x_disable_channel(dev, vc->ch); + + spin_lock_irqsave(&vc->qlock, flags); + tw686x_clear_queue(vc, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&vc->qlock, flags); +} + +static int tw686x_buf_prepare(struct vb2_buffer *vb) +{ + struct tw686x_video_channel *vc = vb2_get_drv_priv(vb->vb2_queue); + unsigned int size = + (vc->width * vc->height * vc->format->depth) >> 3; + + if (vb2_plane_size(vb, 0) < size) + return -EINVAL; + vb2_set_plane_payload(vb, 0, size); + return 0; +} + +static const struct vb2_ops tw686x_video_qops = { + .queue_setup = tw686x_queue_setup, + .buf_queue = tw686x_buf_queue, + .buf_prepare = tw686x_buf_prepare, + .start_streaming = tw686x_start_streaming, + .stop_streaming = tw686x_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int tw686x_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct tw686x_video_channel *vc; + struct tw686x_dev *dev; + unsigned int ch; + + vc = container_of(ctrl->handler, struct tw686x_video_channel, + ctrl_handler); + dev = vc->dev; + ch = vc->ch; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + reg_write(dev, BRIGHT[ch], ctrl->val & 0xff); + return 0; + + case V4L2_CID_CONTRAST: + reg_write(dev, CONTRAST[ch], ctrl->val); + return 0; + + case V4L2_CID_SATURATION: + reg_write(dev, SAT_U[ch], ctrl->val); + reg_write(dev, SAT_V[ch], ctrl->val); + return 0; + + case V4L2_CID_HUE: + reg_write(dev, HUE[ch], ctrl->val & 0xff); + return 0; + } + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops ctrl_ops = { + .s_ctrl = tw686x_s_ctrl, +}; + +static int tw686x_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct tw686x_dev *dev = vc->dev; + + f->fmt.pix.width = vc->width; + f->fmt.pix.height = vc->height; + f->fmt.pix.field = dev->dma_ops->field; + f->fmt.pix.pixelformat = vc->format->fourcc; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + f->fmt.pix.bytesperline = (f->fmt.pix.width * vc->format->depth) / 8; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + return 0; +} + +static int tw686x_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct tw686x_dev *dev = vc->dev; + unsigned int video_height = TW686X_VIDEO_HEIGHT(vc->video_standard); + const struct tw686x_format *format; + + format = format_by_fourcc(f->fmt.pix.pixelformat); + if (!format) { + format = &formats[0]; + f->fmt.pix.pixelformat = format->fourcc; + } + + if (f->fmt.pix.width <= TW686X_VIDEO_WIDTH / 2) + f->fmt.pix.width = TW686X_VIDEO_WIDTH / 2; + else + f->fmt.pix.width = TW686X_VIDEO_WIDTH; + + if (f->fmt.pix.height <= video_height / 2) + f->fmt.pix.height = video_height / 2; + else + f->fmt.pix.height = video_height; + + f->fmt.pix.bytesperline = (f->fmt.pix.width * format->depth) / 8; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + f->fmt.pix.field = dev->dma_ops->field; + + return 0; +} + +static int tw686x_set_format(struct tw686x_video_channel *vc, + unsigned int pixelformat, unsigned int width, + unsigned int height, bool realloc) +{ + struct tw686x_dev *dev = vc->dev; + u32 val, dma_width, dma_height, dma_line_width; + int err, pb; + + vc->format = format_by_fourcc(pixelformat); + vc->width = width; + vc->height = height; + + /* We need new DMA buffers if the framesize has changed */ + if (dev->dma_ops->alloc && realloc) { + for (pb = 0; pb < 2; pb++) + dev->dma_ops->free(vc, pb); + + for (pb = 0; pb < 2; pb++) { + err = dev->dma_ops->alloc(vc, pb); + if (err) { + if (pb > 0) + dev->dma_ops->free(vc, 0); + return err; + } + } + } + + val = reg_read(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch]); + + if (vc->width <= TW686X_VIDEO_WIDTH / 2) + val |= BIT(23); + else + val &= ~BIT(23); + + if (vc->height <= TW686X_VIDEO_HEIGHT(vc->video_standard) / 2) + val |= BIT(24); + else + val &= ~BIT(24); + + val &= ~0x7ffff; + + /* Program the DMA scatter-gather */ + if (dev->dma_mode == TW686X_DMA_MODE_SG) { + u32 start_idx, end_idx; + + start_idx = is_second_gen(dev) ? + 0 : vc->ch * TW686X_MAX_SG_DESC_COUNT; + end_idx = start_idx + TW686X_MAX_SG_DESC_COUNT - 1; + + val |= (end_idx << 10) | start_idx; + } + + val &= ~(0x7 << 20); + val |= vc->format->mode << 20; + reg_write(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch], val); + + /* Program the DMA frame size */ + dma_width = (vc->width * 2) & 0x7ff; + dma_height = vc->height / 2; + dma_line_width = (vc->width * 2) & 0x7ff; + val = (dma_height << 22) | (dma_line_width << 11) | dma_width; + reg_write(vc->dev, VDMA_WHP[vc->ch], val); + return 0; +} + +static int tw686x_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + unsigned long area; + bool realloc; + int err; + + if (vb2_is_busy(&vc->vidq)) + return -EBUSY; + + area = vc->width * vc->height; + err = tw686x_try_fmt_vid_cap(file, priv, f); + if (err) + return err; + + realloc = area != (f->fmt.pix.width * f->fmt.pix.height); + return tw686x_set_format(vc, f->fmt.pix.pixelformat, + f->fmt.pix.width, f->fmt.pix.height, + realloc); +} + +static int tw686x_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct tw686x_dev *dev = vc->dev; + + strscpy(cap->driver, "tw686x", sizeof(cap->driver)); + strscpy(cap->card, dev->name, sizeof(cap->card)); + return 0; +} + +static int tw686x_set_standard(struct tw686x_video_channel *vc, v4l2_std_id id) +{ + u32 val; + + if (id & V4L2_STD_NTSC) + val = 0; + else if (id & V4L2_STD_PAL) + val = 1; + else if (id & V4L2_STD_SECAM) + val = 2; + else if (id & V4L2_STD_NTSC_443) + val = 3; + else if (id & V4L2_STD_PAL_M) + val = 4; + else if (id & V4L2_STD_PAL_Nc) + val = 5; + else if (id & V4L2_STD_PAL_60) + val = 6; + else + return -EINVAL; + + vc->video_standard = id; + reg_write(vc->dev, SDT[vc->ch], val); + + val = reg_read(vc->dev, VIDEO_CONTROL1); + if (id & V4L2_STD_525_60) + val &= ~(1 << (SYS_MODE_DMA_SHIFT + vc->ch)); + else + val |= (1 << (SYS_MODE_DMA_SHIFT + vc->ch)); + reg_write(vc->dev, VIDEO_CONTROL1, val); + + return 0; +} + +static int tw686x_s_std(struct file *file, void *priv, v4l2_std_id id) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct v4l2_format f; + int ret; + + if (vc->video_standard == id) + return 0; + + if (vb2_is_busy(&vc->vidq)) + return -EBUSY; + + ret = tw686x_set_standard(vc, id); + if (ret) + return ret; + /* + * Adjust format after V4L2_STD_525_60/V4L2_STD_625_50 change, + * calling g_fmt and s_fmt will sanitize the height + * according to the standard. + */ + tw686x_g_fmt_vid_cap(file, priv, &f); + tw686x_s_fmt_vid_cap(file, priv, &f); + + /* + * Frame decimation depends on the chosen standard, + * so reset it to the current value. + */ + tw686x_set_framerate(vc, vc->fps); + return 0; +} + +static int tw686x_querystd(struct file *file, void *priv, v4l2_std_id *std) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct tw686x_dev *dev = vc->dev; + unsigned int old_std, detected_std = 0; + unsigned long end; + + if (vb2_is_streaming(&vc->vidq)) + return -EBUSY; + + /* Enable and start standard detection */ + old_std = reg_read(dev, SDT[vc->ch]); + reg_write(dev, SDT[vc->ch], 0x7); + reg_write(dev, SDT_EN[vc->ch], 0xff); + + end = jiffies + msecs_to_jiffies(500); + while (time_is_after_jiffies(end)) { + + detected_std = reg_read(dev, SDT[vc->ch]); + if (!(detected_std & BIT(7))) + break; + msleep(100); + } + reg_write(dev, SDT[vc->ch], old_std); + + /* Exit if still busy */ + if (detected_std & BIT(7)) + return 0; + + detected_std = (detected_std >> 4) & 0x7; + switch (detected_std) { + case TW686X_STD_NTSC_M: + *std &= V4L2_STD_NTSC; + break; + case TW686X_STD_NTSC_443: + *std &= V4L2_STD_NTSC_443; + break; + case TW686X_STD_PAL_M: + *std &= V4L2_STD_PAL_M; + break; + case TW686X_STD_PAL_60: + *std &= V4L2_STD_PAL_60; + break; + case TW686X_STD_PAL: + *std &= V4L2_STD_PAL; + break; + case TW686X_STD_PAL_CN: + *std &= V4L2_STD_PAL_Nc; + break; + case TW686X_STD_SECAM: + *std &= V4L2_STD_SECAM; + break; + default: + *std = 0; + } + return 0; +} + +static int tw686x_g_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + + *id = vc->video_standard; + return 0; +} + +static int tw686x_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + + if (fsize->index) + return -EINVAL; + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.max_width = TW686X_VIDEO_WIDTH; + fsize->stepwise.min_width = fsize->stepwise.max_width / 2; + fsize->stepwise.step_width = fsize->stepwise.min_width; + fsize->stepwise.max_height = TW686X_VIDEO_HEIGHT(vc->video_standard); + fsize->stepwise.min_height = fsize->stepwise.max_height / 2; + fsize->stepwise.step_height = fsize->stepwise.min_height; + return 0; +} + +static int tw686x_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *ival) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + int max_fps = TW686X_MAX_FPS(vc->video_standard); + int max_rates = DIV_ROUND_UP(max_fps, 2); + + if (ival->index >= max_rates) + return -EINVAL; + + ival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + ival->discrete.numerator = 1; + if (ival->index < (max_rates - 1)) + ival->discrete.denominator = (ival->index + 1) * 2; + else + ival->discrete.denominator = max_fps; + return 0; +} + +static int tw686x_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct v4l2_captureparm *cp = &sp->parm.capture; + + if (sp->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + sp->parm.capture.readbuffers = 3; + + cp->capability = V4L2_CAP_TIMEPERFRAME; + cp->timeperframe.numerator = 1; + cp->timeperframe.denominator = vc->fps; + return 0; +} + +static int tw686x_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct v4l2_captureparm *cp = &sp->parm.capture; + unsigned int denominator = cp->timeperframe.denominator; + unsigned int numerator = cp->timeperframe.numerator; + unsigned int fps; + + if (vb2_is_busy(&vc->vidq)) + return -EBUSY; + + fps = (!numerator || !denominator) ? 0 : denominator / numerator; + if (vc->fps != fps) + tw686x_set_framerate(vc, fps); + return tw686x_g_parm(file, priv, sp); +} + +static int tw686x_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index >= ARRAY_SIZE(formats)) + return -EINVAL; + f->pixelformat = formats[f->index].fourcc; + return 0; +} + +static void tw686x_set_input(struct tw686x_video_channel *vc, unsigned int i) +{ + u32 val; + + vc->input = i; + + val = reg_read(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch]); + val &= ~(0x3 << 30); + val |= i << 30; + reg_write(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch], val); +} + +static int tw686x_s_input(struct file *file, void *priv, unsigned int i) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + + if (i >= TW686X_INPUTS_PER_CH) + return -EINVAL; + if (i == vc->input) + return 0; + /* + * Not sure we are able to support on the fly input change + */ + if (vb2_is_busy(&vc->vidq)) + return -EBUSY; + + tw686x_set_input(vc, i); + return 0; +} + +static int tw686x_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + + *i = vc->input; + return 0; +} + +static int tw686x_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + unsigned int vidstat; + + if (i->index >= TW686X_INPUTS_PER_CH) + return -EINVAL; + + snprintf(i->name, sizeof(i->name), "Composite%d", i->index); + i->type = V4L2_INPUT_TYPE_CAMERA; + i->std = vc->device->tvnorms; + i->capabilities = V4L2_IN_CAP_STD; + + vidstat = reg_read(vc->dev, VIDSTAT[vc->ch]); + i->status = 0; + if (vidstat & TW686X_VIDSTAT_VDLOSS) + i->status |= V4L2_IN_ST_NO_SIGNAL; + if (!(vidstat & TW686X_VIDSTAT_HLOCK)) + i->status |= V4L2_IN_ST_NO_H_LOCK; + + return 0; +} + +static const struct v4l2_file_operations tw686x_video_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .unlocked_ioctl = video_ioctl2, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .read = vb2_fop_read, + .mmap = vb2_fop_mmap, +}; + +static const struct v4l2_ioctl_ops tw686x_video_ioctl_ops = { + .vidioc_querycap = tw686x_querycap, + .vidioc_g_fmt_vid_cap = tw686x_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = tw686x_s_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = tw686x_enum_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = tw686x_try_fmt_vid_cap, + + .vidioc_querystd = tw686x_querystd, + .vidioc_g_std = tw686x_g_std, + .vidioc_s_std = tw686x_s_std, + + .vidioc_g_parm = tw686x_g_parm, + .vidioc_s_parm = tw686x_s_parm, + .vidioc_enum_framesizes = tw686x_enum_framesizes, + .vidioc_enum_frameintervals = tw686x_enum_frameintervals, + + .vidioc_enum_input = tw686x_enum_input, + .vidioc_g_input = tw686x_g_input, + .vidioc_s_input = tw686x_s_input, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +void tw686x_video_irq(struct tw686x_dev *dev, unsigned long requests, + unsigned int pb_status, unsigned int fifo_status, + unsigned int *reset_ch) +{ + struct tw686x_video_channel *vc; + unsigned long flags; + unsigned int ch, pb; + + for_each_set_bit(ch, &requests, max_channels(dev)) { + vc = &dev->video_channels[ch]; + + /* + * This can either be a blue frame (with signal-lost bit set) + * or a good frame (with signal-lost bit clear). If we have just + * got signal, then this channel needs resetting. + */ + if (vc->no_signal && !(fifo_status & BIT(ch))) { + v4l2_printk(KERN_DEBUG, &dev->v4l2_dev, + "video%d: signal recovered\n", vc->num); + vc->no_signal = false; + *reset_ch |= BIT(ch); + vc->pb = 0; + continue; + } + vc->no_signal = !!(fifo_status & BIT(ch)); + + /* Check FIFO errors only if there's signal */ + if (!vc->no_signal) { + u32 fifo_ov, fifo_bad; + + fifo_ov = (fifo_status >> 24) & BIT(ch); + fifo_bad = (fifo_status >> 16) & BIT(ch); + if (fifo_ov || fifo_bad) { + /* Mark this channel for reset */ + v4l2_printk(KERN_DEBUG, &dev->v4l2_dev, + "video%d: FIFO error\n", vc->num); + *reset_ch |= BIT(ch); + vc->pb = 0; + continue; + } + } + + pb = !!(pb_status & BIT(ch)); + if (vc->pb != pb) { + /* Mark this channel for reset */ + v4l2_printk(KERN_DEBUG, &dev->v4l2_dev, + "video%d: unexpected p-b buffer!\n", + vc->num); + *reset_ch |= BIT(ch); + vc->pb = 0; + continue; + } + + spin_lock_irqsave(&vc->qlock, flags); + tw686x_buf_done(vc, pb); + dev->dma_ops->buf_refill(vc, pb); + spin_unlock_irqrestore(&vc->qlock, flags); + } +} + +void tw686x_video_free(struct tw686x_dev *dev) +{ + unsigned int ch, pb; + + for (ch = 0; ch < max_channels(dev); ch++) { + struct tw686x_video_channel *vc = &dev->video_channels[ch]; + + video_unregister_device(vc->device); + + if (dev->dma_ops->free) + for (pb = 0; pb < 2; pb++) + dev->dma_ops->free(vc, pb); + } +} + +int tw686x_video_init(struct tw686x_dev *dev) +{ + unsigned int ch, val; + int err; + + if (dev->dma_mode == TW686X_DMA_MODE_MEMCPY) + dev->dma_ops = &memcpy_dma_ops; + else if (dev->dma_mode == TW686X_DMA_MODE_CONTIG) + dev->dma_ops = &contig_dma_ops; + else if (dev->dma_mode == TW686X_DMA_MODE_SG) + dev->dma_ops = &sg_dma_ops; + else + return -EINVAL; + + err = v4l2_device_register(&dev->pci_dev->dev, &dev->v4l2_dev); + if (err) + return err; + + if (dev->dma_ops->setup) { + err = dev->dma_ops->setup(dev); + if (err) + return err; + } + + /* Initialize vc->dev and vc->ch for the error path */ + for (ch = 0; ch < max_channels(dev); ch++) { + struct tw686x_video_channel *vc = &dev->video_channels[ch]; + + vc->dev = dev; + vc->ch = ch; + } + + for (ch = 0; ch < max_channels(dev); ch++) { + struct tw686x_video_channel *vc = &dev->video_channels[ch]; + struct video_device *vdev; + + mutex_init(&vc->vb_mutex); + spin_lock_init(&vc->qlock); + INIT_LIST_HEAD(&vc->vidq_queued); + + /* default settings */ + err = tw686x_set_standard(vc, V4L2_STD_NTSC); + if (err) + goto error; + + err = tw686x_set_format(vc, formats[0].fourcc, + TW686X_VIDEO_WIDTH, + TW686X_VIDEO_HEIGHT(vc->video_standard), + true); + if (err) + goto error; + + tw686x_set_input(vc, 0); + tw686x_set_framerate(vc, 30); + reg_write(dev, VDELAY_LO[ch], 0x14); + reg_write(dev, HACTIVE_LO[ch], 0xd0); + reg_write(dev, VIDEO_SIZE[ch], 0); + + vc->vidq.io_modes = VB2_READ | VB2_MMAP | VB2_DMABUF; + vc->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vc->vidq.drv_priv = vc; + vc->vidq.buf_struct_size = sizeof(struct tw686x_v4l2_buf); + vc->vidq.ops = &tw686x_video_qops; + vc->vidq.mem_ops = dev->dma_ops->mem_ops; + vc->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vc->vidq.min_buffers_needed = 2; + vc->vidq.lock = &vc->vb_mutex; + vc->vidq.gfp_flags = dev->dma_mode != TW686X_DMA_MODE_MEMCPY ? + GFP_DMA32 : 0; + vc->vidq.dev = &dev->pci_dev->dev; + + err = vb2_queue_init(&vc->vidq); + if (err) { + v4l2_err(&dev->v4l2_dev, + "dma%d: cannot init vb2 queue\n", ch); + goto error; + } + + err = v4l2_ctrl_handler_init(&vc->ctrl_handler, 4); + if (err) { + v4l2_err(&dev->v4l2_dev, + "dma%d: cannot init ctrl handler\n", ch); + goto error; + } + v4l2_ctrl_new_std(&vc->ctrl_handler, &ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(&vc->ctrl_handler, &ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 100); + v4l2_ctrl_new_std(&vc->ctrl_handler, &ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(&vc->ctrl_handler, &ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + err = vc->ctrl_handler.error; + if (err) + goto error; + + err = v4l2_ctrl_handler_setup(&vc->ctrl_handler); + if (err) + goto error; + + vdev = video_device_alloc(); + if (!vdev) { + v4l2_err(&dev->v4l2_dev, + "dma%d: unable to allocate device\n", ch); + err = -ENOMEM; + goto error; + } + + snprintf(vdev->name, sizeof(vdev->name), "%s video", dev->name); + vdev->fops = &tw686x_video_fops; + vdev->ioctl_ops = &tw686x_video_ioctl_ops; + vdev->release = video_device_release; + vdev->v4l2_dev = &dev->v4l2_dev; + vdev->queue = &vc->vidq; + vdev->tvnorms = V4L2_STD_525_60 | V4L2_STD_625_50; + vdev->minor = -1; + vdev->lock = &vc->vb_mutex; + vdev->ctrl_handler = &vc->ctrl_handler; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; + vc->device = vdev; + video_set_drvdata(vdev, vc); + + err = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (err < 0) { + video_device_release(vdev); + goto error; + } + vc->num = vdev->num; + } + + val = TW686X_DEF_PHASE_REF; + for (ch = 0; ch < max_channels(dev); ch++) + val |= dev->dma_ops->hw_dma_mode << (16 + ch * 2); + reg_write(dev, PHASE_REF, val); + + reg_write(dev, MISC2[0], 0xe7); + reg_write(dev, VCTRL1[0], 0xcc); + reg_write(dev, LOOP[0], 0xa5); + if (max_channels(dev) > 4) { + reg_write(dev, VCTRL1[1], 0xcc); + reg_write(dev, LOOP[1], 0xa5); + reg_write(dev, MISC2[1], 0xe7); + } + return 0; + +error: + tw686x_video_free(dev); + return err; +} diff --git a/drivers/media/pci/tw686x/tw686x.h b/drivers/media/pci/tw686x/tw686x.h new file mode 100644 index 000000000..21a989950 --- /dev/null +++ b/drivers/media/pci/tw686x/tw686x.h @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2015 VanguardiaSur - www.vanguardiasur.com.ar + * + * Copyright (C) 2015 Industrial Research Institute for Automation + * and Measurements PIAP + * Written by Krzysztof Ha?asa + */ + +#include <linux/mutex.h> +#include <linux/pci.h> +#include <linux/timer.h> +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-v4l2.h> +#include <sound/pcm.h> + +#include "tw686x-regs.h" + +#define TYPE_MAX_CHANNELS 0x0f +#define TYPE_SECOND_GEN 0x10 +#define TW686X_DEF_PHASE_REF 0x1518 + +#define TW686X_AUDIO_PAGE_MAX 16 +#define TW686X_AUDIO_PERIODS_MIN 2 +#define TW686X_AUDIO_PERIODS_MAX TW686X_AUDIO_PAGE_MAX + +#define TW686X_DMA_MODE_MEMCPY 0 +#define TW686X_DMA_MODE_CONTIG 1 +#define TW686X_DMA_MODE_SG 2 + +struct tw686x_format { + char *name; + unsigned int fourcc; + unsigned int depth; + unsigned int mode; +}; + +struct tw686x_dma_desc { + dma_addr_t phys; + void *virt; + unsigned int size; +}; + +struct tw686x_sg_desc { + /* 3 MSBits for flags, 13 LSBits for length */ + __le32 flags_length; + __le32 phys; +}; + +struct tw686x_audio_buf { + dma_addr_t dma; + void *virt; + struct list_head list; +}; + +struct tw686x_v4l2_buf { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +struct tw686x_audio_channel { + struct tw686x_dev *dev; + struct snd_pcm_substream *ss; + unsigned int ch; + struct tw686x_audio_buf *curr_bufs[2]; + struct tw686x_dma_desc dma_descs[2]; + dma_addr_t ptr; + + struct tw686x_audio_buf buf[TW686X_AUDIO_PAGE_MAX]; + struct list_head buf_list; + spinlock_t lock; +}; + +struct tw686x_video_channel { + struct tw686x_dev *dev; + + struct vb2_queue vidq; + struct list_head vidq_queued; + struct video_device *device; + struct tw686x_v4l2_buf *curr_bufs[2]; + struct tw686x_dma_desc dma_descs[2]; + struct tw686x_sg_desc *sg_descs[2]; + + struct v4l2_ctrl_handler ctrl_handler; + const struct tw686x_format *format; + struct mutex vb_mutex; + spinlock_t qlock; + v4l2_std_id video_standard; + unsigned int width, height; + unsigned int h_halve, v_halve; + unsigned int ch; + unsigned int num; + unsigned int fps; + unsigned int input; + unsigned int sequence; + unsigned int pb; + bool no_signal; +}; + +struct tw686x_dma_ops { + int (*setup)(struct tw686x_dev *dev); + int (*alloc)(struct tw686x_video_channel *vc, unsigned int pb); + void (*free)(struct tw686x_video_channel *vc, unsigned int pb); + void (*buf_refill)(struct tw686x_video_channel *vc, unsigned int pb); + const struct vb2_mem_ops *mem_ops; + enum v4l2_field field; + u32 hw_dma_mode; +}; + +/* struct tw686x_dev - global device status */ +struct tw686x_dev { + /* + * spinlock controlling access to the shared device registers + * (DMA enable/disable) + */ + spinlock_t lock; + + struct v4l2_device v4l2_dev; + struct snd_card *snd_card; + + char name[32]; + unsigned int type; + unsigned int dma_mode; + struct pci_dev *pci_dev; + __u32 __iomem *mmio; + + const struct tw686x_dma_ops *dma_ops; + struct tw686x_video_channel *video_channels; + struct tw686x_audio_channel *audio_channels; + + /* Per-device audio parameters */ + int audio_rate; + int period_size; + int audio_enabled; + + struct timer_list dma_delay_timer; + u32 pending_dma_en; /* must be protected by lock */ + u32 pending_dma_cmd; /* must be protected by lock */ +}; + +static inline uint32_t reg_read(struct tw686x_dev *dev, unsigned int reg) +{ + return readl(dev->mmio + reg); +} + +static inline void reg_write(struct tw686x_dev *dev, unsigned int reg, + uint32_t value) +{ + writel(value, dev->mmio + reg); +} + +static inline unsigned int max_channels(struct tw686x_dev *dev) +{ + return dev->type & TYPE_MAX_CHANNELS; /* 4 or 8 channels */ +} + +static inline unsigned is_second_gen(struct tw686x_dev *dev) +{ + /* each channel has its own DMA SG table */ + return dev->type & TYPE_SECOND_GEN; +} + +void tw686x_enable_channel(struct tw686x_dev *dev, unsigned int channel); +void tw686x_disable_channel(struct tw686x_dev *dev, unsigned int channel); + +int tw686x_video_init(struct tw686x_dev *dev); +void tw686x_video_free(struct tw686x_dev *dev); +void tw686x_video_irq(struct tw686x_dev *dev, unsigned long requests, + unsigned int pb_status, unsigned int fifo_status, + unsigned int *reset_ch); + +int tw686x_audio_init(struct tw686x_dev *dev); +void tw686x_audio_free(struct tw686x_dev *dev); +void tw686x_audio_irq(struct tw686x_dev *dev, unsigned long requests, + unsigned int pb_status); |