18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Support for audio capture 48c2ecf20Sopenharmony_ci * PCI function #1 of the cx2388x. 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * (c) 2007 Trent Piepho <xyzzy@speakeasy.org> 78c2ecf20Sopenharmony_ci * (c) 2005,2006 Ricardo Cerqueira <v4l@cerqueira.org> 88c2ecf20Sopenharmony_ci * (c) 2005 Mauro Carvalho Chehab <mchehab@kernel.org> 98c2ecf20Sopenharmony_ci * Based on a dummy cx88 module by Gerd Knorr <kraxel@bytesex.org> 108c2ecf20Sopenharmony_ci * Based on dummy.c by Jaroslav Kysela <perex@perex.cz> 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include "cx88.h" 148c2ecf20Sopenharmony_ci#include "cx88-reg.h" 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include <linux/module.h> 178c2ecf20Sopenharmony_ci#include <linux/init.h> 188c2ecf20Sopenharmony_ci#include <linux/delay.h> 198c2ecf20Sopenharmony_ci#include <linux/device.h> 208c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 218c2ecf20Sopenharmony_ci#include <linux/vmalloc.h> 228c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h> 238c2ecf20Sopenharmony_ci#include <linux/pci.h> 248c2ecf20Sopenharmony_ci#include <linux/slab.h> 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#include <sound/core.h> 278c2ecf20Sopenharmony_ci#include <sound/pcm.h> 288c2ecf20Sopenharmony_ci#include <sound/pcm_params.h> 298c2ecf20Sopenharmony_ci#include <sound/control.h> 308c2ecf20Sopenharmony_ci#include <sound/initval.h> 318c2ecf20Sopenharmony_ci#include <sound/tlv.h> 328c2ecf20Sopenharmony_ci#include <media/i2c/wm8775.h> 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci#define dprintk(level, fmt, arg...) do { \ 358c2ecf20Sopenharmony_ci if (debug + 1 > level) \ 368c2ecf20Sopenharmony_ci printk(KERN_DEBUG pr_fmt("%s: alsa: " fmt), \ 378c2ecf20Sopenharmony_ci chip->core->name, ##arg); \ 388c2ecf20Sopenharmony_ci} while (0) 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci/* 418c2ecf20Sopenharmony_ci * Data type declarations - Can be moded to a header file later 428c2ecf20Sopenharmony_ci */ 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistruct cx88_audio_buffer { 458c2ecf20Sopenharmony_ci unsigned int bpl; 468c2ecf20Sopenharmony_ci struct cx88_riscmem risc; 478c2ecf20Sopenharmony_ci void *vaddr; 488c2ecf20Sopenharmony_ci struct scatterlist *sglist; 498c2ecf20Sopenharmony_ci int sglen; 508c2ecf20Sopenharmony_ci unsigned long nr_pages; 518c2ecf20Sopenharmony_ci}; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistruct cx88_audio_dev { 548c2ecf20Sopenharmony_ci struct cx88_core *core; 558c2ecf20Sopenharmony_ci struct cx88_dmaqueue q; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci /* pci i/o */ 588c2ecf20Sopenharmony_ci struct pci_dev *pci; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci /* audio controls */ 618c2ecf20Sopenharmony_ci int irq; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci struct snd_card *card; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci spinlock_t reg_lock; 668c2ecf20Sopenharmony_ci atomic_t count; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci unsigned int dma_size; 698c2ecf20Sopenharmony_ci unsigned int period_size; 708c2ecf20Sopenharmony_ci unsigned int num_periods; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci struct cx88_audio_buffer *buf; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci struct snd_pcm_substream *substream; 758c2ecf20Sopenharmony_ci}; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci/* 788c2ecf20Sopenharmony_ci * Module global static vars 798c2ecf20Sopenharmony_ci */ 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cistatic int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ 828c2ecf20Sopenharmony_cistatic const char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ 838c2ecf20Sopenharmony_cistatic bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cimodule_param_array(enable, bool, NULL, 0444); 868c2ecf20Sopenharmony_ciMODULE_PARM_DESC(enable, "Enable cx88x soundcard. default enabled."); 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_cimodule_param_array(index, int, NULL, 0444); 898c2ecf20Sopenharmony_ciMODULE_PARM_DESC(index, "Index value for cx88x capture interface(s)."); 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci/* 928c2ecf20Sopenharmony_ci * Module macros 938c2ecf20Sopenharmony_ci */ 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ALSA driver module for cx2388x based TV cards"); 968c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ricardo Cerqueira"); 978c2ecf20Sopenharmony_ciMODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@kernel.org>"); 988c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 998c2ecf20Sopenharmony_ciMODULE_VERSION(CX88_VERSION); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ciMODULE_SUPPORTED_DEVICE("{{Conexant,23881},{{Conexant,23882},{{Conexant,23883}"); 1028c2ecf20Sopenharmony_cistatic unsigned int debug; 1038c2ecf20Sopenharmony_cimodule_param(debug, int, 0644); 1048c2ecf20Sopenharmony_ciMODULE_PARM_DESC(debug, "enable debug messages"); 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci/* 1078c2ecf20Sopenharmony_ci * Module specific functions 1088c2ecf20Sopenharmony_ci */ 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci/* 1118c2ecf20Sopenharmony_ci * BOARD Specific: Sets audio DMA 1128c2ecf20Sopenharmony_ci */ 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistatic int _cx88_start_audio_dma(struct cx88_audio_dev *chip) 1158c2ecf20Sopenharmony_ci{ 1168c2ecf20Sopenharmony_ci struct cx88_audio_buffer *buf = chip->buf; 1178c2ecf20Sopenharmony_ci struct cx88_core *core = chip->core; 1188c2ecf20Sopenharmony_ci const struct sram_channel *audio_ch = &cx88_sram_channels[SRAM_CH25]; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci /* Make sure RISC/FIFO are off before changing FIFO/RISC settings */ 1218c2ecf20Sopenharmony_ci cx_clear(MO_AUD_DMACNTRL, 0x11); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci /* setup fifo + format - out channel */ 1248c2ecf20Sopenharmony_ci cx88_sram_channel_setup(chip->core, audio_ch, buf->bpl, buf->risc.dma); 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci /* sets bpl size */ 1278c2ecf20Sopenharmony_ci cx_write(MO_AUDD_LNGTH, buf->bpl); 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci /* reset counter */ 1308c2ecf20Sopenharmony_ci cx_write(MO_AUDD_GPCNTRL, GP_COUNT_CONTROL_RESET); 1318c2ecf20Sopenharmony_ci atomic_set(&chip->count, 0); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci dprintk(1, 1348c2ecf20Sopenharmony_ci "Start audio DMA, %d B/line, %d lines/FIFO, %d periods, %d byte buffer\n", 1358c2ecf20Sopenharmony_ci buf->bpl, cx_read(audio_ch->cmds_start + 8) >> 1, 1368c2ecf20Sopenharmony_ci chip->num_periods, buf->bpl * chip->num_periods); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci /* Enables corresponding bits at AUD_INT_STAT */ 1398c2ecf20Sopenharmony_ci cx_write(MO_AUD_INTMSK, AUD_INT_OPC_ERR | AUD_INT_DN_SYNC | 1408c2ecf20Sopenharmony_ci AUD_INT_DN_RISCI2 | AUD_INT_DN_RISCI1); 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci /* Clean any pending interrupt bits already set */ 1438c2ecf20Sopenharmony_ci cx_write(MO_AUD_INTSTAT, ~0); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci /* enable audio irqs */ 1468c2ecf20Sopenharmony_ci cx_set(MO_PCI_INTMSK, chip->core->pci_irqmask | PCI_INT_AUDINT); 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci /* start dma */ 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci /* Enables Risc Processor */ 1518c2ecf20Sopenharmony_ci cx_set(MO_DEV_CNTRL2, (1 << 5)); 1528c2ecf20Sopenharmony_ci /* audio downstream FIFO and RISC enable */ 1538c2ecf20Sopenharmony_ci cx_set(MO_AUD_DMACNTRL, 0x11); 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci if (debug) 1568c2ecf20Sopenharmony_ci cx88_sram_channel_dump(chip->core, audio_ch); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci return 0; 1598c2ecf20Sopenharmony_ci} 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci/* 1628c2ecf20Sopenharmony_ci * BOARD Specific: Resets audio DMA 1638c2ecf20Sopenharmony_ci */ 1648c2ecf20Sopenharmony_cistatic int _cx88_stop_audio_dma(struct cx88_audio_dev *chip) 1658c2ecf20Sopenharmony_ci{ 1668c2ecf20Sopenharmony_ci struct cx88_core *core = chip->core; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci dprintk(1, "Stopping audio DMA\n"); 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci /* stop dma */ 1718c2ecf20Sopenharmony_ci cx_clear(MO_AUD_DMACNTRL, 0x11); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci /* disable irqs */ 1748c2ecf20Sopenharmony_ci cx_clear(MO_PCI_INTMSK, PCI_INT_AUDINT); 1758c2ecf20Sopenharmony_ci cx_clear(MO_AUD_INTMSK, AUD_INT_OPC_ERR | AUD_INT_DN_SYNC | 1768c2ecf20Sopenharmony_ci AUD_INT_DN_RISCI2 | AUD_INT_DN_RISCI1); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci if (debug) 1798c2ecf20Sopenharmony_ci cx88_sram_channel_dump(chip->core, 1808c2ecf20Sopenharmony_ci &cx88_sram_channels[SRAM_CH25]); 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci return 0; 1838c2ecf20Sopenharmony_ci} 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci#define MAX_IRQ_LOOP 50 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci/* 1888c2ecf20Sopenharmony_ci * BOARD Specific: IRQ dma bits 1898c2ecf20Sopenharmony_ci */ 1908c2ecf20Sopenharmony_cistatic const char *cx88_aud_irqs[32] = { 1918c2ecf20Sopenharmony_ci "dn_risci1", "up_risci1", "rds_dn_risc1", /* 0-2 */ 1928c2ecf20Sopenharmony_ci NULL, /* reserved */ 1938c2ecf20Sopenharmony_ci "dn_risci2", "up_risci2", "rds_dn_risc2", /* 4-6 */ 1948c2ecf20Sopenharmony_ci NULL, /* reserved */ 1958c2ecf20Sopenharmony_ci "dnf_of", "upf_uf", "rds_dnf_uf", /* 8-10 */ 1968c2ecf20Sopenharmony_ci NULL, /* reserved */ 1978c2ecf20Sopenharmony_ci "dn_sync", "up_sync", "rds_dn_sync", /* 12-14 */ 1988c2ecf20Sopenharmony_ci NULL, /* reserved */ 1998c2ecf20Sopenharmony_ci "opc_err", "par_err", "rip_err", /* 16-18 */ 2008c2ecf20Sopenharmony_ci "pci_abort", "ber_irq", "mchg_irq" /* 19-21 */ 2018c2ecf20Sopenharmony_ci}; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci/* 2048c2ecf20Sopenharmony_ci * BOARD Specific: Threats IRQ audio specific calls 2058c2ecf20Sopenharmony_ci */ 2068c2ecf20Sopenharmony_cistatic void cx8801_aud_irq(struct cx88_audio_dev *chip) 2078c2ecf20Sopenharmony_ci{ 2088c2ecf20Sopenharmony_ci struct cx88_core *core = chip->core; 2098c2ecf20Sopenharmony_ci u32 status, mask; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci status = cx_read(MO_AUD_INTSTAT); 2128c2ecf20Sopenharmony_ci mask = cx_read(MO_AUD_INTMSK); 2138c2ecf20Sopenharmony_ci if (0 == (status & mask)) 2148c2ecf20Sopenharmony_ci return; 2158c2ecf20Sopenharmony_ci cx_write(MO_AUD_INTSTAT, status); 2168c2ecf20Sopenharmony_ci if (debug > 1 || (status & mask & ~0xff)) 2178c2ecf20Sopenharmony_ci cx88_print_irqbits("irq aud", 2188c2ecf20Sopenharmony_ci cx88_aud_irqs, ARRAY_SIZE(cx88_aud_irqs), 2198c2ecf20Sopenharmony_ci status, mask); 2208c2ecf20Sopenharmony_ci /* risc op code error */ 2218c2ecf20Sopenharmony_ci if (status & AUD_INT_OPC_ERR) { 2228c2ecf20Sopenharmony_ci pr_warn("Audio risc op code error\n"); 2238c2ecf20Sopenharmony_ci cx_clear(MO_AUD_DMACNTRL, 0x11); 2248c2ecf20Sopenharmony_ci cx88_sram_channel_dump(core, &cx88_sram_channels[SRAM_CH25]); 2258c2ecf20Sopenharmony_ci } 2268c2ecf20Sopenharmony_ci if (status & AUD_INT_DN_SYNC) { 2278c2ecf20Sopenharmony_ci dprintk(1, "Downstream sync error\n"); 2288c2ecf20Sopenharmony_ci cx_write(MO_AUDD_GPCNTRL, GP_COUNT_CONTROL_RESET); 2298c2ecf20Sopenharmony_ci return; 2308c2ecf20Sopenharmony_ci } 2318c2ecf20Sopenharmony_ci /* risc1 downstream */ 2328c2ecf20Sopenharmony_ci if (status & AUD_INT_DN_RISCI1) { 2338c2ecf20Sopenharmony_ci atomic_set(&chip->count, cx_read(MO_AUDD_GPCNT)); 2348c2ecf20Sopenharmony_ci snd_pcm_period_elapsed(chip->substream); 2358c2ecf20Sopenharmony_ci } 2368c2ecf20Sopenharmony_ci /* FIXME: Any other status should deserve a special handling? */ 2378c2ecf20Sopenharmony_ci} 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci/* 2408c2ecf20Sopenharmony_ci * BOARD Specific: Handles IRQ calls 2418c2ecf20Sopenharmony_ci */ 2428c2ecf20Sopenharmony_cistatic irqreturn_t cx8801_irq(int irq, void *dev_id) 2438c2ecf20Sopenharmony_ci{ 2448c2ecf20Sopenharmony_ci struct cx88_audio_dev *chip = dev_id; 2458c2ecf20Sopenharmony_ci struct cx88_core *core = chip->core; 2468c2ecf20Sopenharmony_ci u32 status; 2478c2ecf20Sopenharmony_ci int loop, handled = 0; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci for (loop = 0; loop < MAX_IRQ_LOOP; loop++) { 2508c2ecf20Sopenharmony_ci status = cx_read(MO_PCI_INTSTAT) & 2518c2ecf20Sopenharmony_ci (core->pci_irqmask | PCI_INT_AUDINT); 2528c2ecf20Sopenharmony_ci if (status == 0) 2538c2ecf20Sopenharmony_ci goto out; 2548c2ecf20Sopenharmony_ci dprintk(3, "cx8801_irq loop %d/%d, status %x\n", 2558c2ecf20Sopenharmony_ci loop, MAX_IRQ_LOOP, status); 2568c2ecf20Sopenharmony_ci handled = 1; 2578c2ecf20Sopenharmony_ci cx_write(MO_PCI_INTSTAT, status); 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci if (status & core->pci_irqmask) 2608c2ecf20Sopenharmony_ci cx88_core_irq(core, status); 2618c2ecf20Sopenharmony_ci if (status & PCI_INT_AUDINT) 2628c2ecf20Sopenharmony_ci cx8801_aud_irq(chip); 2638c2ecf20Sopenharmony_ci } 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci if (loop == MAX_IRQ_LOOP) { 2668c2ecf20Sopenharmony_ci pr_err("IRQ loop detected, disabling interrupts\n"); 2678c2ecf20Sopenharmony_ci cx_clear(MO_PCI_INTMSK, PCI_INT_AUDINT); 2688c2ecf20Sopenharmony_ci } 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci out: 2718c2ecf20Sopenharmony_ci return IRQ_RETVAL(handled); 2728c2ecf20Sopenharmony_ci} 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_cistatic int cx88_alsa_dma_init(struct cx88_audio_dev *chip, 2758c2ecf20Sopenharmony_ci unsigned long nr_pages) 2768c2ecf20Sopenharmony_ci{ 2778c2ecf20Sopenharmony_ci struct cx88_audio_buffer *buf = chip->buf; 2788c2ecf20Sopenharmony_ci struct page *pg; 2798c2ecf20Sopenharmony_ci int i; 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci buf->vaddr = vmalloc_32(nr_pages << PAGE_SHIFT); 2828c2ecf20Sopenharmony_ci if (!buf->vaddr) { 2838c2ecf20Sopenharmony_ci dprintk(1, "vmalloc_32(%lu pages) failed\n", nr_pages); 2848c2ecf20Sopenharmony_ci return -ENOMEM; 2858c2ecf20Sopenharmony_ci } 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci dprintk(1, "vmalloc is at addr %p, size=%lu\n", 2888c2ecf20Sopenharmony_ci buf->vaddr, nr_pages << PAGE_SHIFT); 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci memset(buf->vaddr, 0, nr_pages << PAGE_SHIFT); 2918c2ecf20Sopenharmony_ci buf->nr_pages = nr_pages; 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci buf->sglist = vzalloc(array_size(sizeof(*buf->sglist), buf->nr_pages)); 2948c2ecf20Sopenharmony_ci if (!buf->sglist) 2958c2ecf20Sopenharmony_ci goto vzalloc_err; 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci sg_init_table(buf->sglist, buf->nr_pages); 2988c2ecf20Sopenharmony_ci for (i = 0; i < buf->nr_pages; i++) { 2998c2ecf20Sopenharmony_ci pg = vmalloc_to_page(buf->vaddr + i * PAGE_SIZE); 3008c2ecf20Sopenharmony_ci if (!pg) 3018c2ecf20Sopenharmony_ci goto vmalloc_to_page_err; 3028c2ecf20Sopenharmony_ci sg_set_page(&buf->sglist[i], pg, PAGE_SIZE, 0); 3038c2ecf20Sopenharmony_ci } 3048c2ecf20Sopenharmony_ci return 0; 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_civmalloc_to_page_err: 3078c2ecf20Sopenharmony_ci vfree(buf->sglist); 3088c2ecf20Sopenharmony_ci buf->sglist = NULL; 3098c2ecf20Sopenharmony_civzalloc_err: 3108c2ecf20Sopenharmony_ci vfree(buf->vaddr); 3118c2ecf20Sopenharmony_ci buf->vaddr = NULL; 3128c2ecf20Sopenharmony_ci return -ENOMEM; 3138c2ecf20Sopenharmony_ci} 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_cistatic int cx88_alsa_dma_map(struct cx88_audio_dev *dev) 3168c2ecf20Sopenharmony_ci{ 3178c2ecf20Sopenharmony_ci struct cx88_audio_buffer *buf = dev->buf; 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci buf->sglen = dma_map_sg(&dev->pci->dev, buf->sglist, 3208c2ecf20Sopenharmony_ci buf->nr_pages, DMA_FROM_DEVICE); 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci if (buf->sglen == 0) { 3238c2ecf20Sopenharmony_ci pr_warn("%s: cx88_alsa_map_sg failed\n", __func__); 3248c2ecf20Sopenharmony_ci return -ENOMEM; 3258c2ecf20Sopenharmony_ci } 3268c2ecf20Sopenharmony_ci return 0; 3278c2ecf20Sopenharmony_ci} 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_cistatic int cx88_alsa_dma_unmap(struct cx88_audio_dev *dev) 3308c2ecf20Sopenharmony_ci{ 3318c2ecf20Sopenharmony_ci struct cx88_audio_buffer *buf = dev->buf; 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_ci if (!buf->sglen) 3348c2ecf20Sopenharmony_ci return 0; 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_ci dma_unmap_sg(&dev->pci->dev, buf->sglist, buf->nr_pages, 3378c2ecf20Sopenharmony_ci DMA_FROM_DEVICE); 3388c2ecf20Sopenharmony_ci buf->sglen = 0; 3398c2ecf20Sopenharmony_ci return 0; 3408c2ecf20Sopenharmony_ci} 3418c2ecf20Sopenharmony_ci 3428c2ecf20Sopenharmony_cistatic int cx88_alsa_dma_free(struct cx88_audio_buffer *buf) 3438c2ecf20Sopenharmony_ci{ 3448c2ecf20Sopenharmony_ci vfree(buf->sglist); 3458c2ecf20Sopenharmony_ci buf->sglist = NULL; 3468c2ecf20Sopenharmony_ci vfree(buf->vaddr); 3478c2ecf20Sopenharmony_ci buf->vaddr = NULL; 3488c2ecf20Sopenharmony_ci return 0; 3498c2ecf20Sopenharmony_ci} 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_cistatic int dsp_buffer_free(struct cx88_audio_dev *chip) 3528c2ecf20Sopenharmony_ci{ 3538c2ecf20Sopenharmony_ci struct cx88_riscmem *risc = &chip->buf->risc; 3548c2ecf20Sopenharmony_ci 3558c2ecf20Sopenharmony_ci WARN_ON(!chip->dma_size); 3568c2ecf20Sopenharmony_ci 3578c2ecf20Sopenharmony_ci dprintk(2, "Freeing buffer\n"); 3588c2ecf20Sopenharmony_ci cx88_alsa_dma_unmap(chip); 3598c2ecf20Sopenharmony_ci cx88_alsa_dma_free(chip->buf); 3608c2ecf20Sopenharmony_ci if (risc->cpu) 3618c2ecf20Sopenharmony_ci pci_free_consistent(chip->pci, risc->size, 3628c2ecf20Sopenharmony_ci risc->cpu, risc->dma); 3638c2ecf20Sopenharmony_ci kfree(chip->buf); 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_ci chip->buf = NULL; 3668c2ecf20Sopenharmony_ci 3678c2ecf20Sopenharmony_ci return 0; 3688c2ecf20Sopenharmony_ci} 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci/* 3718c2ecf20Sopenharmony_ci * ALSA PCM Interface 3728c2ecf20Sopenharmony_ci */ 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_ci/* 3758c2ecf20Sopenharmony_ci * Digital hardware definition 3768c2ecf20Sopenharmony_ci */ 3778c2ecf20Sopenharmony_ci#define DEFAULT_FIFO_SIZE 4096 3788c2ecf20Sopenharmony_cistatic const struct snd_pcm_hardware snd_cx88_digital_hw = { 3798c2ecf20Sopenharmony_ci .info = SNDRV_PCM_INFO_MMAP | 3808c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_INTERLEAVED | 3818c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_BLOCK_TRANSFER | 3828c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_MMAP_VALID, 3838c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S16_LE, 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_48000, 3868c2ecf20Sopenharmony_ci .rate_min = 48000, 3878c2ecf20Sopenharmony_ci .rate_max = 48000, 3888c2ecf20Sopenharmony_ci .channels_min = 2, 3898c2ecf20Sopenharmony_ci .channels_max = 2, 3908c2ecf20Sopenharmony_ci /* 3918c2ecf20Sopenharmony_ci * Analog audio output will be full of clicks and pops if there 3928c2ecf20Sopenharmony_ci * are not exactly four lines in the SRAM FIFO buffer. 3938c2ecf20Sopenharmony_ci */ 3948c2ecf20Sopenharmony_ci .period_bytes_min = DEFAULT_FIFO_SIZE / 4, 3958c2ecf20Sopenharmony_ci .period_bytes_max = DEFAULT_FIFO_SIZE / 4, 3968c2ecf20Sopenharmony_ci .periods_min = 1, 3978c2ecf20Sopenharmony_ci .periods_max = 1024, 3988c2ecf20Sopenharmony_ci .buffer_bytes_max = (1024 * 1024), 3998c2ecf20Sopenharmony_ci}; 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_ci/* 4028c2ecf20Sopenharmony_ci * audio pcm capture open callback 4038c2ecf20Sopenharmony_ci */ 4048c2ecf20Sopenharmony_cistatic int snd_cx88_pcm_open(struct snd_pcm_substream *substream) 4058c2ecf20Sopenharmony_ci{ 4068c2ecf20Sopenharmony_ci struct cx88_audio_dev *chip = snd_pcm_substream_chip(substream); 4078c2ecf20Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 4088c2ecf20Sopenharmony_ci int err; 4098c2ecf20Sopenharmony_ci 4108c2ecf20Sopenharmony_ci if (!chip) { 4118c2ecf20Sopenharmony_ci pr_err("BUG: cx88 can't find device struct. Can't proceed with open\n"); 4128c2ecf20Sopenharmony_ci return -ENODEV; 4138c2ecf20Sopenharmony_ci } 4148c2ecf20Sopenharmony_ci 4158c2ecf20Sopenharmony_ci err = snd_pcm_hw_constraint_pow2(runtime, 0, 4168c2ecf20Sopenharmony_ci SNDRV_PCM_HW_PARAM_PERIODS); 4178c2ecf20Sopenharmony_ci if (err < 0) 4188c2ecf20Sopenharmony_ci goto _error; 4198c2ecf20Sopenharmony_ci 4208c2ecf20Sopenharmony_ci chip->substream = substream; 4218c2ecf20Sopenharmony_ci 4228c2ecf20Sopenharmony_ci runtime->hw = snd_cx88_digital_hw; 4238c2ecf20Sopenharmony_ci 4248c2ecf20Sopenharmony_ci if (cx88_sram_channels[SRAM_CH25].fifo_size != DEFAULT_FIFO_SIZE) { 4258c2ecf20Sopenharmony_ci unsigned int bpl = cx88_sram_channels[SRAM_CH25].fifo_size / 4; 4268c2ecf20Sopenharmony_ci 4278c2ecf20Sopenharmony_ci bpl &= ~7; /* must be multiple of 8 */ 4288c2ecf20Sopenharmony_ci runtime->hw.period_bytes_min = bpl; 4298c2ecf20Sopenharmony_ci runtime->hw.period_bytes_max = bpl; 4308c2ecf20Sopenharmony_ci } 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci return 0; 4338c2ecf20Sopenharmony_ci_error: 4348c2ecf20Sopenharmony_ci dprintk(1, "Error opening PCM!\n"); 4358c2ecf20Sopenharmony_ci return err; 4368c2ecf20Sopenharmony_ci} 4378c2ecf20Sopenharmony_ci 4388c2ecf20Sopenharmony_ci/* 4398c2ecf20Sopenharmony_ci * audio close callback 4408c2ecf20Sopenharmony_ci */ 4418c2ecf20Sopenharmony_cistatic int snd_cx88_close(struct snd_pcm_substream *substream) 4428c2ecf20Sopenharmony_ci{ 4438c2ecf20Sopenharmony_ci return 0; 4448c2ecf20Sopenharmony_ci} 4458c2ecf20Sopenharmony_ci 4468c2ecf20Sopenharmony_ci/* 4478c2ecf20Sopenharmony_ci * hw_params callback 4488c2ecf20Sopenharmony_ci */ 4498c2ecf20Sopenharmony_cistatic int snd_cx88_hw_params(struct snd_pcm_substream *substream, 4508c2ecf20Sopenharmony_ci struct snd_pcm_hw_params *hw_params) 4518c2ecf20Sopenharmony_ci{ 4528c2ecf20Sopenharmony_ci struct cx88_audio_dev *chip = snd_pcm_substream_chip(substream); 4538c2ecf20Sopenharmony_ci 4548c2ecf20Sopenharmony_ci struct cx88_audio_buffer *buf; 4558c2ecf20Sopenharmony_ci int ret; 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_ci if (substream->runtime->dma_area) { 4588c2ecf20Sopenharmony_ci dsp_buffer_free(chip); 4598c2ecf20Sopenharmony_ci substream->runtime->dma_area = NULL; 4608c2ecf20Sopenharmony_ci } 4618c2ecf20Sopenharmony_ci 4628c2ecf20Sopenharmony_ci chip->period_size = params_period_bytes(hw_params); 4638c2ecf20Sopenharmony_ci chip->num_periods = params_periods(hw_params); 4648c2ecf20Sopenharmony_ci chip->dma_size = chip->period_size * params_periods(hw_params); 4658c2ecf20Sopenharmony_ci 4668c2ecf20Sopenharmony_ci WARN_ON(!chip->dma_size); 4678c2ecf20Sopenharmony_ci WARN_ON(chip->num_periods & (chip->num_periods - 1)); 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ci buf = kzalloc(sizeof(*buf), GFP_KERNEL); 4708c2ecf20Sopenharmony_ci if (!buf) 4718c2ecf20Sopenharmony_ci return -ENOMEM; 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_ci chip->buf = buf; 4748c2ecf20Sopenharmony_ci buf->bpl = chip->period_size; 4758c2ecf20Sopenharmony_ci 4768c2ecf20Sopenharmony_ci ret = cx88_alsa_dma_init(chip, 4778c2ecf20Sopenharmony_ci (PAGE_ALIGN(chip->dma_size) >> PAGE_SHIFT)); 4788c2ecf20Sopenharmony_ci if (ret < 0) 4798c2ecf20Sopenharmony_ci goto error; 4808c2ecf20Sopenharmony_ci 4818c2ecf20Sopenharmony_ci ret = cx88_alsa_dma_map(chip); 4828c2ecf20Sopenharmony_ci if (ret < 0) 4838c2ecf20Sopenharmony_ci goto error; 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci ret = cx88_risc_databuffer(chip->pci, &buf->risc, buf->sglist, 4868c2ecf20Sopenharmony_ci chip->period_size, chip->num_periods, 1); 4878c2ecf20Sopenharmony_ci if (ret < 0) 4888c2ecf20Sopenharmony_ci goto error; 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_ci /* Loop back to start of program */ 4918c2ecf20Sopenharmony_ci buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); 4928c2ecf20Sopenharmony_ci buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma); 4938c2ecf20Sopenharmony_ci 4948c2ecf20Sopenharmony_ci substream->runtime->dma_area = chip->buf->vaddr; 4958c2ecf20Sopenharmony_ci substream->runtime->dma_bytes = chip->dma_size; 4968c2ecf20Sopenharmony_ci substream->runtime->dma_addr = 0; 4978c2ecf20Sopenharmony_ci return 0; 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_cierror: 5008c2ecf20Sopenharmony_ci kfree(buf); 5018c2ecf20Sopenharmony_ci return ret; 5028c2ecf20Sopenharmony_ci} 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_ci/* 5058c2ecf20Sopenharmony_ci * hw free callback 5068c2ecf20Sopenharmony_ci */ 5078c2ecf20Sopenharmony_cistatic int snd_cx88_hw_free(struct snd_pcm_substream *substream) 5088c2ecf20Sopenharmony_ci{ 5098c2ecf20Sopenharmony_ci struct cx88_audio_dev *chip = snd_pcm_substream_chip(substream); 5108c2ecf20Sopenharmony_ci 5118c2ecf20Sopenharmony_ci if (substream->runtime->dma_area) { 5128c2ecf20Sopenharmony_ci dsp_buffer_free(chip); 5138c2ecf20Sopenharmony_ci substream->runtime->dma_area = NULL; 5148c2ecf20Sopenharmony_ci } 5158c2ecf20Sopenharmony_ci 5168c2ecf20Sopenharmony_ci return 0; 5178c2ecf20Sopenharmony_ci} 5188c2ecf20Sopenharmony_ci 5198c2ecf20Sopenharmony_ci/* 5208c2ecf20Sopenharmony_ci * prepare callback 5218c2ecf20Sopenharmony_ci */ 5228c2ecf20Sopenharmony_cistatic int snd_cx88_prepare(struct snd_pcm_substream *substream) 5238c2ecf20Sopenharmony_ci{ 5248c2ecf20Sopenharmony_ci return 0; 5258c2ecf20Sopenharmony_ci} 5268c2ecf20Sopenharmony_ci 5278c2ecf20Sopenharmony_ci/* 5288c2ecf20Sopenharmony_ci * trigger callback 5298c2ecf20Sopenharmony_ci */ 5308c2ecf20Sopenharmony_cistatic int snd_cx88_card_trigger(struct snd_pcm_substream *substream, int cmd) 5318c2ecf20Sopenharmony_ci{ 5328c2ecf20Sopenharmony_ci struct cx88_audio_dev *chip = snd_pcm_substream_chip(substream); 5338c2ecf20Sopenharmony_ci int err; 5348c2ecf20Sopenharmony_ci 5358c2ecf20Sopenharmony_ci /* Local interrupts are already disabled by ALSA */ 5368c2ecf20Sopenharmony_ci spin_lock(&chip->reg_lock); 5378c2ecf20Sopenharmony_ci 5388c2ecf20Sopenharmony_ci switch (cmd) { 5398c2ecf20Sopenharmony_ci case SNDRV_PCM_TRIGGER_START: 5408c2ecf20Sopenharmony_ci err = _cx88_start_audio_dma(chip); 5418c2ecf20Sopenharmony_ci break; 5428c2ecf20Sopenharmony_ci case SNDRV_PCM_TRIGGER_STOP: 5438c2ecf20Sopenharmony_ci err = _cx88_stop_audio_dma(chip); 5448c2ecf20Sopenharmony_ci break; 5458c2ecf20Sopenharmony_ci default: 5468c2ecf20Sopenharmony_ci err = -EINVAL; 5478c2ecf20Sopenharmony_ci break; 5488c2ecf20Sopenharmony_ci } 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_ci spin_unlock(&chip->reg_lock); 5518c2ecf20Sopenharmony_ci 5528c2ecf20Sopenharmony_ci return err; 5538c2ecf20Sopenharmony_ci} 5548c2ecf20Sopenharmony_ci 5558c2ecf20Sopenharmony_ci/* 5568c2ecf20Sopenharmony_ci * pointer callback 5578c2ecf20Sopenharmony_ci */ 5588c2ecf20Sopenharmony_cistatic snd_pcm_uframes_t snd_cx88_pointer(struct snd_pcm_substream *substream) 5598c2ecf20Sopenharmony_ci{ 5608c2ecf20Sopenharmony_ci struct cx88_audio_dev *chip = snd_pcm_substream_chip(substream); 5618c2ecf20Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 5628c2ecf20Sopenharmony_ci u16 count; 5638c2ecf20Sopenharmony_ci 5648c2ecf20Sopenharmony_ci count = atomic_read(&chip->count); 5658c2ecf20Sopenharmony_ci 5668c2ecf20Sopenharmony_ci// dprintk(2, "%s - count %d (+%u), period %d, frame %lu\n", __func__, 5678c2ecf20Sopenharmony_ci// count, new, count & (runtime->periods-1), 5688c2ecf20Sopenharmony_ci// runtime->period_size * (count & (runtime->periods-1))); 5698c2ecf20Sopenharmony_ci return runtime->period_size * (count & (runtime->periods - 1)); 5708c2ecf20Sopenharmony_ci} 5718c2ecf20Sopenharmony_ci 5728c2ecf20Sopenharmony_ci/* 5738c2ecf20Sopenharmony_ci * page callback (needed for mmap) 5748c2ecf20Sopenharmony_ci */ 5758c2ecf20Sopenharmony_cistatic struct page *snd_cx88_page(struct snd_pcm_substream *substream, 5768c2ecf20Sopenharmony_ci unsigned long offset) 5778c2ecf20Sopenharmony_ci{ 5788c2ecf20Sopenharmony_ci void *pageptr = substream->runtime->dma_area + offset; 5798c2ecf20Sopenharmony_ci 5808c2ecf20Sopenharmony_ci return vmalloc_to_page(pageptr); 5818c2ecf20Sopenharmony_ci} 5828c2ecf20Sopenharmony_ci 5838c2ecf20Sopenharmony_ci/* 5848c2ecf20Sopenharmony_ci * operators 5858c2ecf20Sopenharmony_ci */ 5868c2ecf20Sopenharmony_cistatic const struct snd_pcm_ops snd_cx88_pcm_ops = { 5878c2ecf20Sopenharmony_ci .open = snd_cx88_pcm_open, 5888c2ecf20Sopenharmony_ci .close = snd_cx88_close, 5898c2ecf20Sopenharmony_ci .hw_params = snd_cx88_hw_params, 5908c2ecf20Sopenharmony_ci .hw_free = snd_cx88_hw_free, 5918c2ecf20Sopenharmony_ci .prepare = snd_cx88_prepare, 5928c2ecf20Sopenharmony_ci .trigger = snd_cx88_card_trigger, 5938c2ecf20Sopenharmony_ci .pointer = snd_cx88_pointer, 5948c2ecf20Sopenharmony_ci .page = snd_cx88_page, 5958c2ecf20Sopenharmony_ci}; 5968c2ecf20Sopenharmony_ci 5978c2ecf20Sopenharmony_ci/* 5988c2ecf20Sopenharmony_ci * create a PCM device 5998c2ecf20Sopenharmony_ci */ 6008c2ecf20Sopenharmony_cistatic int snd_cx88_pcm(struct cx88_audio_dev *chip, int device, 6018c2ecf20Sopenharmony_ci const char *name) 6028c2ecf20Sopenharmony_ci{ 6038c2ecf20Sopenharmony_ci int err; 6048c2ecf20Sopenharmony_ci struct snd_pcm *pcm; 6058c2ecf20Sopenharmony_ci 6068c2ecf20Sopenharmony_ci err = snd_pcm_new(chip->card, name, device, 0, 1, &pcm); 6078c2ecf20Sopenharmony_ci if (err < 0) 6088c2ecf20Sopenharmony_ci return err; 6098c2ecf20Sopenharmony_ci pcm->private_data = chip; 6108c2ecf20Sopenharmony_ci strscpy(pcm->name, name, sizeof(pcm->name)); 6118c2ecf20Sopenharmony_ci snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cx88_pcm_ops); 6128c2ecf20Sopenharmony_ci 6138c2ecf20Sopenharmony_ci return 0; 6148c2ecf20Sopenharmony_ci} 6158c2ecf20Sopenharmony_ci 6168c2ecf20Sopenharmony_ci/* 6178c2ecf20Sopenharmony_ci * CONTROL INTERFACE 6188c2ecf20Sopenharmony_ci */ 6198c2ecf20Sopenharmony_cistatic int snd_cx88_volume_info(struct snd_kcontrol *kcontrol, 6208c2ecf20Sopenharmony_ci struct snd_ctl_elem_info *info) 6218c2ecf20Sopenharmony_ci{ 6228c2ecf20Sopenharmony_ci info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; 6238c2ecf20Sopenharmony_ci info->count = 2; 6248c2ecf20Sopenharmony_ci info->value.integer.min = 0; 6258c2ecf20Sopenharmony_ci info->value.integer.max = 0x3f; 6268c2ecf20Sopenharmony_ci 6278c2ecf20Sopenharmony_ci return 0; 6288c2ecf20Sopenharmony_ci} 6298c2ecf20Sopenharmony_ci 6308c2ecf20Sopenharmony_cistatic int snd_cx88_volume_get(struct snd_kcontrol *kcontrol, 6318c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *value) 6328c2ecf20Sopenharmony_ci{ 6338c2ecf20Sopenharmony_ci struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol); 6348c2ecf20Sopenharmony_ci struct cx88_core *core = chip->core; 6358c2ecf20Sopenharmony_ci int vol = 0x3f - (cx_read(AUD_VOL_CTL) & 0x3f), 6368c2ecf20Sopenharmony_ci bal = cx_read(AUD_BAL_CTL); 6378c2ecf20Sopenharmony_ci 6388c2ecf20Sopenharmony_ci value->value.integer.value[(bal & 0x40) ? 0 : 1] = vol; 6398c2ecf20Sopenharmony_ci vol -= (bal & 0x3f); 6408c2ecf20Sopenharmony_ci value->value.integer.value[(bal & 0x40) ? 1 : 0] = vol < 0 ? 0 : vol; 6418c2ecf20Sopenharmony_ci 6428c2ecf20Sopenharmony_ci return 0; 6438c2ecf20Sopenharmony_ci} 6448c2ecf20Sopenharmony_ci 6458c2ecf20Sopenharmony_cistatic void snd_cx88_wm8775_volume_put(struct snd_kcontrol *kcontrol, 6468c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *value) 6478c2ecf20Sopenharmony_ci{ 6488c2ecf20Sopenharmony_ci struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol); 6498c2ecf20Sopenharmony_ci struct cx88_core *core = chip->core; 6508c2ecf20Sopenharmony_ci u16 left = value->value.integer.value[0]; 6518c2ecf20Sopenharmony_ci u16 right = value->value.integer.value[1]; 6528c2ecf20Sopenharmony_ci int v, b; 6538c2ecf20Sopenharmony_ci 6548c2ecf20Sopenharmony_ci /* Pass volume & balance onto any WM8775 */ 6558c2ecf20Sopenharmony_ci if (left >= right) { 6568c2ecf20Sopenharmony_ci v = left << 10; 6578c2ecf20Sopenharmony_ci b = left ? (0x8000 * right) / left : 0x8000; 6588c2ecf20Sopenharmony_ci } else { 6598c2ecf20Sopenharmony_ci v = right << 10; 6608c2ecf20Sopenharmony_ci b = right ? 0xffff - (0x8000 * left) / right : 0x8000; 6618c2ecf20Sopenharmony_ci } 6628c2ecf20Sopenharmony_ci wm8775_s_ctrl(core, V4L2_CID_AUDIO_VOLUME, v); 6638c2ecf20Sopenharmony_ci wm8775_s_ctrl(core, V4L2_CID_AUDIO_BALANCE, b); 6648c2ecf20Sopenharmony_ci} 6658c2ecf20Sopenharmony_ci 6668c2ecf20Sopenharmony_ci/* OK - TODO: test it */ 6678c2ecf20Sopenharmony_cistatic int snd_cx88_volume_put(struct snd_kcontrol *kcontrol, 6688c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *value) 6698c2ecf20Sopenharmony_ci{ 6708c2ecf20Sopenharmony_ci struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol); 6718c2ecf20Sopenharmony_ci struct cx88_core *core = chip->core; 6728c2ecf20Sopenharmony_ci int left, right, v, b; 6738c2ecf20Sopenharmony_ci int changed = 0; 6748c2ecf20Sopenharmony_ci u32 old; 6758c2ecf20Sopenharmony_ci 6768c2ecf20Sopenharmony_ci if (core->sd_wm8775) 6778c2ecf20Sopenharmony_ci snd_cx88_wm8775_volume_put(kcontrol, value); 6788c2ecf20Sopenharmony_ci 6798c2ecf20Sopenharmony_ci left = value->value.integer.value[0] & 0x3f; 6808c2ecf20Sopenharmony_ci right = value->value.integer.value[1] & 0x3f; 6818c2ecf20Sopenharmony_ci b = right - left; 6828c2ecf20Sopenharmony_ci if (b < 0) { 6838c2ecf20Sopenharmony_ci v = 0x3f - left; 6848c2ecf20Sopenharmony_ci b = (-b) | 0x40; 6858c2ecf20Sopenharmony_ci } else { 6868c2ecf20Sopenharmony_ci v = 0x3f - right; 6878c2ecf20Sopenharmony_ci } 6888c2ecf20Sopenharmony_ci /* Do we really know this will always be called with IRQs on? */ 6898c2ecf20Sopenharmony_ci spin_lock_irq(&chip->reg_lock); 6908c2ecf20Sopenharmony_ci old = cx_read(AUD_VOL_CTL); 6918c2ecf20Sopenharmony_ci if (v != (old & 0x3f)) { 6928c2ecf20Sopenharmony_ci cx_swrite(SHADOW_AUD_VOL_CTL, AUD_VOL_CTL, (old & ~0x3f) | v); 6938c2ecf20Sopenharmony_ci changed = 1; 6948c2ecf20Sopenharmony_ci } 6958c2ecf20Sopenharmony_ci if ((cx_read(AUD_BAL_CTL) & 0x7f) != b) { 6968c2ecf20Sopenharmony_ci cx_write(AUD_BAL_CTL, b); 6978c2ecf20Sopenharmony_ci changed = 1; 6988c2ecf20Sopenharmony_ci } 6998c2ecf20Sopenharmony_ci spin_unlock_irq(&chip->reg_lock); 7008c2ecf20Sopenharmony_ci 7018c2ecf20Sopenharmony_ci return changed; 7028c2ecf20Sopenharmony_ci} 7038c2ecf20Sopenharmony_ci 7048c2ecf20Sopenharmony_cistatic const DECLARE_TLV_DB_SCALE(snd_cx88_db_scale, -6300, 100, 0); 7058c2ecf20Sopenharmony_ci 7068c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new snd_cx88_volume = { 7078c2ecf20Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 7088c2ecf20Sopenharmony_ci .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | 7098c2ecf20Sopenharmony_ci SNDRV_CTL_ELEM_ACCESS_TLV_READ, 7108c2ecf20Sopenharmony_ci .name = "Analog-TV Volume", 7118c2ecf20Sopenharmony_ci .info = snd_cx88_volume_info, 7128c2ecf20Sopenharmony_ci .get = snd_cx88_volume_get, 7138c2ecf20Sopenharmony_ci .put = snd_cx88_volume_put, 7148c2ecf20Sopenharmony_ci .tlv.p = snd_cx88_db_scale, 7158c2ecf20Sopenharmony_ci}; 7168c2ecf20Sopenharmony_ci 7178c2ecf20Sopenharmony_cistatic int snd_cx88_switch_get(struct snd_kcontrol *kcontrol, 7188c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *value) 7198c2ecf20Sopenharmony_ci{ 7208c2ecf20Sopenharmony_ci struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol); 7218c2ecf20Sopenharmony_ci struct cx88_core *core = chip->core; 7228c2ecf20Sopenharmony_ci u32 bit = kcontrol->private_value; 7238c2ecf20Sopenharmony_ci 7248c2ecf20Sopenharmony_ci value->value.integer.value[0] = !(cx_read(AUD_VOL_CTL) & bit); 7258c2ecf20Sopenharmony_ci return 0; 7268c2ecf20Sopenharmony_ci} 7278c2ecf20Sopenharmony_ci 7288c2ecf20Sopenharmony_cistatic int snd_cx88_switch_put(struct snd_kcontrol *kcontrol, 7298c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *value) 7308c2ecf20Sopenharmony_ci{ 7318c2ecf20Sopenharmony_ci struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol); 7328c2ecf20Sopenharmony_ci struct cx88_core *core = chip->core; 7338c2ecf20Sopenharmony_ci u32 bit = kcontrol->private_value; 7348c2ecf20Sopenharmony_ci int ret = 0; 7358c2ecf20Sopenharmony_ci u32 vol; 7368c2ecf20Sopenharmony_ci 7378c2ecf20Sopenharmony_ci spin_lock_irq(&chip->reg_lock); 7388c2ecf20Sopenharmony_ci vol = cx_read(AUD_VOL_CTL); 7398c2ecf20Sopenharmony_ci if (value->value.integer.value[0] != !(vol & bit)) { 7408c2ecf20Sopenharmony_ci vol ^= bit; 7418c2ecf20Sopenharmony_ci cx_swrite(SHADOW_AUD_VOL_CTL, AUD_VOL_CTL, vol); 7428c2ecf20Sopenharmony_ci /* Pass mute onto any WM8775 */ 7438c2ecf20Sopenharmony_ci if (core->sd_wm8775 && ((1 << 6) == bit)) 7448c2ecf20Sopenharmony_ci wm8775_s_ctrl(core, 7458c2ecf20Sopenharmony_ci V4L2_CID_AUDIO_MUTE, 0 != (vol & bit)); 7468c2ecf20Sopenharmony_ci ret = 1; 7478c2ecf20Sopenharmony_ci } 7488c2ecf20Sopenharmony_ci spin_unlock_irq(&chip->reg_lock); 7498c2ecf20Sopenharmony_ci return ret; 7508c2ecf20Sopenharmony_ci} 7518c2ecf20Sopenharmony_ci 7528c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new snd_cx88_dac_switch = { 7538c2ecf20Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 7548c2ecf20Sopenharmony_ci .name = "Audio-Out Switch", 7558c2ecf20Sopenharmony_ci .info = snd_ctl_boolean_mono_info, 7568c2ecf20Sopenharmony_ci .get = snd_cx88_switch_get, 7578c2ecf20Sopenharmony_ci .put = snd_cx88_switch_put, 7588c2ecf20Sopenharmony_ci .private_value = (1 << 8), 7598c2ecf20Sopenharmony_ci}; 7608c2ecf20Sopenharmony_ci 7618c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new snd_cx88_source_switch = { 7628c2ecf20Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 7638c2ecf20Sopenharmony_ci .name = "Analog-TV Switch", 7648c2ecf20Sopenharmony_ci .info = snd_ctl_boolean_mono_info, 7658c2ecf20Sopenharmony_ci .get = snd_cx88_switch_get, 7668c2ecf20Sopenharmony_ci .put = snd_cx88_switch_put, 7678c2ecf20Sopenharmony_ci .private_value = (1 << 6), 7688c2ecf20Sopenharmony_ci}; 7698c2ecf20Sopenharmony_ci 7708c2ecf20Sopenharmony_cistatic int snd_cx88_alc_get(struct snd_kcontrol *kcontrol, 7718c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *value) 7728c2ecf20Sopenharmony_ci{ 7738c2ecf20Sopenharmony_ci struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol); 7748c2ecf20Sopenharmony_ci struct cx88_core *core = chip->core; 7758c2ecf20Sopenharmony_ci s32 val; 7768c2ecf20Sopenharmony_ci 7778c2ecf20Sopenharmony_ci val = wm8775_g_ctrl(core, V4L2_CID_AUDIO_LOUDNESS); 7788c2ecf20Sopenharmony_ci value->value.integer.value[0] = val ? 1 : 0; 7798c2ecf20Sopenharmony_ci return 0; 7808c2ecf20Sopenharmony_ci} 7818c2ecf20Sopenharmony_ci 7828c2ecf20Sopenharmony_cistatic int snd_cx88_alc_put(struct snd_kcontrol *kcontrol, 7838c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *value) 7848c2ecf20Sopenharmony_ci{ 7858c2ecf20Sopenharmony_ci struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol); 7868c2ecf20Sopenharmony_ci struct cx88_core *core = chip->core; 7878c2ecf20Sopenharmony_ci 7888c2ecf20Sopenharmony_ci wm8775_s_ctrl(core, V4L2_CID_AUDIO_LOUDNESS, 7898c2ecf20Sopenharmony_ci value->value.integer.value[0] != 0); 7908c2ecf20Sopenharmony_ci return 0; 7918c2ecf20Sopenharmony_ci} 7928c2ecf20Sopenharmony_ci 7938c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new snd_cx88_alc_switch = { 7948c2ecf20Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 7958c2ecf20Sopenharmony_ci .name = "Line-In ALC Switch", 7968c2ecf20Sopenharmony_ci .info = snd_ctl_boolean_mono_info, 7978c2ecf20Sopenharmony_ci .get = snd_cx88_alc_get, 7988c2ecf20Sopenharmony_ci .put = snd_cx88_alc_put, 7998c2ecf20Sopenharmony_ci}; 8008c2ecf20Sopenharmony_ci 8018c2ecf20Sopenharmony_ci/* 8028c2ecf20Sopenharmony_ci * Basic Flow for Sound Devices 8038c2ecf20Sopenharmony_ci */ 8048c2ecf20Sopenharmony_ci 8058c2ecf20Sopenharmony_ci/* 8068c2ecf20Sopenharmony_ci * PCI ID Table - 14f1:8801 and 14f1:8811 means function 1: Audio 8078c2ecf20Sopenharmony_ci * Only boards with eeprom and byte 1 at eeprom=1 have it 8088c2ecf20Sopenharmony_ci */ 8098c2ecf20Sopenharmony_ci 8108c2ecf20Sopenharmony_cistatic const struct pci_device_id cx88_audio_pci_tbl[] = { 8118c2ecf20Sopenharmony_ci {0x14f1, 0x8801, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, 8128c2ecf20Sopenharmony_ci {0x14f1, 0x8811, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, 8138c2ecf20Sopenharmony_ci {0, } 8148c2ecf20Sopenharmony_ci}; 8158c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, cx88_audio_pci_tbl); 8168c2ecf20Sopenharmony_ci 8178c2ecf20Sopenharmony_ci/* 8188c2ecf20Sopenharmony_ci * Chip-specific destructor 8198c2ecf20Sopenharmony_ci */ 8208c2ecf20Sopenharmony_ci 8218c2ecf20Sopenharmony_cistatic int snd_cx88_free(struct cx88_audio_dev *chip) 8228c2ecf20Sopenharmony_ci{ 8238c2ecf20Sopenharmony_ci if (chip->irq >= 0) 8248c2ecf20Sopenharmony_ci free_irq(chip->irq, chip); 8258c2ecf20Sopenharmony_ci 8268c2ecf20Sopenharmony_ci cx88_core_put(chip->core, chip->pci); 8278c2ecf20Sopenharmony_ci 8288c2ecf20Sopenharmony_ci pci_disable_device(chip->pci); 8298c2ecf20Sopenharmony_ci return 0; 8308c2ecf20Sopenharmony_ci} 8318c2ecf20Sopenharmony_ci 8328c2ecf20Sopenharmony_ci/* 8338c2ecf20Sopenharmony_ci * Component Destructor 8348c2ecf20Sopenharmony_ci */ 8358c2ecf20Sopenharmony_cistatic void snd_cx88_dev_free(struct snd_card *card) 8368c2ecf20Sopenharmony_ci{ 8378c2ecf20Sopenharmony_ci struct cx88_audio_dev *chip = card->private_data; 8388c2ecf20Sopenharmony_ci 8398c2ecf20Sopenharmony_ci snd_cx88_free(chip); 8408c2ecf20Sopenharmony_ci} 8418c2ecf20Sopenharmony_ci 8428c2ecf20Sopenharmony_ci/* 8438c2ecf20Sopenharmony_ci * Alsa Constructor - Component probe 8448c2ecf20Sopenharmony_ci */ 8458c2ecf20Sopenharmony_ci 8468c2ecf20Sopenharmony_cistatic int devno; 8478c2ecf20Sopenharmony_cistatic int snd_cx88_create(struct snd_card *card, struct pci_dev *pci, 8488c2ecf20Sopenharmony_ci struct cx88_audio_dev **rchip, 8498c2ecf20Sopenharmony_ci struct cx88_core **core_ptr) 8508c2ecf20Sopenharmony_ci{ 8518c2ecf20Sopenharmony_ci struct cx88_audio_dev *chip; 8528c2ecf20Sopenharmony_ci struct cx88_core *core; 8538c2ecf20Sopenharmony_ci int err; 8548c2ecf20Sopenharmony_ci unsigned char pci_lat; 8558c2ecf20Sopenharmony_ci 8568c2ecf20Sopenharmony_ci *rchip = NULL; 8578c2ecf20Sopenharmony_ci 8588c2ecf20Sopenharmony_ci err = pci_enable_device(pci); 8598c2ecf20Sopenharmony_ci if (err < 0) 8608c2ecf20Sopenharmony_ci return err; 8618c2ecf20Sopenharmony_ci 8628c2ecf20Sopenharmony_ci pci_set_master(pci); 8638c2ecf20Sopenharmony_ci 8648c2ecf20Sopenharmony_ci chip = card->private_data; 8658c2ecf20Sopenharmony_ci 8668c2ecf20Sopenharmony_ci core = cx88_core_get(pci); 8678c2ecf20Sopenharmony_ci if (!core) { 8688c2ecf20Sopenharmony_ci err = -EINVAL; 8698c2ecf20Sopenharmony_ci return err; 8708c2ecf20Sopenharmony_ci } 8718c2ecf20Sopenharmony_ci 8728c2ecf20Sopenharmony_ci err = pci_set_dma_mask(pci, DMA_BIT_MASK(32)); 8738c2ecf20Sopenharmony_ci if (err) { 8748c2ecf20Sopenharmony_ci dprintk(0, "%s/1: Oops: no 32bit PCI DMA ???\n", core->name); 8758c2ecf20Sopenharmony_ci cx88_core_put(core, pci); 8768c2ecf20Sopenharmony_ci return err; 8778c2ecf20Sopenharmony_ci } 8788c2ecf20Sopenharmony_ci 8798c2ecf20Sopenharmony_ci /* pci init */ 8808c2ecf20Sopenharmony_ci chip->card = card; 8818c2ecf20Sopenharmony_ci chip->pci = pci; 8828c2ecf20Sopenharmony_ci chip->irq = -1; 8838c2ecf20Sopenharmony_ci spin_lock_init(&chip->reg_lock); 8848c2ecf20Sopenharmony_ci 8858c2ecf20Sopenharmony_ci chip->core = core; 8868c2ecf20Sopenharmony_ci 8878c2ecf20Sopenharmony_ci /* get irq */ 8888c2ecf20Sopenharmony_ci err = request_irq(chip->pci->irq, cx8801_irq, 8898c2ecf20Sopenharmony_ci IRQF_SHARED, chip->core->name, chip); 8908c2ecf20Sopenharmony_ci if (err < 0) { 8918c2ecf20Sopenharmony_ci dprintk(0, "%s: can't get IRQ %d\n", 8928c2ecf20Sopenharmony_ci chip->core->name, chip->pci->irq); 8938c2ecf20Sopenharmony_ci return err; 8948c2ecf20Sopenharmony_ci } 8958c2ecf20Sopenharmony_ci 8968c2ecf20Sopenharmony_ci /* print pci info */ 8978c2ecf20Sopenharmony_ci pci_read_config_byte(pci, PCI_LATENCY_TIMER, &pci_lat); 8988c2ecf20Sopenharmony_ci 8998c2ecf20Sopenharmony_ci dprintk(1, 9008c2ecf20Sopenharmony_ci "ALSA %s/%i: found at %s, rev: %d, irq: %d, latency: %d, mmio: 0x%llx\n", 9018c2ecf20Sopenharmony_ci core->name, devno, 9028c2ecf20Sopenharmony_ci pci_name(pci), pci->revision, pci->irq, 9038c2ecf20Sopenharmony_ci pci_lat, (unsigned long long)pci_resource_start(pci, 0)); 9048c2ecf20Sopenharmony_ci 9058c2ecf20Sopenharmony_ci chip->irq = pci->irq; 9068c2ecf20Sopenharmony_ci synchronize_irq(chip->irq); 9078c2ecf20Sopenharmony_ci 9088c2ecf20Sopenharmony_ci *rchip = chip; 9098c2ecf20Sopenharmony_ci *core_ptr = core; 9108c2ecf20Sopenharmony_ci 9118c2ecf20Sopenharmony_ci return 0; 9128c2ecf20Sopenharmony_ci} 9138c2ecf20Sopenharmony_ci 9148c2ecf20Sopenharmony_cistatic int cx88_audio_initdev(struct pci_dev *pci, 9158c2ecf20Sopenharmony_ci const struct pci_device_id *pci_id) 9168c2ecf20Sopenharmony_ci{ 9178c2ecf20Sopenharmony_ci struct snd_card *card; 9188c2ecf20Sopenharmony_ci struct cx88_audio_dev *chip; 9198c2ecf20Sopenharmony_ci struct cx88_core *core = NULL; 9208c2ecf20Sopenharmony_ci int err; 9218c2ecf20Sopenharmony_ci 9228c2ecf20Sopenharmony_ci if (devno >= SNDRV_CARDS) 9238c2ecf20Sopenharmony_ci return (-ENODEV); 9248c2ecf20Sopenharmony_ci 9258c2ecf20Sopenharmony_ci if (!enable[devno]) { 9268c2ecf20Sopenharmony_ci ++devno; 9278c2ecf20Sopenharmony_ci return (-ENOENT); 9288c2ecf20Sopenharmony_ci } 9298c2ecf20Sopenharmony_ci 9308c2ecf20Sopenharmony_ci err = snd_card_new(&pci->dev, index[devno], id[devno], THIS_MODULE, 9318c2ecf20Sopenharmony_ci sizeof(struct cx88_audio_dev), &card); 9328c2ecf20Sopenharmony_ci if (err < 0) 9338c2ecf20Sopenharmony_ci return err; 9348c2ecf20Sopenharmony_ci 9358c2ecf20Sopenharmony_ci card->private_free = snd_cx88_dev_free; 9368c2ecf20Sopenharmony_ci 9378c2ecf20Sopenharmony_ci err = snd_cx88_create(card, pci, &chip, &core); 9388c2ecf20Sopenharmony_ci if (err < 0) 9398c2ecf20Sopenharmony_ci goto error; 9408c2ecf20Sopenharmony_ci 9418c2ecf20Sopenharmony_ci err = snd_cx88_pcm(chip, 0, "CX88 Digital"); 9428c2ecf20Sopenharmony_ci if (err < 0) 9438c2ecf20Sopenharmony_ci goto error; 9448c2ecf20Sopenharmony_ci 9458c2ecf20Sopenharmony_ci err = snd_ctl_add(card, snd_ctl_new1(&snd_cx88_volume, chip)); 9468c2ecf20Sopenharmony_ci if (err < 0) 9478c2ecf20Sopenharmony_ci goto error; 9488c2ecf20Sopenharmony_ci err = snd_ctl_add(card, snd_ctl_new1(&snd_cx88_dac_switch, chip)); 9498c2ecf20Sopenharmony_ci if (err < 0) 9508c2ecf20Sopenharmony_ci goto error; 9518c2ecf20Sopenharmony_ci err = snd_ctl_add(card, snd_ctl_new1(&snd_cx88_source_switch, chip)); 9528c2ecf20Sopenharmony_ci if (err < 0) 9538c2ecf20Sopenharmony_ci goto error; 9548c2ecf20Sopenharmony_ci 9558c2ecf20Sopenharmony_ci /* If there's a wm8775 then add a Line-In ALC switch */ 9568c2ecf20Sopenharmony_ci if (core->sd_wm8775) { 9578c2ecf20Sopenharmony_ci err = snd_ctl_add(card, snd_ctl_new1(&snd_cx88_alc_switch, chip)); 9588c2ecf20Sopenharmony_ci if (err < 0) 9598c2ecf20Sopenharmony_ci goto error; 9608c2ecf20Sopenharmony_ci } 9618c2ecf20Sopenharmony_ci 9628c2ecf20Sopenharmony_ci strscpy(card->driver, "CX88x", sizeof(card->driver)); 9638c2ecf20Sopenharmony_ci sprintf(card->shortname, "Conexant CX%x", pci->device); 9648c2ecf20Sopenharmony_ci sprintf(card->longname, "%s at %#llx", 9658c2ecf20Sopenharmony_ci card->shortname, 9668c2ecf20Sopenharmony_ci (unsigned long long)pci_resource_start(pci, 0)); 9678c2ecf20Sopenharmony_ci strscpy(card->mixername, "CX88", sizeof(card->mixername)); 9688c2ecf20Sopenharmony_ci 9698c2ecf20Sopenharmony_ci dprintk(0, "%s/%i: ALSA support for cx2388x boards\n", 9708c2ecf20Sopenharmony_ci card->driver, devno); 9718c2ecf20Sopenharmony_ci 9728c2ecf20Sopenharmony_ci err = snd_card_register(card); 9738c2ecf20Sopenharmony_ci if (err < 0) 9748c2ecf20Sopenharmony_ci goto error; 9758c2ecf20Sopenharmony_ci pci_set_drvdata(pci, card); 9768c2ecf20Sopenharmony_ci 9778c2ecf20Sopenharmony_ci devno++; 9788c2ecf20Sopenharmony_ci return 0; 9798c2ecf20Sopenharmony_ci 9808c2ecf20Sopenharmony_cierror: 9818c2ecf20Sopenharmony_ci snd_card_free(card); 9828c2ecf20Sopenharmony_ci return err; 9838c2ecf20Sopenharmony_ci} 9848c2ecf20Sopenharmony_ci 9858c2ecf20Sopenharmony_ci/* 9868c2ecf20Sopenharmony_ci * ALSA destructor 9878c2ecf20Sopenharmony_ci */ 9888c2ecf20Sopenharmony_cistatic void cx88_audio_finidev(struct pci_dev *pci) 9898c2ecf20Sopenharmony_ci{ 9908c2ecf20Sopenharmony_ci struct snd_card *card = pci_get_drvdata(pci); 9918c2ecf20Sopenharmony_ci 9928c2ecf20Sopenharmony_ci snd_card_free(card); 9938c2ecf20Sopenharmony_ci 9948c2ecf20Sopenharmony_ci devno--; 9958c2ecf20Sopenharmony_ci} 9968c2ecf20Sopenharmony_ci 9978c2ecf20Sopenharmony_ci/* 9988c2ecf20Sopenharmony_ci * PCI driver definition 9998c2ecf20Sopenharmony_ci */ 10008c2ecf20Sopenharmony_ci 10018c2ecf20Sopenharmony_cistatic struct pci_driver cx88_audio_pci_driver = { 10028c2ecf20Sopenharmony_ci .name = "cx88_audio", 10038c2ecf20Sopenharmony_ci .id_table = cx88_audio_pci_tbl, 10048c2ecf20Sopenharmony_ci .probe = cx88_audio_initdev, 10058c2ecf20Sopenharmony_ci .remove = cx88_audio_finidev, 10068c2ecf20Sopenharmony_ci}; 10078c2ecf20Sopenharmony_ci 10088c2ecf20Sopenharmony_cimodule_pci_driver(cx88_audio_pci_driver); 1009