18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Support for the mpeg transport stream transfers 58c2ecf20Sopenharmony_ci * PCI function #2 of the cx2388x. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * (c) 2004 Jelle Foks <jelle@foks.us> 88c2ecf20Sopenharmony_ci * (c) 2004 Chris Pascoe <c.pascoe@itee.uq.edu.au> 98c2ecf20Sopenharmony_ci * (c) 2004 Gerd Knorr <kraxel@bytesex.org> 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include "cx88.h" 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/slab.h> 168c2ecf20Sopenharmony_ci#include <linux/init.h> 178c2ecf20Sopenharmony_ci#include <linux/device.h> 188c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h> 198c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 208c2ecf20Sopenharmony_ci#include <linux/delay.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------ */ 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("mpeg driver for cx2388x based TV cards"); 258c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jelle Foks <jelle@foks.us>"); 268c2ecf20Sopenharmony_ciMODULE_AUTHOR("Chris Pascoe <c.pascoe@itee.uq.edu.au>"); 278c2ecf20Sopenharmony_ciMODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); 288c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 298c2ecf20Sopenharmony_ciMODULE_VERSION(CX88_VERSION); 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistatic unsigned int debug; 328c2ecf20Sopenharmony_cimodule_param(debug, int, 0644); 338c2ecf20Sopenharmony_ciMODULE_PARM_DESC(debug, "enable debug messages [mpeg]"); 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci#define dprintk(level, fmt, arg...) do { \ 368c2ecf20Sopenharmony_ci if (debug + 1 > level) \ 378c2ecf20Sopenharmony_ci printk(KERN_DEBUG pr_fmt("%s: mpeg:" fmt), \ 388c2ecf20Sopenharmony_ci __func__, ##arg); \ 398c2ecf20Sopenharmony_ci} while (0) 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci#if defined(CONFIG_MODULES) && defined(MODULE) 428c2ecf20Sopenharmony_cistatic void request_module_async(struct work_struct *work) 438c2ecf20Sopenharmony_ci{ 448c2ecf20Sopenharmony_ci struct cx8802_dev *dev = container_of(work, struct cx8802_dev, 458c2ecf20Sopenharmony_ci request_module_wk); 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci if (dev->core->board.mpeg & CX88_MPEG_DVB) 488c2ecf20Sopenharmony_ci request_module("cx88-dvb"); 498c2ecf20Sopenharmony_ci if (dev->core->board.mpeg & CX88_MPEG_BLACKBIRD) 508c2ecf20Sopenharmony_ci request_module("cx88-blackbird"); 518c2ecf20Sopenharmony_ci} 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistatic void request_modules(struct cx8802_dev *dev) 548c2ecf20Sopenharmony_ci{ 558c2ecf20Sopenharmony_ci INIT_WORK(&dev->request_module_wk, request_module_async); 568c2ecf20Sopenharmony_ci schedule_work(&dev->request_module_wk); 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic void flush_request_modules(struct cx8802_dev *dev) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci flush_work(&dev->request_module_wk); 628c2ecf20Sopenharmony_ci} 638c2ecf20Sopenharmony_ci#else 648c2ecf20Sopenharmony_ci#define request_modules(dev) 658c2ecf20Sopenharmony_ci#define flush_request_modules(dev) 668c2ecf20Sopenharmony_ci#endif /* CONFIG_MODULES */ 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic LIST_HEAD(cx8802_devlist); 698c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(cx8802_mutex); 708c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------ */ 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ciint cx8802_start_dma(struct cx8802_dev *dev, 738c2ecf20Sopenharmony_ci struct cx88_dmaqueue *q, 748c2ecf20Sopenharmony_ci struct cx88_buffer *buf) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci struct cx88_core *core = dev->core; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci dprintk(1, "w: %d, h: %d, f: %d\n", 798c2ecf20Sopenharmony_ci core->width, core->height, core->field); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci /* setup fifo + format */ 828c2ecf20Sopenharmony_ci cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH28], 838c2ecf20Sopenharmony_ci dev->ts_packet_size, buf->risc.dma); 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci /* write TS length to chip */ 868c2ecf20Sopenharmony_ci cx_write(MO_TS_LNGTH, dev->ts_packet_size); 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci /* 898c2ecf20Sopenharmony_ci * FIXME: this needs a review. 908c2ecf20Sopenharmony_ci * also: move to cx88-blackbird + cx88-dvb source files? 918c2ecf20Sopenharmony_ci */ 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci dprintk(1, "core->active_type_id = 0x%08x\n", core->active_type_id); 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci if ((core->active_type_id == CX88_MPEG_DVB) && 968c2ecf20Sopenharmony_ci (core->board.mpeg & CX88_MPEG_DVB)) { 978c2ecf20Sopenharmony_ci dprintk(1, "cx8802_start_dma doing .dvb\n"); 988c2ecf20Sopenharmony_ci /* negedge driven & software reset */ 998c2ecf20Sopenharmony_ci cx_write(TS_GEN_CNTRL, 0x0040 | dev->ts_gen_cntrl); 1008c2ecf20Sopenharmony_ci udelay(100); 1018c2ecf20Sopenharmony_ci cx_write(MO_PINMUX_IO, 0x00); 1028c2ecf20Sopenharmony_ci cx_write(TS_HW_SOP_CNTRL, 0x47 << 16 | 188 << 4 | 0x01); 1038c2ecf20Sopenharmony_ci switch (core->boardnr) { 1048c2ecf20Sopenharmony_ci case CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q: 1058c2ecf20Sopenharmony_ci case CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T: 1068c2ecf20Sopenharmony_ci case CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD: 1078c2ecf20Sopenharmony_ci case CX88_BOARD_PCHDTV_HD5500: 1088c2ecf20Sopenharmony_ci cx_write(TS_SOP_STAT, 1 << 13); 1098c2ecf20Sopenharmony_ci break; 1108c2ecf20Sopenharmony_ci case CX88_BOARD_SAMSUNG_SMT_7020: 1118c2ecf20Sopenharmony_ci cx_write(TS_SOP_STAT, 0x00); 1128c2ecf20Sopenharmony_ci break; 1138c2ecf20Sopenharmony_ci case CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1: 1148c2ecf20Sopenharmony_ci case CX88_BOARD_HAUPPAUGE_NOVASE2_S1: 1158c2ecf20Sopenharmony_ci /* Enable MPEG parallel IO and video signal pins */ 1168c2ecf20Sopenharmony_ci cx_write(MO_PINMUX_IO, 0x88); 1178c2ecf20Sopenharmony_ci udelay(100); 1188c2ecf20Sopenharmony_ci break; 1198c2ecf20Sopenharmony_ci case CX88_BOARD_HAUPPAUGE_HVR1300: 1208c2ecf20Sopenharmony_ci /* Enable MPEG parallel IO and video signal pins */ 1218c2ecf20Sopenharmony_ci cx_write(MO_PINMUX_IO, 0x88); 1228c2ecf20Sopenharmony_ci cx_write(TS_SOP_STAT, 0); 1238c2ecf20Sopenharmony_ci cx_write(TS_VALERR_CNTRL, 0); 1248c2ecf20Sopenharmony_ci break; 1258c2ecf20Sopenharmony_ci case CX88_BOARD_PINNACLE_PCTV_HD_800i: 1268c2ecf20Sopenharmony_ci /* Enable MPEG parallel IO and video signal pins */ 1278c2ecf20Sopenharmony_ci cx_write(MO_PINMUX_IO, 0x88); 1288c2ecf20Sopenharmony_ci cx_write(TS_HW_SOP_CNTRL, (0x47 << 16) | (188 << 4)); 1298c2ecf20Sopenharmony_ci dev->ts_gen_cntrl = 5; 1308c2ecf20Sopenharmony_ci cx_write(TS_SOP_STAT, 0); 1318c2ecf20Sopenharmony_ci cx_write(TS_VALERR_CNTRL, 0); 1328c2ecf20Sopenharmony_ci udelay(100); 1338c2ecf20Sopenharmony_ci break; 1348c2ecf20Sopenharmony_ci default: 1358c2ecf20Sopenharmony_ci cx_write(TS_SOP_STAT, 0x00); 1368c2ecf20Sopenharmony_ci break; 1378c2ecf20Sopenharmony_ci } 1388c2ecf20Sopenharmony_ci cx_write(TS_GEN_CNTRL, dev->ts_gen_cntrl); 1398c2ecf20Sopenharmony_ci udelay(100); 1408c2ecf20Sopenharmony_ci } else if ((core->active_type_id == CX88_MPEG_BLACKBIRD) && 1418c2ecf20Sopenharmony_ci (core->board.mpeg & CX88_MPEG_BLACKBIRD)) { 1428c2ecf20Sopenharmony_ci dprintk(1, "cx8802_start_dma doing .blackbird\n"); 1438c2ecf20Sopenharmony_ci cx_write(MO_PINMUX_IO, 0x88); /* enable MPEG parallel IO */ 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci /* punctured clock TS & posedge driven & software reset */ 1468c2ecf20Sopenharmony_ci cx_write(TS_GEN_CNTRL, 0x46); 1478c2ecf20Sopenharmony_ci udelay(100); 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci cx_write(TS_HW_SOP_CNTRL, 0x408); /* mpeg start byte */ 1508c2ecf20Sopenharmony_ci cx_write(TS_VALERR_CNTRL, 0x2000); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci /* punctured clock TS & posedge driven */ 1538c2ecf20Sopenharmony_ci cx_write(TS_GEN_CNTRL, 0x06); 1548c2ecf20Sopenharmony_ci udelay(100); 1558c2ecf20Sopenharmony_ci } else { 1568c2ecf20Sopenharmony_ci pr_err("%s() Failed. Unsupported value in .mpeg (0x%08x)\n", 1578c2ecf20Sopenharmony_ci __func__, core->board.mpeg); 1588c2ecf20Sopenharmony_ci return -EINVAL; 1598c2ecf20Sopenharmony_ci } 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci /* reset counter */ 1628c2ecf20Sopenharmony_ci cx_write(MO_TS_GPCNTRL, GP_COUNT_CONTROL_RESET); 1638c2ecf20Sopenharmony_ci q->count = 0; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci /* clear interrupt status register */ 1668c2ecf20Sopenharmony_ci cx_write(MO_TS_INTSTAT, 0x1f1111); 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci /* enable irqs */ 1698c2ecf20Sopenharmony_ci dprintk(1, "setting the interrupt mask\n"); 1708c2ecf20Sopenharmony_ci cx_set(MO_PCI_INTMSK, core->pci_irqmask | PCI_INT_TSINT); 1718c2ecf20Sopenharmony_ci cx_set(MO_TS_INTMSK, 0x1f0011); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci /* start dma */ 1748c2ecf20Sopenharmony_ci cx_set(MO_DEV_CNTRL2, (1 << 5)); 1758c2ecf20Sopenharmony_ci cx_set(MO_TS_DMACNTRL, 0x11); 1768c2ecf20Sopenharmony_ci return 0; 1778c2ecf20Sopenharmony_ci} 1788c2ecf20Sopenharmony_ciEXPORT_SYMBOL(cx8802_start_dma); 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_cistatic int cx8802_stop_dma(struct cx8802_dev *dev) 1818c2ecf20Sopenharmony_ci{ 1828c2ecf20Sopenharmony_ci struct cx88_core *core = dev->core; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci dprintk(1, "\n"); 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci /* stop dma */ 1878c2ecf20Sopenharmony_ci cx_clear(MO_TS_DMACNTRL, 0x11); 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci /* disable irqs */ 1908c2ecf20Sopenharmony_ci cx_clear(MO_PCI_INTMSK, PCI_INT_TSINT); 1918c2ecf20Sopenharmony_ci cx_clear(MO_TS_INTMSK, 0x1f0011); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci /* Reset the controller */ 1948c2ecf20Sopenharmony_ci cx_write(TS_GEN_CNTRL, 0xcd); 1958c2ecf20Sopenharmony_ci return 0; 1968c2ecf20Sopenharmony_ci} 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_cistatic int cx8802_restart_queue(struct cx8802_dev *dev, 1998c2ecf20Sopenharmony_ci struct cx88_dmaqueue *q) 2008c2ecf20Sopenharmony_ci{ 2018c2ecf20Sopenharmony_ci struct cx88_buffer *buf; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci dprintk(1, "\n"); 2048c2ecf20Sopenharmony_ci if (list_empty(&q->active)) 2058c2ecf20Sopenharmony_ci return 0; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci buf = list_entry(q->active.next, struct cx88_buffer, list); 2088c2ecf20Sopenharmony_ci dprintk(2, "restart_queue [%p/%d]: restart dma\n", 2098c2ecf20Sopenharmony_ci buf, buf->vb.vb2_buf.index); 2108c2ecf20Sopenharmony_ci cx8802_start_dma(dev, q, buf); 2118c2ecf20Sopenharmony_ci return 0; 2128c2ecf20Sopenharmony_ci} 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------ */ 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ciint cx8802_buf_prepare(struct vb2_queue *q, struct cx8802_dev *dev, 2178c2ecf20Sopenharmony_ci struct cx88_buffer *buf) 2188c2ecf20Sopenharmony_ci{ 2198c2ecf20Sopenharmony_ci int size = dev->ts_packet_size * dev->ts_packet_count; 2208c2ecf20Sopenharmony_ci struct sg_table *sgt = vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0); 2218c2ecf20Sopenharmony_ci struct cx88_riscmem *risc = &buf->risc; 2228c2ecf20Sopenharmony_ci int rc; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci if (vb2_plane_size(&buf->vb.vb2_buf, 0) < size) 2258c2ecf20Sopenharmony_ci return -EINVAL; 2268c2ecf20Sopenharmony_ci vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size); 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci rc = cx88_risc_databuffer(dev->pci, risc, sgt->sgl, 2298c2ecf20Sopenharmony_ci dev->ts_packet_size, dev->ts_packet_count, 0); 2308c2ecf20Sopenharmony_ci if (rc) { 2318c2ecf20Sopenharmony_ci if (risc->cpu) 2328c2ecf20Sopenharmony_ci pci_free_consistent(dev->pci, risc->size, 2338c2ecf20Sopenharmony_ci risc->cpu, risc->dma); 2348c2ecf20Sopenharmony_ci memset(risc, 0, sizeof(*risc)); 2358c2ecf20Sopenharmony_ci return rc; 2368c2ecf20Sopenharmony_ci } 2378c2ecf20Sopenharmony_ci return 0; 2388c2ecf20Sopenharmony_ci} 2398c2ecf20Sopenharmony_ciEXPORT_SYMBOL(cx8802_buf_prepare); 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_civoid cx8802_buf_queue(struct cx8802_dev *dev, struct cx88_buffer *buf) 2428c2ecf20Sopenharmony_ci{ 2438c2ecf20Sopenharmony_ci struct cx88_buffer *prev; 2448c2ecf20Sopenharmony_ci struct cx88_dmaqueue *cx88q = &dev->mpegq; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci dprintk(1, "\n"); 2478c2ecf20Sopenharmony_ci /* add jump to start */ 2488c2ecf20Sopenharmony_ci buf->risc.cpu[1] = cpu_to_le32(buf->risc.dma + 8); 2498c2ecf20Sopenharmony_ci buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_CNT_INC); 2508c2ecf20Sopenharmony_ci buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma + 8); 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci if (list_empty(&cx88q->active)) { 2538c2ecf20Sopenharmony_ci dprintk(1, "queue is empty - first active\n"); 2548c2ecf20Sopenharmony_ci list_add_tail(&buf->list, &cx88q->active); 2558c2ecf20Sopenharmony_ci dprintk(1, "[%p/%d] %s - first active\n", 2568c2ecf20Sopenharmony_ci buf, buf->vb.vb2_buf.index, __func__); 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci } else { 2598c2ecf20Sopenharmony_ci buf->risc.cpu[0] |= cpu_to_le32(RISC_IRQ1); 2608c2ecf20Sopenharmony_ci dprintk(1, "queue is not empty - append to active\n"); 2618c2ecf20Sopenharmony_ci prev = list_entry(cx88q->active.prev, struct cx88_buffer, list); 2628c2ecf20Sopenharmony_ci list_add_tail(&buf->list, &cx88q->active); 2638c2ecf20Sopenharmony_ci prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); 2648c2ecf20Sopenharmony_ci dprintk(1, "[%p/%d] %s - append to active\n", 2658c2ecf20Sopenharmony_ci buf, buf->vb.vb2_buf.index, __func__); 2668c2ecf20Sopenharmony_ci } 2678c2ecf20Sopenharmony_ci} 2688c2ecf20Sopenharmony_ciEXPORT_SYMBOL(cx8802_buf_queue); 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci/* ----------------------------------------------------------- */ 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_cistatic void do_cancel_buffers(struct cx8802_dev *dev) 2738c2ecf20Sopenharmony_ci{ 2748c2ecf20Sopenharmony_ci struct cx88_dmaqueue *q = &dev->mpegq; 2758c2ecf20Sopenharmony_ci struct cx88_buffer *buf; 2768c2ecf20Sopenharmony_ci unsigned long flags; 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci spin_lock_irqsave(&dev->slock, flags); 2798c2ecf20Sopenharmony_ci while (!list_empty(&q->active)) { 2808c2ecf20Sopenharmony_ci buf = list_entry(q->active.next, struct cx88_buffer, list); 2818c2ecf20Sopenharmony_ci list_del(&buf->list); 2828c2ecf20Sopenharmony_ci vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); 2838c2ecf20Sopenharmony_ci } 2848c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&dev->slock, flags); 2858c2ecf20Sopenharmony_ci} 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_civoid cx8802_cancel_buffers(struct cx8802_dev *dev) 2888c2ecf20Sopenharmony_ci{ 2898c2ecf20Sopenharmony_ci dprintk(1, "\n"); 2908c2ecf20Sopenharmony_ci cx8802_stop_dma(dev); 2918c2ecf20Sopenharmony_ci do_cancel_buffers(dev); 2928c2ecf20Sopenharmony_ci} 2938c2ecf20Sopenharmony_ciEXPORT_SYMBOL(cx8802_cancel_buffers); 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_cistatic const char *cx88_mpeg_irqs[32] = { 2968c2ecf20Sopenharmony_ci "ts_risci1", NULL, NULL, NULL, 2978c2ecf20Sopenharmony_ci "ts_risci2", NULL, NULL, NULL, 2988c2ecf20Sopenharmony_ci "ts_oflow", NULL, NULL, NULL, 2998c2ecf20Sopenharmony_ci "ts_sync", NULL, NULL, NULL, 3008c2ecf20Sopenharmony_ci "opc_err", "par_err", "rip_err", "pci_abort", 3018c2ecf20Sopenharmony_ci "ts_err?", 3028c2ecf20Sopenharmony_ci}; 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_cistatic void cx8802_mpeg_irq(struct cx8802_dev *dev) 3058c2ecf20Sopenharmony_ci{ 3068c2ecf20Sopenharmony_ci struct cx88_core *core = dev->core; 3078c2ecf20Sopenharmony_ci u32 status, mask, count; 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci dprintk(1, "\n"); 3108c2ecf20Sopenharmony_ci status = cx_read(MO_TS_INTSTAT); 3118c2ecf20Sopenharmony_ci mask = cx_read(MO_TS_INTMSK); 3128c2ecf20Sopenharmony_ci if (0 == (status & mask)) 3138c2ecf20Sopenharmony_ci return; 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ci cx_write(MO_TS_INTSTAT, status); 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci if (debug || (status & mask & ~0xff)) 3188c2ecf20Sopenharmony_ci cx88_print_irqbits("irq mpeg ", 3198c2ecf20Sopenharmony_ci cx88_mpeg_irqs, ARRAY_SIZE(cx88_mpeg_irqs), 3208c2ecf20Sopenharmony_ci status, mask); 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci /* risc op code error */ 3238c2ecf20Sopenharmony_ci if (status & (1 << 16)) { 3248c2ecf20Sopenharmony_ci pr_warn("mpeg risc op code error\n"); 3258c2ecf20Sopenharmony_ci cx_clear(MO_TS_DMACNTRL, 0x11); 3268c2ecf20Sopenharmony_ci cx88_sram_channel_dump(dev->core, 3278c2ecf20Sopenharmony_ci &cx88_sram_channels[SRAM_CH28]); 3288c2ecf20Sopenharmony_ci } 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci /* risc1 y */ 3318c2ecf20Sopenharmony_ci if (status & 0x01) { 3328c2ecf20Sopenharmony_ci dprintk(1, "wake up\n"); 3338c2ecf20Sopenharmony_ci spin_lock(&dev->slock); 3348c2ecf20Sopenharmony_ci count = cx_read(MO_TS_GPCNT); 3358c2ecf20Sopenharmony_ci cx88_wakeup(dev->core, &dev->mpegq, count); 3368c2ecf20Sopenharmony_ci spin_unlock(&dev->slock); 3378c2ecf20Sopenharmony_ci } 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_ci /* other general errors */ 3408c2ecf20Sopenharmony_ci if (status & 0x1f0100) { 3418c2ecf20Sopenharmony_ci dprintk(0, "general errors: 0x%08x\n", status & 0x1f0100); 3428c2ecf20Sopenharmony_ci spin_lock(&dev->slock); 3438c2ecf20Sopenharmony_ci cx8802_stop_dma(dev); 3448c2ecf20Sopenharmony_ci spin_unlock(&dev->slock); 3458c2ecf20Sopenharmony_ci } 3468c2ecf20Sopenharmony_ci} 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_ci#define MAX_IRQ_LOOP 10 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_cistatic irqreturn_t cx8802_irq(int irq, void *dev_id) 3518c2ecf20Sopenharmony_ci{ 3528c2ecf20Sopenharmony_ci struct cx8802_dev *dev = dev_id; 3538c2ecf20Sopenharmony_ci struct cx88_core *core = dev->core; 3548c2ecf20Sopenharmony_ci u32 status; 3558c2ecf20Sopenharmony_ci int loop, handled = 0; 3568c2ecf20Sopenharmony_ci 3578c2ecf20Sopenharmony_ci for (loop = 0; loop < MAX_IRQ_LOOP; loop++) { 3588c2ecf20Sopenharmony_ci status = cx_read(MO_PCI_INTSTAT) & 3598c2ecf20Sopenharmony_ci (core->pci_irqmask | PCI_INT_TSINT); 3608c2ecf20Sopenharmony_ci if (status == 0) 3618c2ecf20Sopenharmony_ci goto out; 3628c2ecf20Sopenharmony_ci dprintk(1, "cx8802_irq\n"); 3638c2ecf20Sopenharmony_ci dprintk(1, " loop: %d/%d\n", loop, MAX_IRQ_LOOP); 3648c2ecf20Sopenharmony_ci dprintk(1, " status: %d\n", status); 3658c2ecf20Sopenharmony_ci handled = 1; 3668c2ecf20Sopenharmony_ci cx_write(MO_PCI_INTSTAT, status); 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_ci if (status & core->pci_irqmask) 3698c2ecf20Sopenharmony_ci cx88_core_irq(core, status); 3708c2ecf20Sopenharmony_ci if (status & PCI_INT_TSINT) 3718c2ecf20Sopenharmony_ci cx8802_mpeg_irq(dev); 3728c2ecf20Sopenharmony_ci } 3738c2ecf20Sopenharmony_ci if (loop == MAX_IRQ_LOOP) { 3748c2ecf20Sopenharmony_ci dprintk(0, "clearing mask\n"); 3758c2ecf20Sopenharmony_ci pr_warn("irq loop -- clearing mask\n"); 3768c2ecf20Sopenharmony_ci cx_write(MO_PCI_INTMSK, 0); 3778c2ecf20Sopenharmony_ci } 3788c2ecf20Sopenharmony_ci 3798c2ecf20Sopenharmony_ci out: 3808c2ecf20Sopenharmony_ci return IRQ_RETVAL(handled); 3818c2ecf20Sopenharmony_ci} 3828c2ecf20Sopenharmony_ci 3838c2ecf20Sopenharmony_cistatic int cx8802_init_common(struct cx8802_dev *dev) 3848c2ecf20Sopenharmony_ci{ 3858c2ecf20Sopenharmony_ci struct cx88_core *core = dev->core; 3868c2ecf20Sopenharmony_ci int err; 3878c2ecf20Sopenharmony_ci 3888c2ecf20Sopenharmony_ci /* pci init */ 3898c2ecf20Sopenharmony_ci if (pci_enable_device(dev->pci)) 3908c2ecf20Sopenharmony_ci return -EIO; 3918c2ecf20Sopenharmony_ci pci_set_master(dev->pci); 3928c2ecf20Sopenharmony_ci err = pci_set_dma_mask(dev->pci, DMA_BIT_MASK(32)); 3938c2ecf20Sopenharmony_ci if (err) { 3948c2ecf20Sopenharmony_ci pr_err("Oops: no 32bit PCI DMA ???\n"); 3958c2ecf20Sopenharmony_ci return -EIO; 3968c2ecf20Sopenharmony_ci } 3978c2ecf20Sopenharmony_ci 3988c2ecf20Sopenharmony_ci dev->pci_rev = dev->pci->revision; 3998c2ecf20Sopenharmony_ci pci_read_config_byte(dev->pci, PCI_LATENCY_TIMER, &dev->pci_lat); 4008c2ecf20Sopenharmony_ci pr_info("found at %s, rev: %d, irq: %d, latency: %d, mmio: 0x%llx\n", 4018c2ecf20Sopenharmony_ci pci_name(dev->pci), dev->pci_rev, dev->pci->irq, 4028c2ecf20Sopenharmony_ci dev->pci_lat, 4038c2ecf20Sopenharmony_ci (unsigned long long)pci_resource_start(dev->pci, 0)); 4048c2ecf20Sopenharmony_ci 4058c2ecf20Sopenharmony_ci /* initialize driver struct */ 4068c2ecf20Sopenharmony_ci spin_lock_init(&dev->slock); 4078c2ecf20Sopenharmony_ci 4088c2ecf20Sopenharmony_ci /* init dma queue */ 4098c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&dev->mpegq.active); 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_ci /* get irq */ 4128c2ecf20Sopenharmony_ci err = request_irq(dev->pci->irq, cx8802_irq, 4138c2ecf20Sopenharmony_ci IRQF_SHARED, dev->core->name, dev); 4148c2ecf20Sopenharmony_ci if (err < 0) { 4158c2ecf20Sopenharmony_ci pr_err("can't get IRQ %d\n", dev->pci->irq); 4168c2ecf20Sopenharmony_ci return err; 4178c2ecf20Sopenharmony_ci } 4188c2ecf20Sopenharmony_ci cx_set(MO_PCI_INTMSK, core->pci_irqmask); 4198c2ecf20Sopenharmony_ci 4208c2ecf20Sopenharmony_ci /* everything worked */ 4218c2ecf20Sopenharmony_ci pci_set_drvdata(dev->pci, dev); 4228c2ecf20Sopenharmony_ci return 0; 4238c2ecf20Sopenharmony_ci} 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_cistatic void cx8802_fini_common(struct cx8802_dev *dev) 4268c2ecf20Sopenharmony_ci{ 4278c2ecf20Sopenharmony_ci dprintk(2, "\n"); 4288c2ecf20Sopenharmony_ci cx8802_stop_dma(dev); 4298c2ecf20Sopenharmony_ci pci_disable_device(dev->pci); 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_ci /* unregister stuff */ 4328c2ecf20Sopenharmony_ci free_irq(dev->pci->irq, dev); 4338c2ecf20Sopenharmony_ci} 4348c2ecf20Sopenharmony_ci 4358c2ecf20Sopenharmony_ci/* ----------------------------------------------------------- */ 4368c2ecf20Sopenharmony_ci 4378c2ecf20Sopenharmony_cistatic int cx8802_suspend_common(struct pci_dev *pci_dev, pm_message_t state) 4388c2ecf20Sopenharmony_ci{ 4398c2ecf20Sopenharmony_ci struct cx8802_dev *dev = pci_get_drvdata(pci_dev); 4408c2ecf20Sopenharmony_ci unsigned long flags; 4418c2ecf20Sopenharmony_ci 4428c2ecf20Sopenharmony_ci /* stop mpeg dma */ 4438c2ecf20Sopenharmony_ci spin_lock_irqsave(&dev->slock, flags); 4448c2ecf20Sopenharmony_ci if (!list_empty(&dev->mpegq.active)) { 4458c2ecf20Sopenharmony_ci dprintk(2, "suspend\n"); 4468c2ecf20Sopenharmony_ci pr_info("suspend mpeg\n"); 4478c2ecf20Sopenharmony_ci cx8802_stop_dma(dev); 4488c2ecf20Sopenharmony_ci } 4498c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&dev->slock, flags); 4508c2ecf20Sopenharmony_ci 4518c2ecf20Sopenharmony_ci /* FIXME -- shutdown device */ 4528c2ecf20Sopenharmony_ci cx88_shutdown(dev->core); 4538c2ecf20Sopenharmony_ci 4548c2ecf20Sopenharmony_ci pci_save_state(pci_dev); 4558c2ecf20Sopenharmony_ci if (pci_set_power_state(pci_dev, 4568c2ecf20Sopenharmony_ci pci_choose_state(pci_dev, state)) != 0) { 4578c2ecf20Sopenharmony_ci pci_disable_device(pci_dev); 4588c2ecf20Sopenharmony_ci dev->state.disabled = 1; 4598c2ecf20Sopenharmony_ci } 4608c2ecf20Sopenharmony_ci return 0; 4618c2ecf20Sopenharmony_ci} 4628c2ecf20Sopenharmony_ci 4638c2ecf20Sopenharmony_cistatic int cx8802_resume_common(struct pci_dev *pci_dev) 4648c2ecf20Sopenharmony_ci{ 4658c2ecf20Sopenharmony_ci struct cx8802_dev *dev = pci_get_drvdata(pci_dev); 4668c2ecf20Sopenharmony_ci unsigned long flags; 4678c2ecf20Sopenharmony_ci int err; 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ci if (dev->state.disabled) { 4708c2ecf20Sopenharmony_ci err = pci_enable_device(pci_dev); 4718c2ecf20Sopenharmony_ci if (err) { 4728c2ecf20Sopenharmony_ci pr_err("can't enable device\n"); 4738c2ecf20Sopenharmony_ci return err; 4748c2ecf20Sopenharmony_ci } 4758c2ecf20Sopenharmony_ci dev->state.disabled = 0; 4768c2ecf20Sopenharmony_ci } 4778c2ecf20Sopenharmony_ci err = pci_set_power_state(pci_dev, PCI_D0); 4788c2ecf20Sopenharmony_ci if (err) { 4798c2ecf20Sopenharmony_ci pr_err("can't enable device\n"); 4808c2ecf20Sopenharmony_ci pci_disable_device(pci_dev); 4818c2ecf20Sopenharmony_ci dev->state.disabled = 1; 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_ci return err; 4848c2ecf20Sopenharmony_ci } 4858c2ecf20Sopenharmony_ci pci_restore_state(pci_dev); 4868c2ecf20Sopenharmony_ci 4878c2ecf20Sopenharmony_ci /* FIXME: re-initialize hardware */ 4888c2ecf20Sopenharmony_ci cx88_reset(dev->core); 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_ci /* restart video+vbi capture */ 4918c2ecf20Sopenharmony_ci spin_lock_irqsave(&dev->slock, flags); 4928c2ecf20Sopenharmony_ci if (!list_empty(&dev->mpegq.active)) { 4938c2ecf20Sopenharmony_ci pr_info("resume mpeg\n"); 4948c2ecf20Sopenharmony_ci cx8802_restart_queue(dev, &dev->mpegq); 4958c2ecf20Sopenharmony_ci } 4968c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&dev->slock, flags); 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci return 0; 4998c2ecf20Sopenharmony_ci} 5008c2ecf20Sopenharmony_ci 5018c2ecf20Sopenharmony_cistruct cx8802_driver *cx8802_get_driver(struct cx8802_dev *dev, 5028c2ecf20Sopenharmony_ci enum cx88_board_type btype) 5038c2ecf20Sopenharmony_ci{ 5048c2ecf20Sopenharmony_ci struct cx8802_driver *d; 5058c2ecf20Sopenharmony_ci 5068c2ecf20Sopenharmony_ci list_for_each_entry(d, &dev->drvlist, drvlist) 5078c2ecf20Sopenharmony_ci if (d->type_id == btype) 5088c2ecf20Sopenharmony_ci return d; 5098c2ecf20Sopenharmony_ci 5108c2ecf20Sopenharmony_ci return NULL; 5118c2ecf20Sopenharmony_ci} 5128c2ecf20Sopenharmony_ciEXPORT_SYMBOL(cx8802_get_driver); 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_ci/* Driver asked for hardware access. */ 5158c2ecf20Sopenharmony_cistatic int cx8802_request_acquire(struct cx8802_driver *drv) 5168c2ecf20Sopenharmony_ci{ 5178c2ecf20Sopenharmony_ci struct cx88_core *core = drv->core; 5188c2ecf20Sopenharmony_ci unsigned int i; 5198c2ecf20Sopenharmony_ci 5208c2ecf20Sopenharmony_ci /* Fail a request for hardware if the device is busy. */ 5218c2ecf20Sopenharmony_ci if (core->active_type_id != CX88_BOARD_NONE && 5228c2ecf20Sopenharmony_ci core->active_type_id != drv->type_id) 5238c2ecf20Sopenharmony_ci return -EBUSY; 5248c2ecf20Sopenharmony_ci 5258c2ecf20Sopenharmony_ci if (drv->type_id == CX88_MPEG_DVB) { 5268c2ecf20Sopenharmony_ci /* When switching to DVB, always set the input to the tuner */ 5278c2ecf20Sopenharmony_ci core->last_analog_input = core->input; 5288c2ecf20Sopenharmony_ci core->input = 0; 5298c2ecf20Sopenharmony_ci for (i = 0; 5308c2ecf20Sopenharmony_ci i < (sizeof(core->board.input) / 5318c2ecf20Sopenharmony_ci sizeof(struct cx88_input)); 5328c2ecf20Sopenharmony_ci i++) { 5338c2ecf20Sopenharmony_ci if (core->board.input[i].type == CX88_VMUX_DVB) { 5348c2ecf20Sopenharmony_ci core->input = i; 5358c2ecf20Sopenharmony_ci break; 5368c2ecf20Sopenharmony_ci } 5378c2ecf20Sopenharmony_ci } 5388c2ecf20Sopenharmony_ci } 5398c2ecf20Sopenharmony_ci 5408c2ecf20Sopenharmony_ci if (drv->advise_acquire) { 5418c2ecf20Sopenharmony_ci core->active_ref++; 5428c2ecf20Sopenharmony_ci if (core->active_type_id == CX88_BOARD_NONE) { 5438c2ecf20Sopenharmony_ci core->active_type_id = drv->type_id; 5448c2ecf20Sopenharmony_ci drv->advise_acquire(drv); 5458c2ecf20Sopenharmony_ci } 5468c2ecf20Sopenharmony_ci 5478c2ecf20Sopenharmony_ci dprintk(1, "Post acquire GPIO=%x\n", cx_read(MO_GP0_IO)); 5488c2ecf20Sopenharmony_ci } 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_ci return 0; 5518c2ecf20Sopenharmony_ci} 5528c2ecf20Sopenharmony_ci 5538c2ecf20Sopenharmony_ci/* Driver asked to release hardware. */ 5548c2ecf20Sopenharmony_cistatic int cx8802_request_release(struct cx8802_driver *drv) 5558c2ecf20Sopenharmony_ci{ 5568c2ecf20Sopenharmony_ci struct cx88_core *core = drv->core; 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_ci if (drv->advise_release && --core->active_ref == 0) { 5598c2ecf20Sopenharmony_ci if (drv->type_id == CX88_MPEG_DVB) { 5608c2ecf20Sopenharmony_ci /* 5618c2ecf20Sopenharmony_ci * If the DVB driver is releasing, reset the input 5628c2ecf20Sopenharmony_ci * state to the last configured analog input 5638c2ecf20Sopenharmony_ci */ 5648c2ecf20Sopenharmony_ci core->input = core->last_analog_input; 5658c2ecf20Sopenharmony_ci } 5668c2ecf20Sopenharmony_ci 5678c2ecf20Sopenharmony_ci drv->advise_release(drv); 5688c2ecf20Sopenharmony_ci core->active_type_id = CX88_BOARD_NONE; 5698c2ecf20Sopenharmony_ci dprintk(1, "Post release GPIO=%x\n", cx_read(MO_GP0_IO)); 5708c2ecf20Sopenharmony_ci } 5718c2ecf20Sopenharmony_ci 5728c2ecf20Sopenharmony_ci return 0; 5738c2ecf20Sopenharmony_ci} 5748c2ecf20Sopenharmony_ci 5758c2ecf20Sopenharmony_cistatic int cx8802_check_driver(struct cx8802_driver *drv) 5768c2ecf20Sopenharmony_ci{ 5778c2ecf20Sopenharmony_ci if (!drv) 5788c2ecf20Sopenharmony_ci return -ENODEV; 5798c2ecf20Sopenharmony_ci 5808c2ecf20Sopenharmony_ci if ((drv->type_id != CX88_MPEG_DVB) && 5818c2ecf20Sopenharmony_ci (drv->type_id != CX88_MPEG_BLACKBIRD)) 5828c2ecf20Sopenharmony_ci return -EINVAL; 5838c2ecf20Sopenharmony_ci 5848c2ecf20Sopenharmony_ci if ((drv->hw_access != CX8802_DRVCTL_SHARED) && 5858c2ecf20Sopenharmony_ci (drv->hw_access != CX8802_DRVCTL_EXCLUSIVE)) 5868c2ecf20Sopenharmony_ci return -EINVAL; 5878c2ecf20Sopenharmony_ci 5888c2ecf20Sopenharmony_ci if ((!drv->probe) || 5898c2ecf20Sopenharmony_ci (!drv->remove) || 5908c2ecf20Sopenharmony_ci (!drv->advise_acquire) || 5918c2ecf20Sopenharmony_ci (!drv->advise_release)) 5928c2ecf20Sopenharmony_ci return -EINVAL; 5938c2ecf20Sopenharmony_ci 5948c2ecf20Sopenharmony_ci return 0; 5958c2ecf20Sopenharmony_ci} 5968c2ecf20Sopenharmony_ci 5978c2ecf20Sopenharmony_ciint cx8802_register_driver(struct cx8802_driver *drv) 5988c2ecf20Sopenharmony_ci{ 5998c2ecf20Sopenharmony_ci struct cx8802_dev *dev; 6008c2ecf20Sopenharmony_ci struct cx8802_driver *driver; 6018c2ecf20Sopenharmony_ci int err, i = 0; 6028c2ecf20Sopenharmony_ci 6038c2ecf20Sopenharmony_ci pr_info("registering cx8802 driver, type: %s access: %s\n", 6048c2ecf20Sopenharmony_ci drv->type_id == CX88_MPEG_DVB ? "dvb" : "blackbird", 6058c2ecf20Sopenharmony_ci drv->hw_access == CX8802_DRVCTL_SHARED ? 6068c2ecf20Sopenharmony_ci "shared" : "exclusive"); 6078c2ecf20Sopenharmony_ci 6088c2ecf20Sopenharmony_ci err = cx8802_check_driver(drv); 6098c2ecf20Sopenharmony_ci if (err) { 6108c2ecf20Sopenharmony_ci pr_err("cx8802_driver is invalid\n"); 6118c2ecf20Sopenharmony_ci return err; 6128c2ecf20Sopenharmony_ci } 6138c2ecf20Sopenharmony_ci 6148c2ecf20Sopenharmony_ci mutex_lock(&cx8802_mutex); 6158c2ecf20Sopenharmony_ci 6168c2ecf20Sopenharmony_ci list_for_each_entry(dev, &cx8802_devlist, devlist) { 6178c2ecf20Sopenharmony_ci pr_info("subsystem: %04x:%04x, board: %s [card=%d]\n", 6188c2ecf20Sopenharmony_ci dev->pci->subsystem_vendor, 6198c2ecf20Sopenharmony_ci dev->pci->subsystem_device, dev->core->board.name, 6208c2ecf20Sopenharmony_ci dev->core->boardnr); 6218c2ecf20Sopenharmony_ci 6228c2ecf20Sopenharmony_ci /* Bring up a new struct for each driver instance */ 6238c2ecf20Sopenharmony_ci driver = kzalloc(sizeof(*drv), GFP_KERNEL); 6248c2ecf20Sopenharmony_ci if (!driver) { 6258c2ecf20Sopenharmony_ci err = -ENOMEM; 6268c2ecf20Sopenharmony_ci goto out; 6278c2ecf20Sopenharmony_ci } 6288c2ecf20Sopenharmony_ci 6298c2ecf20Sopenharmony_ci /* Snapshot of the driver registration data */ 6308c2ecf20Sopenharmony_ci drv->core = dev->core; 6318c2ecf20Sopenharmony_ci drv->suspend = cx8802_suspend_common; 6328c2ecf20Sopenharmony_ci drv->resume = cx8802_resume_common; 6338c2ecf20Sopenharmony_ci drv->request_acquire = cx8802_request_acquire; 6348c2ecf20Sopenharmony_ci drv->request_release = cx8802_request_release; 6358c2ecf20Sopenharmony_ci memcpy(driver, drv, sizeof(*driver)); 6368c2ecf20Sopenharmony_ci 6378c2ecf20Sopenharmony_ci mutex_lock(&drv->core->lock); 6388c2ecf20Sopenharmony_ci err = drv->probe(driver); 6398c2ecf20Sopenharmony_ci if (err == 0) { 6408c2ecf20Sopenharmony_ci i++; 6418c2ecf20Sopenharmony_ci list_add_tail(&driver->drvlist, &dev->drvlist); 6428c2ecf20Sopenharmony_ci } else { 6438c2ecf20Sopenharmony_ci pr_err("cx8802 probe failed, err = %d\n", err); 6448c2ecf20Sopenharmony_ci } 6458c2ecf20Sopenharmony_ci mutex_unlock(&drv->core->lock); 6468c2ecf20Sopenharmony_ci } 6478c2ecf20Sopenharmony_ci 6488c2ecf20Sopenharmony_ci err = i ? 0 : -ENODEV; 6498c2ecf20Sopenharmony_ciout: 6508c2ecf20Sopenharmony_ci mutex_unlock(&cx8802_mutex); 6518c2ecf20Sopenharmony_ci return err; 6528c2ecf20Sopenharmony_ci} 6538c2ecf20Sopenharmony_ciEXPORT_SYMBOL(cx8802_register_driver); 6548c2ecf20Sopenharmony_ci 6558c2ecf20Sopenharmony_ciint cx8802_unregister_driver(struct cx8802_driver *drv) 6568c2ecf20Sopenharmony_ci{ 6578c2ecf20Sopenharmony_ci struct cx8802_dev *dev; 6588c2ecf20Sopenharmony_ci struct cx8802_driver *d, *dtmp; 6598c2ecf20Sopenharmony_ci int err = 0; 6608c2ecf20Sopenharmony_ci 6618c2ecf20Sopenharmony_ci pr_info("unregistering cx8802 driver, type: %s access: %s\n", 6628c2ecf20Sopenharmony_ci drv->type_id == CX88_MPEG_DVB ? "dvb" : "blackbird", 6638c2ecf20Sopenharmony_ci drv->hw_access == CX8802_DRVCTL_SHARED ? 6648c2ecf20Sopenharmony_ci "shared" : "exclusive"); 6658c2ecf20Sopenharmony_ci 6668c2ecf20Sopenharmony_ci mutex_lock(&cx8802_mutex); 6678c2ecf20Sopenharmony_ci 6688c2ecf20Sopenharmony_ci list_for_each_entry(dev, &cx8802_devlist, devlist) { 6698c2ecf20Sopenharmony_ci pr_info("subsystem: %04x:%04x, board: %s [card=%d]\n", 6708c2ecf20Sopenharmony_ci dev->pci->subsystem_vendor, 6718c2ecf20Sopenharmony_ci dev->pci->subsystem_device, dev->core->board.name, 6728c2ecf20Sopenharmony_ci dev->core->boardnr); 6738c2ecf20Sopenharmony_ci 6748c2ecf20Sopenharmony_ci mutex_lock(&dev->core->lock); 6758c2ecf20Sopenharmony_ci 6768c2ecf20Sopenharmony_ci list_for_each_entry_safe(d, dtmp, &dev->drvlist, drvlist) { 6778c2ecf20Sopenharmony_ci /* only unregister the correct driver type */ 6788c2ecf20Sopenharmony_ci if (d->type_id != drv->type_id) 6798c2ecf20Sopenharmony_ci continue; 6808c2ecf20Sopenharmony_ci 6818c2ecf20Sopenharmony_ci err = d->remove(d); 6828c2ecf20Sopenharmony_ci if (err == 0) { 6838c2ecf20Sopenharmony_ci list_del(&d->drvlist); 6848c2ecf20Sopenharmony_ci kfree(d); 6858c2ecf20Sopenharmony_ci } else 6868c2ecf20Sopenharmony_ci pr_err("cx8802 driver remove failed (%d)\n", 6878c2ecf20Sopenharmony_ci err); 6888c2ecf20Sopenharmony_ci } 6898c2ecf20Sopenharmony_ci 6908c2ecf20Sopenharmony_ci mutex_unlock(&dev->core->lock); 6918c2ecf20Sopenharmony_ci } 6928c2ecf20Sopenharmony_ci 6938c2ecf20Sopenharmony_ci mutex_unlock(&cx8802_mutex); 6948c2ecf20Sopenharmony_ci 6958c2ecf20Sopenharmony_ci return err; 6968c2ecf20Sopenharmony_ci} 6978c2ecf20Sopenharmony_ciEXPORT_SYMBOL(cx8802_unregister_driver); 6988c2ecf20Sopenharmony_ci 6998c2ecf20Sopenharmony_ci/* ----------------------------------------------------------- */ 7008c2ecf20Sopenharmony_cistatic int cx8802_probe(struct pci_dev *pci_dev, 7018c2ecf20Sopenharmony_ci const struct pci_device_id *pci_id) 7028c2ecf20Sopenharmony_ci{ 7038c2ecf20Sopenharmony_ci struct cx8802_dev *dev; 7048c2ecf20Sopenharmony_ci struct cx88_core *core; 7058c2ecf20Sopenharmony_ci int err; 7068c2ecf20Sopenharmony_ci 7078c2ecf20Sopenharmony_ci /* general setup */ 7088c2ecf20Sopenharmony_ci core = cx88_core_get(pci_dev); 7098c2ecf20Sopenharmony_ci if (!core) 7108c2ecf20Sopenharmony_ci return -EINVAL; 7118c2ecf20Sopenharmony_ci 7128c2ecf20Sopenharmony_ci pr_info("cx2388x 8802 Driver Manager\n"); 7138c2ecf20Sopenharmony_ci 7148c2ecf20Sopenharmony_ci err = -ENODEV; 7158c2ecf20Sopenharmony_ci if (!core->board.mpeg) 7168c2ecf20Sopenharmony_ci goto fail_core; 7178c2ecf20Sopenharmony_ci 7188c2ecf20Sopenharmony_ci err = -ENOMEM; 7198c2ecf20Sopenharmony_ci dev = kzalloc(sizeof(*dev), GFP_KERNEL); 7208c2ecf20Sopenharmony_ci if (!dev) 7218c2ecf20Sopenharmony_ci goto fail_core; 7228c2ecf20Sopenharmony_ci dev->pci = pci_dev; 7238c2ecf20Sopenharmony_ci dev->core = core; 7248c2ecf20Sopenharmony_ci 7258c2ecf20Sopenharmony_ci /* Maintain a reference so cx88-video can query the 8802 device. */ 7268c2ecf20Sopenharmony_ci core->dvbdev = dev; 7278c2ecf20Sopenharmony_ci 7288c2ecf20Sopenharmony_ci err = cx8802_init_common(dev); 7298c2ecf20Sopenharmony_ci if (err != 0) 7308c2ecf20Sopenharmony_ci goto fail_dev; 7318c2ecf20Sopenharmony_ci 7328c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&dev->drvlist); 7338c2ecf20Sopenharmony_ci mutex_lock(&cx8802_mutex); 7348c2ecf20Sopenharmony_ci list_add_tail(&dev->devlist, &cx8802_devlist); 7358c2ecf20Sopenharmony_ci mutex_unlock(&cx8802_mutex); 7368c2ecf20Sopenharmony_ci 7378c2ecf20Sopenharmony_ci /* now autoload cx88-dvb or cx88-blackbird */ 7388c2ecf20Sopenharmony_ci request_modules(dev); 7398c2ecf20Sopenharmony_ci return 0; 7408c2ecf20Sopenharmony_ci 7418c2ecf20Sopenharmony_ci fail_dev: 7428c2ecf20Sopenharmony_ci kfree(dev); 7438c2ecf20Sopenharmony_ci fail_core: 7448c2ecf20Sopenharmony_ci core->dvbdev = NULL; 7458c2ecf20Sopenharmony_ci cx88_core_put(core, pci_dev); 7468c2ecf20Sopenharmony_ci return err; 7478c2ecf20Sopenharmony_ci} 7488c2ecf20Sopenharmony_ci 7498c2ecf20Sopenharmony_cistatic void cx8802_remove(struct pci_dev *pci_dev) 7508c2ecf20Sopenharmony_ci{ 7518c2ecf20Sopenharmony_ci struct cx8802_dev *dev; 7528c2ecf20Sopenharmony_ci 7538c2ecf20Sopenharmony_ci dev = pci_get_drvdata(pci_dev); 7548c2ecf20Sopenharmony_ci 7558c2ecf20Sopenharmony_ci dprintk(1, "%s\n", __func__); 7568c2ecf20Sopenharmony_ci 7578c2ecf20Sopenharmony_ci flush_request_modules(dev); 7588c2ecf20Sopenharmony_ci 7598c2ecf20Sopenharmony_ci mutex_lock(&dev->core->lock); 7608c2ecf20Sopenharmony_ci 7618c2ecf20Sopenharmony_ci if (!list_empty(&dev->drvlist)) { 7628c2ecf20Sopenharmony_ci struct cx8802_driver *drv, *tmp; 7638c2ecf20Sopenharmony_ci int err; 7648c2ecf20Sopenharmony_ci 7658c2ecf20Sopenharmony_ci pr_warn("Trying to remove cx8802 driver while cx8802 sub-drivers still loaded?!\n"); 7668c2ecf20Sopenharmony_ci 7678c2ecf20Sopenharmony_ci list_for_each_entry_safe(drv, tmp, &dev->drvlist, drvlist) { 7688c2ecf20Sopenharmony_ci err = drv->remove(drv); 7698c2ecf20Sopenharmony_ci if (err == 0) { 7708c2ecf20Sopenharmony_ci list_del(&drv->drvlist); 7718c2ecf20Sopenharmony_ci } else 7728c2ecf20Sopenharmony_ci pr_err("cx8802 driver remove failed (%d)\n", 7738c2ecf20Sopenharmony_ci err); 7748c2ecf20Sopenharmony_ci kfree(drv); 7758c2ecf20Sopenharmony_ci } 7768c2ecf20Sopenharmony_ci } 7778c2ecf20Sopenharmony_ci 7788c2ecf20Sopenharmony_ci mutex_unlock(&dev->core->lock); 7798c2ecf20Sopenharmony_ci 7808c2ecf20Sopenharmony_ci /* Destroy any 8802 reference. */ 7818c2ecf20Sopenharmony_ci dev->core->dvbdev = NULL; 7828c2ecf20Sopenharmony_ci 7838c2ecf20Sopenharmony_ci /* common */ 7848c2ecf20Sopenharmony_ci cx8802_fini_common(dev); 7858c2ecf20Sopenharmony_ci cx88_core_put(dev->core, dev->pci); 7868c2ecf20Sopenharmony_ci kfree(dev); 7878c2ecf20Sopenharmony_ci} 7888c2ecf20Sopenharmony_ci 7898c2ecf20Sopenharmony_cistatic const struct pci_device_id cx8802_pci_tbl[] = { 7908c2ecf20Sopenharmony_ci { 7918c2ecf20Sopenharmony_ci .vendor = 0x14f1, 7928c2ecf20Sopenharmony_ci .device = 0x8802, 7938c2ecf20Sopenharmony_ci .subvendor = PCI_ANY_ID, 7948c2ecf20Sopenharmony_ci .subdevice = PCI_ANY_ID, 7958c2ecf20Sopenharmony_ci }, { 7968c2ecf20Sopenharmony_ci /* --- end of list --- */ 7978c2ecf20Sopenharmony_ci } 7988c2ecf20Sopenharmony_ci}; 7998c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, cx8802_pci_tbl); 8008c2ecf20Sopenharmony_ci 8018c2ecf20Sopenharmony_cistatic struct pci_driver cx8802_pci_driver = { 8028c2ecf20Sopenharmony_ci .name = "cx88-mpeg driver manager", 8038c2ecf20Sopenharmony_ci .id_table = cx8802_pci_tbl, 8048c2ecf20Sopenharmony_ci .probe = cx8802_probe, 8058c2ecf20Sopenharmony_ci .remove = cx8802_remove, 8068c2ecf20Sopenharmony_ci}; 8078c2ecf20Sopenharmony_ci 8088c2ecf20Sopenharmony_cimodule_pci_driver(cx8802_pci_driver); 809