[PATCH] media: pci: Add core driver for Techwell TW68xx video capture devices

Fidal Palamparambil posted 1 patch 4 weeks, 1 day ago
drivers/media/pci/tw68/tw68-core.c | 165 +++++++++++++++--------------
1 file changed, 84 insertions(+), 81 deletions(-)
[PATCH] media: pci: Add core driver for Techwell TW68xx video capture devices
Posted by Fidal Palamparambil 4 weeks, 1 day ago
From: Fidal Palamparambil <hellomystoryswherei@gmail.com>

Introduce a new PCI driver for Techwell TW6800/6801/6804-based video
capture cards. This replaces the old Nuvoton WPCM450 SoC ID code in
this tree snapshot.

Key features:
- PCI probe and remove handling for multiple TW68xx variants.
- Full hardware initialization sequence with documented register setup.
- Video IRQ handler with proper masking and error diagnostics.
- Device memory mapping via devm_ioremap_resource and managed IRQs.
- DMA mask configuration with fallback to 24-bit addressing.
- V4L2 core registration, control handler initialization, and
  video device setup.
- Power management (suspend/resume) support to restore DMA state and
  active buffers.

This driver follows V4L2 framework conventions, drawing from cx88,
sa7134, and bt87x driver design. Original authorship and contributions
are retained, with updates for modern kernel APIs.

Signed-off-by: Fidal Palamparambil <hellomystoryswherei@gmail.com>
---
 drivers/media/pci/tw68/tw68-core.c | 165 +++++++++++++++--------------
 1 file changed, 84 insertions(+), 81 deletions(-)

diff --git a/drivers/media/pci/tw68/tw68-core.c b/drivers/media/pci/tw68/tw68-core.c
index 35dd19b2427e..fd5c4cccfb56 100644
--- a/drivers/media/pci/tw68/tw68-core.c
+++ b/drivers/media/pci/tw68/tw68-core.c
@@ -72,9 +72,9 @@ static const struct pci_device_id tw68_pci_tbl[] = {
 	{0,}
 };
 
+MODULE_DEVICE_TABLE(pci, tw68_pci_tbl);
 /* ------------------------------------------------------------------ */
 
-
 /*
  * The device is given a "soft reset". According to the specifications,
  * after this "all register content remain unchanged", so we also write
@@ -130,7 +130,6 @@ static int tw68_hw_init1(struct tw68_dev *dev)
 	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 */
@@ -191,36 +190,43 @@ static irqreturn_t tw68_irq(int irq, void *dev_id)
 
 	status = orig = tw_readl(TW68_INTSTAT) & dev->pci_irqmask;
 	/* Check if anything to do */
-	if (0 == status)
+	if (status == 0)
 		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;
+		if (status == 0)
+			goto out;
 	}
-	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);
+	
+	dev_warn_ratelimited(&dev->pci->dev,
+			     "%s: **** INTERRUPT NOT HANDLED - clearing mask (orig 0x%08x, cur 0x%08x)\n",
+			     dev->name, orig, tw_readl(TW68_INTSTAT));
+	dev_warn_ratelimited(&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);
+out:
 	return IRQ_HANDLED;
 }
 
 static int tw68_initdev(struct pci_dev *pci_dev,
-				     const struct pci_device_id *pci_id)
+			const struct pci_device_id *pci_id)
 {
 	struct tw68_dev *dev;
+	struct resource *res;
 	int vidnr = -1;
 	int err;
 
 	dev = devm_kzalloc(&pci_dev->dev, sizeof(*dev), GFP_KERNEL);
-	if (NULL == dev)
+	if (!dev)
 		return -ENOMEM;
 
 	dev->instance = v4l2_device_set_name(&dev->v4l2_dev, "tw68",
-						&tw68_instance);
+					     &tw68_instance);
 
 	err = v4l2_device_register(&pci_dev->dev, &dev->v4l2_dev);
 	if (err)
@@ -228,30 +234,43 @@ static int tw68_initdev(struct pci_dev *pci_dev,
 
 	/* pci init */
 	dev->pci = pci_dev;
-	if (pci_enable_device(pci_dev)) {
-		err = -EIO;
-		goto fail1;
+	err = pci_enable_device(pci_dev);
+	if (err) {
+		dev_err(&pci_dev->dev, "Failed to enable PCI device\n");
+		goto fail_v4l2;
 	}
 
 	dev->name = dev->v4l2_dev.name;
 
-	if (UNSET != latency) {
-		pr_info("%s: setting pci latency timer to %d\n",
-		       dev->name, latency);
+	if (latency != UNSET) {
+		dev_info(&pci_dev->dev, "setting pci latency timer to %d\n",
+			 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_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat);
+	dev_info(&pci_dev->dev, "found at %s, rev: %d, irq: %d, latency: %d, mmio: 0x%llx\n",
+		 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);
+	
+	/* Set DMA mask - try 32-bit first, then fallback to 24-bit if needed */
 	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;
+		err = dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(24));
+		if (err) {
+			dev_err(&pci_dev->dev, "No suitable DMA available\n");
+			goto fail_pci;
+		}
+	}
+	
+	err = dma_set_coherent_mask(&pci_dev->dev, DMA_BIT_MASK(32));
+	if (err) {
+		dev_err(&pci_dev->dev, "Failed to set consistent DMA mask\n");
+		goto fail_pci;
 	}
 
 	switch (pci_id->device) {
@@ -273,66 +292,51 @@ static int tw68_initdev(struct pci_dev *pci_dev,
 		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;
+	/* get mmio using devm_ioremap_resource */
+	res = &pci_dev->resource[0];
+	dev->lmmio = devm_ioremap_resource(&pci_dev->dev, res);
+	if (IS_ERR(dev->lmmio)) {
+		err = PTR_ERR(dev->lmmio);
+		dev_err(&pci_dev->dev, "can't ioremap() MMIO memory\n");
+		goto fail_pci;
 	}
-	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);
+			       IRQF_SHARED, dev->name, dev);
 	if (err < 0) {
-		pr_err("%s: can't get IRQ %d\n",
-		       dev->name, pci_dev->irq);
-		goto fail3;
+		dev_err(&pci_dev->dev, "can't get IRQ %d\n", pci_dev->irq);
+		goto fail_pci;
 	}
 
 	/*
-	 *  Now do remainder of initialisation, first for
-	 *  things unique for this card, then for general board
+	 * 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;
+		dev_err(&pci_dev->dev, "can't register video device\n");
+		goto fail_pci;
 	}
+	
 	tw_setl(TW68_INTMASK, dev->pci_irqmask);
 
-	pr_info("%s: registered device %s\n",
-	       dev->name, video_device_node_name(&dev->vdev));
+	dev_info(&pci_dev->dev, "registered device %s\n",
+		 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:
+fail_pci:
+	pci_disable_device(pci_dev);
+fail_v4l2:
 	v4l2_device_unregister(&dev->v4l2_dev);
 	return err;
 }
@@ -340,8 +344,7 @@ static int tw68_initdev(struct pci_dev *pci_dev,
 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);
+	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);
@@ -351,11 +354,8 @@ static void tw68_finidev(struct pci_dev *pci_dev)
 	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));
-
+	/* release resources - devm handles ioremap cleanup */
+	pci_disable_device(pci_dev);
 	v4l2_device_unregister(&dev->v4l2_dev);
 }
 
@@ -363,8 +363,7 @@ 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);
+	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;
@@ -379,25 +378,29 @@ static int __maybe_unused tw68_suspend(struct device *dev_d)
 
 static int __maybe_unused tw68_resume(struct device *dev_d)
 {
+	struct pci_dev *pci_dev = to_pci_dev(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_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.*/
-
+	/* Do things that are done in tw68_initdev,
+	 * except for initializing memory structures.
+	 */
 	msleep(100);
 
+	tw68_hw_init1(dev);
 	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);
+	/* Restore interrupt mask */
+	tw_setl(TW68_INTMASK, dev->pci_irqmask);
 
+	/* Resume unfinished buffer(s) */
+	spin_lock_irqsave(&dev->slock, flags);
+	if (!list_empty(&dev->active)) {
+		buf = container_of(dev->active.next, struct tw68_buf, list);
+		tw68_video_start_dma(dev, buf);
+	}
 	spin_unlock_irqrestore(&dev->slock, flags);
 
 	return 0;
@@ -415,4 +418,4 @@ static struct pci_driver tw68_pci_driver = {
 	.driver.pm = &tw68_pm_ops,
 };
 
-module_pci_driver(tw68_pci_driver);
+module_pci_driver(tw68_pci_driver);
\ No newline at end of file
-- 
2.50.1.windows.1
Re: [PATCH] media: pci: Add core driver for Techwell TW68xx video capture devices
Posted by kernel test robot 4 weeks ago
Hi Fidal,

kernel test robot noticed the following build warnings:

[auto build test WARNING on linus/master]
[also build test WARNING on v6.17-rc4 next-20250904]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Fidal-Palamparambil/media-pci-Add-core-driver-for-Techwell-TW68xx-video-capture-devices/20250904-014821
base:   linus/master
patch link:    https://lore.kernel.org/r/20250903174657.1689-1-hellomystoryswherei%40gmail.com
patch subject: [PATCH] media: pci: Add core driver for Techwell TW68xx video capture devices
config: i386-buildonly-randconfig-006-20250904 (https://download.01.org/0day-ci/archive/20250904/202509041912.qtRHikwE-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250904/202509041912.qtRHikwE-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202509041912.qtRHikwE-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/media/pci/tw68/tw68-core.c:381:18: warning: unused variable 'pci_dev' [-Wunused-variable]
     381 |         struct pci_dev *pci_dev = to_pci_dev(dev_d);
         |                         ^~~~~~~
   1 warning generated.


vim +/pci_dev +381 drivers/media/pci/tw68/tw68-core.c

   378	
   379	static int __maybe_unused tw68_resume(struct device *dev_d)
   380	{
 > 381		struct pci_dev *pci_dev = to_pci_dev(dev_d);
   382		struct v4l2_device *v4l2_dev = dev_get_drvdata(dev_d);
   383		struct tw68_dev *dev = container_of(v4l2_dev, struct tw68_dev, v4l2_dev);
   384		struct tw68_buf *buf;
   385		unsigned long flags;
   386	
   387		/* Do things that are done in tw68_initdev,
   388		 * except for initializing memory structures.
   389		 */
   390		msleep(100);
   391	
   392		tw68_hw_init1(dev);
   393		tw68_set_tvnorm_hw(dev);
   394	
   395		/* Restore interrupt mask */
   396		tw_setl(TW68_INTMASK, dev->pci_irqmask);
   397	
   398		/* Resume unfinished buffer(s) */
   399		spin_lock_irqsave(&dev->slock, flags);
   400		if (!list_empty(&dev->active)) {
   401			buf = container_of(dev->active.next, struct tw68_buf, list);
   402			tw68_video_start_dma(dev, buf);
   403		}
   404		spin_unlock_irqrestore(&dev->slock, flags);
   405	
   406		return 0;
   407	}
   408	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki