[<prev] [next>] [day] [month] [year] [list]
Message-ID: <20250903175417.1340-1-palamparambilfidal089@gmail.com>
Date: Wed, 3 Sep 2025 21:54:17 +0400
From: Fidal@....codeaurora.org, Palamparambil@....codeaurora.org,
palamparambilfidal089@...il.com
To: kgdb-bugreport@...ts.sourceforge.net
Cc: linux-kernel@...r.kernel.org,
daniel.thompson@...aro.org,
dianders@...omium.org,
Fidal Palamparambil <hellomystoryswherei@...il.com>
Subject: [PATCH] media: pci: Add core driver for Techwell TW68xx video capture devices
From: Fidal Palamparambil <hellomystoryswherei@...il.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@...il.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
Powered by blists - more mailing lists