diff options
Diffstat (limited to 'drivers/media/pci/cx23885/cx23885-core.c')
| -rw-r--r-- | drivers/media/pci/cx23885/cx23885-core.c | 311 |
1 files changed, 254 insertions, 57 deletions
diff --git a/drivers/media/pci/cx23885/cx23885-core.c b/drivers/media/pci/cx23885/cx23885-core.c index 02b5ec549369..a39f445ce22a 100644 --- a/drivers/media/pci/cx23885/cx23885-core.c +++ b/drivers/media/pci/cx23885/cx23885-core.c @@ -1,18 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Driver for the Conexant CX23885 PCIe bridge * * Copyright (c) 2006 Steven Toth <stoth@linuxtv.org> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * - * GNU General Public License for more details. */ #include "cx23885.h" @@ -23,6 +13,7 @@ #include <linux/moduleparam.h> #include <linux/kmod.h> #include <linux/kernel.h> +#include <linux/pci.h> #include <linux/slab.h> #include <linux/interrupt.h> #include <linux/delay.h> @@ -41,6 +32,18 @@ MODULE_AUTHOR("Steven Toth <stoth@linuxtv.org>"); MODULE_LICENSE("GPL"); MODULE_VERSION(CX23885_VERSION); +/* + * Some platforms have been found to require periodic resetting of the DMA + * engine. Ryzen and XEON platforms are known to be affected. The symptom + * encountered is "mpeg risc op code error". Only Ryzen platforms employ + * this workaround if the option equals 1. The workaround can be explicitly + * disabled for all platforms by setting to 0, the workaround can be forced + * on for any platform by setting to 2. + */ +static unsigned int dma_reset_workaround = 1; +module_param(dma_reset_workaround, int, 0644); +MODULE_PARM_DESC(dma_reset_workaround, "periodic RiSC dma engine reset; 0-force disable, 1-driver detect (default), 2-force enable"); + static unsigned int debug; module_param(debug, int, 0644); MODULE_PARM_DESC(debug, "enable debug messages"); @@ -422,19 +425,30 @@ static void cx23885_wakeup(struct cx23885_tsport *port, struct cx23885_dmaqueue *q, u32 count) { struct cx23885_buffer *buf; - - if (list_empty(&q->active)) - return; - buf = list_entry(q->active.next, - struct cx23885_buffer, queue); - - buf->vb.vb2_buf.timestamp = ktime_get_ns(); - buf->vb.sequence = q->count++; - dprintk(1, "[%p/%d] wakeup reg=%d buf=%d\n", buf, - buf->vb.vb2_buf.index, - count, q->count); - list_del(&buf->queue); - vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + int count_delta; + int max_buf_done = 5; /* service maximum five buffers */ + + do { + if (list_empty(&q->active)) + return; + buf = list_entry(q->active.next, + struct cx23885_buffer, queue); + + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + buf->vb.sequence = q->count++; + if (count != (q->count % 65536)) { + dprintk(1, "[%p/%d] wakeup reg=%d buf=%d\n", buf, + buf->vb.vb2_buf.index, count, q->count); + } else { + dprintk(7, "[%p/%d] wakeup reg=%d buf=%d\n", buf, + buf->vb.vb2_buf.index, count, q->count); + } + list_del(&buf->queue); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + max_buf_done--; + /* count register is 16 bits so apply modulo appropriately */ + count_delta = ((int)count - (int)(q->count % 65536)); + } while ((count_delta > 0) && (max_buf_done > 0)); } int cx23885_sram_channel_setup(struct cx23885_dev *dev, @@ -540,14 +554,14 @@ void cx23885_sram_channel_dump(struct cx23885_dev *dev, for (i = 0; i < 4; i++) { risc = cx_read(ch->cmds_start + 4 * (i + 14)); - pr_warn("%s: risc%d: ", dev->name, i); + pr_warn("%s: risc%d:", dev->name, i); cx23885_risc_decode(risc); } for (i = 0; i < (64 >> 2); i += n) { risc = cx_read(ch->ctrl_start + 4 * i); /* No consideration for bits 63-32 */ - pr_warn("%s: (0x%08x) iq %x: ", dev->name, + pr_warn("%s: (0x%08x) iq %x:", dev->name, ch->ctrl_start + 4 * i, i); n = cx23885_risc_decode(risc); for (j = 1; j < n; j++) { @@ -580,7 +594,7 @@ static void cx23885_risc_disasm(struct cx23885_tsport *port, pr_info("%s: risc disasm: %p [dma=0x%08lx]\n", dev->name, risc->cpu, (unsigned long)risc->dma); for (i = 0; i < (risc->size >> 2); i += n) { - pr_info("%s: %04d: ", dev->name, i); + pr_info("%s: %04d:", dev->name, i); n = cx23885_risc_decode(le32_to_cpu(risc->cpu[i])); for (j = 1; j < n; j++) pr_info("%s: %04d: 0x%08x [ arg #%d ]\n", @@ -590,6 +604,30 @@ static void cx23885_risc_disasm(struct cx23885_tsport *port, } } +static void cx23885_clear_bridge_error(struct cx23885_dev *dev) +{ + uint32_t reg1_val, reg2_val; + + if (!dev->need_dma_reset) + return; + + reg1_val = cx_read(TC_REQ); /* read-only */ + reg2_val = cx_read(TC_REQ_SET); + + if (reg1_val && reg2_val) { + cx_write(TC_REQ, reg1_val); + cx_write(TC_REQ_SET, reg2_val); + cx_read(VID_B_DMA); + cx_read(VBI_B_DMA); + cx_read(VID_C_DMA); + cx_read(VBI_C_DMA); + + dev_info(&dev->pci->dev, + "dma in progress detected 0x%08x 0x%08x, clearing\n", + reg1_val, reg2_val); + } +} + static void cx23885_shutdown(struct cx23885_dev *dev) { /* disable RISC controller */ @@ -635,7 +673,9 @@ static void cx23885_reset(struct cx23885_dev *dev) cx_write(CLK_DELAY, cx_read(CLK_DELAY) & 0x80000000); cx_write(PAD_CTRL, 0x00500300); - mdelay(100); + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); + msleep(100); cx23885_sram_channel_setup(dev, &dev->sram_channels[SRAM_CH01], 720*4, 0); @@ -651,6 +691,11 @@ static void cx23885_reset(struct cx23885_dev *dev) cx23885_sram_channel_setup(dev, &dev->sram_channels[SRAM_CH09], 128, 0); cx23885_gpio_setup(dev); + + cx23885_irq_get_mask(dev); + + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); } @@ -665,6 +710,8 @@ static int cx23885_pci_quirks(struct cx23885_dev *dev) if (dev->bridge == CX23885_BRIDGE_885) cx_clear(RDR_TLCTL0, 1 << 4); + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); return 0; } @@ -839,10 +886,10 @@ static int cx23885_dev_setup(struct cx23885_dev *dev) /* Configure the internal memory */ if (dev->pci->device == 0x8880) { - /* Could be 887 or 888, assume a default */ - dev->bridge = CX23885_BRIDGE_887; + /* Could be 887 or 888, assume an 888 default */ + dev->bridge = CX23885_BRIDGE_888; /* Apply a sensible clock frequency for the PCIe bridge */ - dev->clk_freq = 25000000; + dev->clk_freq = 50000000; dev->sram_channels = cx23887_sram_channels; } else if (dev->pci->device == 0x8852) { @@ -869,10 +916,28 @@ static int cx23885_dev_setup(struct cx23885_dev *dev) cx23885_card_list(dev); } + if (dev->pci->device == 0x8852) { + /* no DIF on cx23885, so no analog tuner support possible */ + if (dev->board == CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC) + dev->board = CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC_885; + else if (dev->board == CX23885_BOARD_HAUPPAUGE_QUADHD_DVB) + dev->board = CX23885_BOARD_HAUPPAUGE_QUADHD_DVB_885; + } + /* If the user specific a clk freq override, apply it */ if (cx23885_boards[dev->board].clk_freq > 0) dev->clk_freq = cx23885_boards[dev->board].clk_freq; + if (dev->board == CX23885_BOARD_HAUPPAUGE_IMPACTVCBE && + dev->pci->subsystem_device == 0x7137) { + /* Hauppauge ImpactVCBe device ID 0x7137 is populated + * with an 888, and a 25Mhz crystal, instead of the + * usual third overtone 50Mhz. The default clock rate must + * be overridden so the cx25840 is properly configured + */ + dev->clk_freq = 25000000; + } + dev->pci_bus = dev->pci->bus->number; dev->pci_slot = PCI_SLOT(dev->pci->devfn); cx23885_irq_add(dev, 0x001f00); @@ -965,7 +1030,7 @@ static int cx23885_dev_setup(struct cx23885_dev *dev) cx23885_i2c_register(&dev->i2c_bus[1]); cx23885_i2c_register(&dev->i2c_bus[2]); cx23885_card_setup(dev); - call_all(dev, core, s_power, 0); + call_all(dev, tuner, standby); cx23885_ir_init(dev); if (dev->board == CX23885_BOARD_VIEWCAST_460E) { @@ -1153,7 +1218,8 @@ int cx23885_risc_buffer(struct pci_dev *pci, struct cx23885_riscmem *risc, / PAGE_SIZE + lines); instructions += 5; risc->size = instructions * 12; - risc->cpu = pci_alloc_consistent(pci, risc->size, &risc->dma); + risc->cpu = dma_alloc_coherent(&pci->dev, risc->size, &risc->dma, + GFP_KERNEL); if (risc->cpu == NULL) return -ENOMEM; @@ -1190,7 +1256,8 @@ int cx23885_risc_databuffer(struct pci_dev *pci, instructions += 4; risc->size = instructions * 12; - risc->cpu = pci_alloc_consistent(pci, risc->size, &risc->dma); + risc->cpu = dma_alloc_coherent(&pci->dev, risc->size, &risc->dma, + GFP_KERNEL); if (risc->cpu == NULL) return -ENOMEM; @@ -1228,7 +1295,8 @@ int cx23885_risc_vbibuffer(struct pci_dev *pci, struct cx23885_riscmem *risc, / PAGE_SIZE + lines); instructions += 5; risc->size = instructions * 12; - risc->cpu = pci_alloc_consistent(pci, risc->size, &risc->dma); + risc->cpu = dma_alloc_coherent(&pci->dev, risc->size, &risc->dma, + GFP_KERNEL); if (risc->cpu == NULL) return -ENOMEM; /* write risc instructions */ @@ -1257,8 +1325,9 @@ void cx23885_free_buffer(struct cx23885_dev *dev, struct cx23885_buffer *buf) { struct cx23885_riscmem *risc = &buf->risc; - BUG_ON(in_interrupt()); - pci_free_consistent(dev->pci, risc->size, risc->cpu, risc->dma); + if (risc->cpu) + dma_free_coherent(&dev->pci->dev, risc->size, risc->cpu, risc->dma); + memset(risc, 0, sizeof(*risc)); } static void cx23885_tsport_reg_dump(struct cx23885_tsport *port) @@ -1311,6 +1380,18 @@ static void cx23885_tsport_reg_dump(struct cx23885_tsport *port) port->reg_ts_clk_en, cx_read(port->reg_ts_clk_en)); dprintk(1, "%s() ts_int_msk(0x%08X) 0x%08x\n", __func__, port->reg_ts_int_msk, cx_read(port->reg_ts_int_msk)); + dprintk(1, "%s() ts_int_status(0x%08X) 0x%08x\n", __func__, + port->reg_ts_int_stat, cx_read(port->reg_ts_int_stat)); + dprintk(1, "%s() PCI_INT_STAT 0x%08X\n", __func__, + cx_read(PCI_INT_STAT)); + dprintk(1, "%s() VID_B_INT_MSTAT 0x%08X\n", __func__, + cx_read(VID_B_INT_MSTAT)); + dprintk(1, "%s() VID_B_INT_SSTAT 0x%08X\n", __func__, + cx_read(VID_B_INT_SSTAT)); + dprintk(1, "%s() VID_C_INT_MSTAT 0x%08X\n", __func__, + cx_read(VID_C_INT_MSTAT)); + dprintk(1, "%s() VID_C_INT_SSTAT 0x%08X\n", __func__, + cx_read(VID_C_INT_SSTAT)); } int cx23885_start_dma(struct cx23885_tsport *port, @@ -1323,6 +1404,9 @@ int cx23885_start_dma(struct cx23885_tsport *port, dprintk(1, "%s() w: %d, h: %d, f: %d\n", __func__, dev->width, dev->height, dev->field); + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); + /* Stop the fifo and risc engine for this port */ cx_clear(port->reg_dma_ctl, port->dma_ctl_val); @@ -1392,8 +1476,15 @@ int cx23885_start_dma(struct cx23885_tsport *port, reg = reg | 0xa; cx_write(PAD_CTRL, reg); - /* FIXME and these two registers should be documented. */ + /* Sets MOE_CLK_DIS to disable MoE clock */ + /* sets MCLK_DLY_SEL/BCLK_DLY_SEL to 1 buffer delay each */ cx_write(CLK_DELAY, cx_read(CLK_DELAY) | 0x80000011); + + /* ALT_GPIO_ALT_SET: GPIO[0] + * IR_ALT_TX_SEL: GPIO[1] + * GPIO1_ALT_SEL: VIP_656_DATA[0] + * GPIO0_ALT_SEL: VIP_656_CLK + */ cx_write(ALT_PIN_OUT_SEL, 0x10100045); } @@ -1403,16 +1494,26 @@ int cx23885_start_dma(struct cx23885_tsport *port, case CX23885_BRIDGE_888: /* enable irqs */ dprintk(1, "%s() enabling TS int's and DMA\n", __func__); + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); cx_set(port->reg_ts_int_msk, port->ts_int_msk_val); cx_set(port->reg_dma_ctl, port->dma_ctl_val); + + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); cx23885_irq_add(dev, port->pci_irqmask); cx23885_irq_enable_all(dev); + + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); break; default: BUG(); } cx_set(DEV_CNTRL2, (1<<5)); /* Enable RISC controller */ + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); if (cx23885_boards[dev->board].portb == CX23885_MPEG_ENCODER) cx23885_av_clk(dev, 1); @@ -1420,6 +1521,11 @@ int cx23885_start_dma(struct cx23885_tsport *port, if (debug > 4) cx23885_tsport_reg_dump(port); + cx23885_irq_get_mask(dev); + + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); + return 0; } @@ -1427,15 +1533,28 @@ static int cx23885_stop_dma(struct cx23885_tsport *port) { struct cx23885_dev *dev = port->dev; u32 reg; + int delay = 0; + uint32_t reg1_val; + uint32_t reg2_val; dprintk(1, "%s()\n", __func__); /* Stop interrupts and DMA */ cx_clear(port->reg_ts_int_msk, port->ts_int_msk_val); cx_clear(port->reg_dma_ctl, port->dma_ctl_val); + /* just in case wait for any dma to complete before allowing dealloc */ + mdelay(20); + for (delay = 0; delay < 100; delay++) { + reg1_val = cx_read(TC_REQ); + reg2_val = cx_read(TC_REQ_SET); + if (reg1_val == 0 || reg2_val == 0) + break; + mdelay(1); + } + dev_dbg(&dev->pci->dev, "delay=%d reg1=0x%08x reg2=0x%08x\n", + delay, reg1_val, reg2_val); if (cx23885_boards[dev->board].portb == CX23885_MPEG_ENCODER) { - reg = cx_read(PAD_CTRL); /* Set TS1_OE */ @@ -1446,7 +1565,6 @@ static int cx23885_stop_dma(struct cx23885_tsport *port) cx_write(PAD_CTRL, reg); cx_write(port->reg_src_sel, 0); cx_write(port->reg_gen_ctrl, 8); - } if (cx23885_boards[dev->board].portb == CX23885_MPEG_ENCODER) @@ -1675,6 +1793,12 @@ static irqreturn_t cx23885_irq(int irq, void *dev_id) pci_status = cx_read(PCI_INT_STAT); pci_mask = cx23885_irq_get_mask(dev); + if ((pci_status & pci_mask) == 0) { + dprintk(7, "pci_status: 0x%08x pci_mask: 0x%08x\n", + pci_status, pci_mask); + goto out; + } + vida_status = cx_read(VID_A_INT_STAT); vida_mask = cx_read(VID_A_INT_MSK); audint_status = cx_read(AUDIO_INT_INT_STAT); @@ -1684,7 +1808,9 @@ static irqreturn_t cx23885_irq(int irq, void *dev_id) ts2_status = cx_read(VID_C_INT_STAT); ts2_mask = cx_read(VID_C_INT_MSK); - if ((pci_status == 0) && (ts2_status == 0) && (ts1_status == 0)) + if (((pci_status & pci_mask) == 0) && + ((ts2_status & ts2_mask) == 0) && + ((ts1_status & ts1_mask) == 0)) goto out; vida_count = cx_read(VID_A_GPCNT); @@ -1811,7 +1937,7 @@ static irqreturn_t cx23885_irq(int irq, void *dev_id) } if (handled) - cx_write(PCI_INT_STAT, pci_status); + cx_write(PCI_INT_STAT, pci_status & pci_mask); out: return IRQ_RETVAL(handled); } @@ -1864,9 +1990,9 @@ static inline int encoder_on_portc(struct cx23885_dev *dev) * and report errors if we think we're tampering with a GPIo that might * be assigned to the encoder (and used for the host bus). * - * GPIO 2 thru 0 - On the cx23885 bridge - * GPIO 18 thru 3 - On the cx23417 host bus interface - * GPIO 23 thru 19 - On the cx25840 a/v core + * GPIO 2 through 0 - On the cx23885 bridge + * GPIO 18 through 3 - On the cx23417 host bus interface + * GPIO 23 through 19 - On the cx25840 a/v core */ void cx23885_gpio_set(struct cx23885_dev *dev, u32 mask) { @@ -1944,6 +2070,53 @@ void cx23885_gpio_enable(struct cx23885_dev *dev, u32 mask, int asoutput) /* TODO: 23-19 */ } +static struct { + int vendor, dev; +} const broken_dev_id[] = { + /* According with + * https://openbenchmarking.org/system/1703021-RI-AMDZEN08075/Ryzen%207%201800X/lspci, + * 0x1451 is PCI ID for the IOMMU found on Ryzen + */ + { PCI_VENDOR_ID_AMD, 0x1451 }, + /* According to sudo lspci -nn, + * 0x1423 is the PCI ID for the IOMMU found on Kaveri + */ + { PCI_VENDOR_ID_AMD, 0x1423 }, + /* 0x1481 is the PCI ID for the IOMMU found on Starship/Matisse + */ + { PCI_VENDOR_ID_AMD, 0x1481 }, + /* 0x1419 is the PCI ID for the IOMMU found on 15h (Models 10h-1fh) family + */ + { PCI_VENDOR_ID_AMD, 0x1419 }, + /* 0x1631 is the PCI ID for the IOMMU found on Renoir/Cezanne + */ + { PCI_VENDOR_ID_AMD, 0x1631 }, + /* 0x5a23 is the PCI ID for the IOMMU found on RD890S/RD990 + */ + { PCI_VENDOR_ID_ATI, 0x5a23 }, +}; + +static bool cx23885_does_need_dma_reset(void) +{ + int i; + struct pci_dev *pdev = NULL; + + if (dma_reset_workaround == 0) + return false; + else if (dma_reset_workaround == 2) + return true; + + for (i = 0; i < ARRAY_SIZE(broken_dev_id); i++) { + pdev = pci_get_device(broken_dev_id[i].vendor, + broken_dev_id[i].dev, NULL); + if (pdev) { + pci_dev_put(pdev); + return true; + } + } + return false; +} + static int cx23885_initdev(struct pci_dev *pci_dev, const struct pci_device_id *pci_id) { @@ -1955,6 +2128,8 @@ static int cx23885_initdev(struct pci_dev *pci_dev, if (NULL == dev) return -ENOMEM; + dev->need_dma_reset = cx23885_does_need_dma_reset(); + err = v4l2_device_register(&pci_dev->dev, &dev->v4l2_dev); if (err < 0) goto fail_free; @@ -1992,10 +2167,10 @@ static int cx23885_initdev(struct pci_dev *pci_dev, (unsigned long long)pci_resource_start(pci_dev, 0)); pci_set_master(pci_dev); - err = pci_set_dma_mask(pci_dev, 0xffffffff); + err = dma_set_mask(&pci_dev->dev, 0xffffffff); if (err) { pr_err("%s/0: Oops: no 32bit PCI DMA ???\n", dev->name); - goto fail_ctrl; + goto fail_dma_set_mask; } err = request_irq(pci_dev->irq, cx23885_irq, @@ -2003,7 +2178,7 @@ static int cx23885_initdev(struct pci_dev *pci_dev, if (err < 0) { pr_err("%s: can't get IRQ %d\n", dev->name, pci_dev->irq); - goto fail_irq; + goto fail_dma_set_mask; } switch (dev->board) { @@ -2025,7 +2200,7 @@ static int cx23885_initdev(struct pci_dev *pci_dev, return 0; -fail_irq: +fail_dma_set_mask: cx23885_dev_unregister(dev); fail_ctrl: v4l2_ctrl_handler_free(hdl); @@ -2056,7 +2231,29 @@ static void cx23885_finidev(struct pci_dev *pci_dev) kfree(dev); } -static struct pci_device_id cx23885_pci_tbl[] = { +static int __maybe_unused cx23885_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 cx23885_dev *dev = to_cx23885(v4l2_dev); + + cx23885_shutdown(dev); + + return 0; +} + +static int __maybe_unused cx23885_resume(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 cx23885_dev *dev = to_cx23885(v4l2_dev); + + cx23885_reset(dev); + + return 0; +} + +static const struct pci_device_id cx23885_pci_tbl[] = { { /* CX23885 */ .vendor = 0x14f1, @@ -2075,14 +2272,14 @@ static struct pci_device_id cx23885_pci_tbl[] = { }; MODULE_DEVICE_TABLE(pci, cx23885_pci_tbl); +static SIMPLE_DEV_PM_OPS(cx23885_pm_ops, cx23885_suspend, cx23885_resume); + static struct pci_driver cx23885_pci_driver = { - .name = "cx23885", - .id_table = cx23885_pci_tbl, - .probe = cx23885_initdev, - .remove = cx23885_finidev, - /* TODO */ - .suspend = NULL, - .resume = NULL, + .name = "cx23885", + .id_table = cx23885_pci_tbl, + .probe = cx23885_initdev, + .remove = cx23885_finidev, + .driver.pm = &cx23885_pm_ops, }; static int __init cx23885_init(void) |
