18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Driver for C-Media CMI8328-based soundcards, such as AudioExcel AV500 48c2ecf20Sopenharmony_ci * Copyright (c) 2012 Ondrej Zary 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * AudioExcel AV500 card consists of: 78c2ecf20Sopenharmony_ci * - CMI8328 - main chip (SB Pro emulation, gameport, OPL3, MPU401, CD-ROM) 88c2ecf20Sopenharmony_ci * - CS4231A - WSS codec 98c2ecf20Sopenharmony_ci * - Dream SAM9233+GMS950400+RAM+ROM: Wavetable MIDI, connected to MPU401 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/init.h> 138c2ecf20Sopenharmony_ci#include <linux/isa.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/gameport.h> 168c2ecf20Sopenharmony_ci#include <asm/dma.h> 178c2ecf20Sopenharmony_ci#include <sound/core.h> 188c2ecf20Sopenharmony_ci#include <sound/wss.h> 198c2ecf20Sopenharmony_ci#include <sound/opl3.h> 208c2ecf20Sopenharmony_ci#include <sound/mpu401.h> 218c2ecf20Sopenharmony_ci#define SNDRV_LEGACY_FIND_FREE_IOPORT 228c2ecf20Sopenharmony_ci#define SNDRV_LEGACY_FIND_FREE_IRQ 238c2ecf20Sopenharmony_ci#define SNDRV_LEGACY_FIND_FREE_DMA 248c2ecf20Sopenharmony_ci#include <sound/initval.h> 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ondrej Zary <linux@rainbow-software.org>"); 278c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("C-Media CMI8328"); 288c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_GAMEPORT) 318c2ecf20Sopenharmony_ci#define SUPPORT_JOYSTICK 1 328c2ecf20Sopenharmony_ci#endif 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci/* I/O port is configured by jumpers on the card to one of these */ 358c2ecf20Sopenharmony_cistatic const int cmi8328_ports[] = { 0x530, 0xe80, 0xf40, 0x604 }; 368c2ecf20Sopenharmony_ci#define CMI8328_MAX ARRAY_SIZE(cmi8328_ports) 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistatic int index[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = -1}; 398c2ecf20Sopenharmony_cistatic char *id[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = NULL}; 408c2ecf20Sopenharmony_cistatic long port[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_PORT}; 418c2ecf20Sopenharmony_cistatic int irq[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_IRQ}; 428c2ecf20Sopenharmony_cistatic int dma1[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_DMA}; 438c2ecf20Sopenharmony_cistatic int dma2[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_DMA}; 448c2ecf20Sopenharmony_cistatic long mpuport[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_PORT}; 458c2ecf20Sopenharmony_cistatic int mpuirq[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_IRQ}; 468c2ecf20Sopenharmony_ci#ifdef SUPPORT_JOYSTICK 478c2ecf20Sopenharmony_cistatic bool gameport[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = true}; 488c2ecf20Sopenharmony_ci#endif 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cimodule_param_array(index, int, NULL, 0444); 518c2ecf20Sopenharmony_ciMODULE_PARM_DESC(index, "Index value for CMI8328 soundcard."); 528c2ecf20Sopenharmony_cimodule_param_array(id, charp, NULL, 0444); 538c2ecf20Sopenharmony_ciMODULE_PARM_DESC(id, "ID string for CMI8328 soundcard."); 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cimodule_param_hw_array(port, long, ioport, NULL, 0444); 568c2ecf20Sopenharmony_ciMODULE_PARM_DESC(port, "Port # for CMI8328 driver."); 578c2ecf20Sopenharmony_cimodule_param_hw_array(irq, int, irq, NULL, 0444); 588c2ecf20Sopenharmony_ciMODULE_PARM_DESC(irq, "IRQ # for CMI8328 driver."); 598c2ecf20Sopenharmony_cimodule_param_hw_array(dma1, int, dma, NULL, 0444); 608c2ecf20Sopenharmony_ciMODULE_PARM_DESC(dma1, "DMA1 for CMI8328 driver."); 618c2ecf20Sopenharmony_cimodule_param_hw_array(dma2, int, dma, NULL, 0444); 628c2ecf20Sopenharmony_ciMODULE_PARM_DESC(dma2, "DMA2 for CMI8328 driver."); 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cimodule_param_hw_array(mpuport, long, ioport, NULL, 0444); 658c2ecf20Sopenharmony_ciMODULE_PARM_DESC(mpuport, "MPU-401 port # for CMI8328 driver."); 668c2ecf20Sopenharmony_cimodule_param_hw_array(mpuirq, int, irq, NULL, 0444); 678c2ecf20Sopenharmony_ciMODULE_PARM_DESC(mpuirq, "IRQ # for CMI8328 MPU-401 port."); 688c2ecf20Sopenharmony_ci#ifdef SUPPORT_JOYSTICK 698c2ecf20Sopenharmony_cimodule_param_array(gameport, bool, NULL, 0444); 708c2ecf20Sopenharmony_ciMODULE_PARM_DESC(gameport, "Enable gameport."); 718c2ecf20Sopenharmony_ci#endif 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistruct snd_cmi8328 { 748c2ecf20Sopenharmony_ci u16 port; 758c2ecf20Sopenharmony_ci u8 cfg[3]; 768c2ecf20Sopenharmony_ci u8 wss_cfg; 778c2ecf20Sopenharmony_ci struct snd_card *card; 788c2ecf20Sopenharmony_ci struct snd_wss *wss; 798c2ecf20Sopenharmony_ci#ifdef SUPPORT_JOYSTICK 808c2ecf20Sopenharmony_ci struct gameport *gameport; 818c2ecf20Sopenharmony_ci#endif 828c2ecf20Sopenharmony_ci}; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci/* CMI8328 configuration registers */ 858c2ecf20Sopenharmony_ci#define CFG1 0x61 868c2ecf20Sopenharmony_ci#define CFG1_SB_DISABLE (1 << 0) 878c2ecf20Sopenharmony_ci#define CFG1_GAMEPORT (1 << 1) 888c2ecf20Sopenharmony_ci/* 898c2ecf20Sopenharmony_ci * bit 0: SB: 0=enabled, 1=disabled 908c2ecf20Sopenharmony_ci * bit 1: gameport: 0=disabled, 1=enabled 918c2ecf20Sopenharmony_ci * bits 2-4: SB IRQ: 001=3, 010=5, 011=7, 100=9, 101=10, 110=11 928c2ecf20Sopenharmony_ci * bits 5-6: SB DMA: 00=disabled (when SB disabled), 01=DMA0, 10=DMA1, 11=DMA3 938c2ecf20Sopenharmony_ci * bit 7: SB port: 0=0x220, 1=0x240 948c2ecf20Sopenharmony_ci */ 958c2ecf20Sopenharmony_ci#define CFG2 0x62 968c2ecf20Sopenharmony_ci#define CFG2_MPU_ENABLE (1 << 2) 978c2ecf20Sopenharmony_ci/* 988c2ecf20Sopenharmony_ci * bits 0-1: CD-ROM mode: 00=disabled, 01=Panasonic, 10=Sony/Mitsumi/Wearnes, 998c2ecf20Sopenharmony_ci 11=IDE 1008c2ecf20Sopenharmony_ci * bit 2: MPU401: 0=disabled, 1=enabled 1018c2ecf20Sopenharmony_ci * bits 3-4: MPU401 IRQ: 00=3, 01=5, 10=7, 11=9, 1028c2ecf20Sopenharmony_ci * bits 5-7: MPU401 port: 000=0x300, 001=0x310, 010=0x320, 011=0x330, 100=0x332, 1038c2ecf20Sopenharmony_ci 101=0x334, 110=0x336 1048c2ecf20Sopenharmony_ci */ 1058c2ecf20Sopenharmony_ci#define CFG3 0x63 1068c2ecf20Sopenharmony_ci/* 1078c2ecf20Sopenharmony_ci * bits 0-2: CD-ROM IRQ: 000=disabled, 001=3, 010=5, 011=7, 100=9, 101=10, 1088c2ecf20Sopenharmony_ci 110=11 1098c2ecf20Sopenharmony_ci * bits 3-4: CD-ROM DMA: 00=disabled, 01=DMA0, 10=DMA1, 11=DMA3 1108c2ecf20Sopenharmony_ci * bits 5-7: CD-ROM port: 000=0x300, 001=0x310, 010=0x320, 011=0x330, 100=0x340, 1118c2ecf20Sopenharmony_ci 101=0x350, 110=0x360, 111=0x370 1128c2ecf20Sopenharmony_ci */ 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistatic u8 snd_cmi8328_cfg_read(u16 port, u8 reg) 1158c2ecf20Sopenharmony_ci{ 1168c2ecf20Sopenharmony_ci outb(0x43, port + 3); 1178c2ecf20Sopenharmony_ci outb(0x21, port + 3); 1188c2ecf20Sopenharmony_ci outb(reg, port + 3); 1198c2ecf20Sopenharmony_ci return inb(port); 1208c2ecf20Sopenharmony_ci} 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_cistatic void snd_cmi8328_cfg_write(u16 port, u8 reg, u8 val) 1238c2ecf20Sopenharmony_ci{ 1248c2ecf20Sopenharmony_ci outb(0x43, port + 3); 1258c2ecf20Sopenharmony_ci outb(0x21, port + 3); 1268c2ecf20Sopenharmony_ci outb(reg, port + 3); 1278c2ecf20Sopenharmony_ci outb(val, port + 3); /* yes, value goes to the same port as index */ 1288c2ecf20Sopenharmony_ci} 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 1318c2ecf20Sopenharmony_cistatic void snd_cmi8328_cfg_save(u16 port, u8 cfg[]) 1328c2ecf20Sopenharmony_ci{ 1338c2ecf20Sopenharmony_ci cfg[0] = snd_cmi8328_cfg_read(port, CFG1); 1348c2ecf20Sopenharmony_ci cfg[1] = snd_cmi8328_cfg_read(port, CFG2); 1358c2ecf20Sopenharmony_ci cfg[2] = snd_cmi8328_cfg_read(port, CFG3); 1368c2ecf20Sopenharmony_ci} 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_cistatic void snd_cmi8328_cfg_restore(u16 port, u8 cfg[]) 1398c2ecf20Sopenharmony_ci{ 1408c2ecf20Sopenharmony_ci snd_cmi8328_cfg_write(port, CFG1, cfg[0]); 1418c2ecf20Sopenharmony_ci snd_cmi8328_cfg_write(port, CFG2, cfg[1]); 1428c2ecf20Sopenharmony_ci snd_cmi8328_cfg_write(port, CFG3, cfg[2]); 1438c2ecf20Sopenharmony_ci} 1448c2ecf20Sopenharmony_ci#endif /* CONFIG_PM */ 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_cistatic int snd_cmi8328_mixer(struct snd_wss *chip) 1478c2ecf20Sopenharmony_ci{ 1488c2ecf20Sopenharmony_ci struct snd_card *card; 1498c2ecf20Sopenharmony_ci struct snd_ctl_elem_id id1, id2; 1508c2ecf20Sopenharmony_ci int err; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci card = chip->card; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci memset(&id1, 0, sizeof(id1)); 1558c2ecf20Sopenharmony_ci memset(&id2, 0, sizeof(id2)); 1568c2ecf20Sopenharmony_ci id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; 1578c2ecf20Sopenharmony_ci /* rename AUX0 switch to CD */ 1588c2ecf20Sopenharmony_ci strcpy(id1.name, "Aux Playback Switch"); 1598c2ecf20Sopenharmony_ci strcpy(id2.name, "CD Playback Switch"); 1608c2ecf20Sopenharmony_ci err = snd_ctl_rename_id(card, &id1, &id2); 1618c2ecf20Sopenharmony_ci if (err < 0) { 1628c2ecf20Sopenharmony_ci snd_printk(KERN_ERR "error renaming control\n"); 1638c2ecf20Sopenharmony_ci return err; 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci /* rename AUX0 volume to CD */ 1668c2ecf20Sopenharmony_ci strcpy(id1.name, "Aux Playback Volume"); 1678c2ecf20Sopenharmony_ci strcpy(id2.name, "CD Playback Volume"); 1688c2ecf20Sopenharmony_ci err = snd_ctl_rename_id(card, &id1, &id2); 1698c2ecf20Sopenharmony_ci if (err < 0) { 1708c2ecf20Sopenharmony_ci snd_printk(KERN_ERR "error renaming control\n"); 1718c2ecf20Sopenharmony_ci return err; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci /* rename AUX1 switch to Synth */ 1748c2ecf20Sopenharmony_ci strcpy(id1.name, "Aux Playback Switch"); 1758c2ecf20Sopenharmony_ci id1.index = 1; 1768c2ecf20Sopenharmony_ci strcpy(id2.name, "Synth Playback Switch"); 1778c2ecf20Sopenharmony_ci err = snd_ctl_rename_id(card, &id1, &id2); 1788c2ecf20Sopenharmony_ci if (err < 0) { 1798c2ecf20Sopenharmony_ci snd_printk(KERN_ERR "error renaming control\n"); 1808c2ecf20Sopenharmony_ci return err; 1818c2ecf20Sopenharmony_ci } 1828c2ecf20Sopenharmony_ci /* rename AUX1 volume to Synth */ 1838c2ecf20Sopenharmony_ci strcpy(id1.name, "Aux Playback Volume"); 1848c2ecf20Sopenharmony_ci id1.index = 1; 1858c2ecf20Sopenharmony_ci strcpy(id2.name, "Synth Playback Volume"); 1868c2ecf20Sopenharmony_ci err = snd_ctl_rename_id(card, &id1, &id2); 1878c2ecf20Sopenharmony_ci if (err < 0) { 1888c2ecf20Sopenharmony_ci snd_printk(KERN_ERR "error renaming control\n"); 1898c2ecf20Sopenharmony_ci return err; 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci return 0; 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci/* find index of an item in "-1"-ended array */ 1968c2ecf20Sopenharmony_cistatic int array_find(const int array[], int item) 1978c2ecf20Sopenharmony_ci{ 1988c2ecf20Sopenharmony_ci int i; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci for (i = 0; array[i] != -1; i++) 2018c2ecf20Sopenharmony_ci if (array[i] == item) 2028c2ecf20Sopenharmony_ci return i; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci return -1; 2058c2ecf20Sopenharmony_ci} 2068c2ecf20Sopenharmony_ci/* the same for long */ 2078c2ecf20Sopenharmony_cistatic int array_find_l(const long array[], long item) 2088c2ecf20Sopenharmony_ci{ 2098c2ecf20Sopenharmony_ci int i; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci for (i = 0; array[i] != -1; i++) 2128c2ecf20Sopenharmony_ci if (array[i] == item) 2138c2ecf20Sopenharmony_ci return i; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci return -1; 2168c2ecf20Sopenharmony_ci} 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_cistatic int snd_cmi8328_probe(struct device *pdev, unsigned int ndev) 2198c2ecf20Sopenharmony_ci{ 2208c2ecf20Sopenharmony_ci struct snd_card *card; 2218c2ecf20Sopenharmony_ci struct snd_opl3 *opl3; 2228c2ecf20Sopenharmony_ci struct snd_cmi8328 *cmi; 2238c2ecf20Sopenharmony_ci#ifdef SUPPORT_JOYSTICK 2248c2ecf20Sopenharmony_ci struct resource *res; 2258c2ecf20Sopenharmony_ci#endif 2268c2ecf20Sopenharmony_ci int err, pos; 2278c2ecf20Sopenharmony_ci static const long mpu_ports[] = { 0x330, 0x300, 0x310, 0x320, 0x332, 0x334, 2288c2ecf20Sopenharmony_ci 0x336, -1 }; 2298c2ecf20Sopenharmony_ci static const u8 mpu_port_bits[] = { 3, 0, 1, 2, 4, 5, 6 }; 2308c2ecf20Sopenharmony_ci static const int mpu_irqs[] = { 9, 7, 5, 3, -1 }; 2318c2ecf20Sopenharmony_ci static const u8 mpu_irq_bits[] = { 3, 2, 1, 0 }; 2328c2ecf20Sopenharmony_ci static const int irqs[] = { 9, 10, 11, 7, -1 }; 2338c2ecf20Sopenharmony_ci static const u8 irq_bits[] = { 2, 3, 4, 1 }; 2348c2ecf20Sopenharmony_ci static const int dma1s[] = { 3, 1, 0, -1 }; 2358c2ecf20Sopenharmony_ci static const u8 dma_bits[] = { 3, 2, 1 }; 2368c2ecf20Sopenharmony_ci static const int dma2s[][2] = { {1, -1}, {0, -1}, {-1, -1}, {0, -1} }; 2378c2ecf20Sopenharmony_ci u16 port = cmi8328_ports[ndev]; 2388c2ecf20Sopenharmony_ci u8 val; 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci /* 0xff is invalid configuration (but settable - hope it isn't set) */ 2418c2ecf20Sopenharmony_ci if (snd_cmi8328_cfg_read(port, CFG1) == 0xff) 2428c2ecf20Sopenharmony_ci return -ENODEV; 2438c2ecf20Sopenharmony_ci /* the SB disable bit must NEVER EVER be cleared or the WSS dies */ 2448c2ecf20Sopenharmony_ci snd_cmi8328_cfg_write(port, CFG1, CFG1_SB_DISABLE); 2458c2ecf20Sopenharmony_ci if (snd_cmi8328_cfg_read(port, CFG1) != CFG1_SB_DISABLE) 2468c2ecf20Sopenharmony_ci return -ENODEV; 2478c2ecf20Sopenharmony_ci /* disable everything first */ 2488c2ecf20Sopenharmony_ci snd_cmi8328_cfg_write(port, CFG2, 0); /* disable CDROM and MPU401 */ 2498c2ecf20Sopenharmony_ci snd_cmi8328_cfg_write(port, CFG3, 0); /* disable CDROM IRQ and DMA */ 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci if (irq[ndev] == SNDRV_AUTO_IRQ) { 2528c2ecf20Sopenharmony_ci irq[ndev] = snd_legacy_find_free_irq(irqs); 2538c2ecf20Sopenharmony_ci if (irq[ndev] < 0) { 2548c2ecf20Sopenharmony_ci snd_printk(KERN_ERR "unable to find a free IRQ\n"); 2558c2ecf20Sopenharmony_ci return -EBUSY; 2568c2ecf20Sopenharmony_ci } 2578c2ecf20Sopenharmony_ci } 2588c2ecf20Sopenharmony_ci if (dma1[ndev] == SNDRV_AUTO_DMA) { 2598c2ecf20Sopenharmony_ci dma1[ndev] = snd_legacy_find_free_dma(dma1s); 2608c2ecf20Sopenharmony_ci if (dma1[ndev] < 0) { 2618c2ecf20Sopenharmony_ci snd_printk(KERN_ERR "unable to find a free DMA1\n"); 2628c2ecf20Sopenharmony_ci return -EBUSY; 2638c2ecf20Sopenharmony_ci } 2648c2ecf20Sopenharmony_ci } 2658c2ecf20Sopenharmony_ci if (dma2[ndev] == SNDRV_AUTO_DMA) { 2668c2ecf20Sopenharmony_ci dma2[ndev] = snd_legacy_find_free_dma(dma2s[dma1[ndev] % 4]); 2678c2ecf20Sopenharmony_ci if (dma2[ndev] < 0) { 2688c2ecf20Sopenharmony_ci snd_printk(KERN_WARNING "unable to find a free DMA2, full-duplex will not work\n"); 2698c2ecf20Sopenharmony_ci dma2[ndev] = -1; 2708c2ecf20Sopenharmony_ci } 2718c2ecf20Sopenharmony_ci } 2728c2ecf20Sopenharmony_ci /* configure WSS IRQ... */ 2738c2ecf20Sopenharmony_ci pos = array_find(irqs, irq[ndev]); 2748c2ecf20Sopenharmony_ci if (pos < 0) { 2758c2ecf20Sopenharmony_ci snd_printk(KERN_ERR "invalid IRQ %d\n", irq[ndev]); 2768c2ecf20Sopenharmony_ci return -EINVAL; 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci val = irq_bits[pos] << 3; 2798c2ecf20Sopenharmony_ci /* ...and DMA... */ 2808c2ecf20Sopenharmony_ci pos = array_find(dma1s, dma1[ndev]); 2818c2ecf20Sopenharmony_ci if (pos < 0) { 2828c2ecf20Sopenharmony_ci snd_printk(KERN_ERR "invalid DMA1 %d\n", dma1[ndev]); 2838c2ecf20Sopenharmony_ci return -EINVAL; 2848c2ecf20Sopenharmony_ci } 2858c2ecf20Sopenharmony_ci val |= dma_bits[pos]; 2868c2ecf20Sopenharmony_ci /* ...and DMA2 */ 2878c2ecf20Sopenharmony_ci if (dma2[ndev] >= 0 && dma1[ndev] != dma2[ndev]) { 2888c2ecf20Sopenharmony_ci pos = array_find(dma2s[dma1[ndev]], dma2[ndev]); 2898c2ecf20Sopenharmony_ci if (pos < 0) { 2908c2ecf20Sopenharmony_ci snd_printk(KERN_ERR "invalid DMA2 %d\n", dma2[ndev]); 2918c2ecf20Sopenharmony_ci return -EINVAL; 2928c2ecf20Sopenharmony_ci } 2938c2ecf20Sopenharmony_ci val |= 0x04; /* enable separate capture DMA */ 2948c2ecf20Sopenharmony_ci } 2958c2ecf20Sopenharmony_ci outb(val, port); 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci err = snd_card_new(pdev, index[ndev], id[ndev], THIS_MODULE, 2988c2ecf20Sopenharmony_ci sizeof(struct snd_cmi8328), &card); 2998c2ecf20Sopenharmony_ci if (err < 0) 3008c2ecf20Sopenharmony_ci return err; 3018c2ecf20Sopenharmony_ci cmi = card->private_data; 3028c2ecf20Sopenharmony_ci cmi->card = card; 3038c2ecf20Sopenharmony_ci cmi->port = port; 3048c2ecf20Sopenharmony_ci cmi->wss_cfg = val; 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ci err = snd_wss_create(card, port + 4, -1, irq[ndev], dma1[ndev], 3078c2ecf20Sopenharmony_ci dma2[ndev], WSS_HW_DETECT, 0, &cmi->wss); 3088c2ecf20Sopenharmony_ci if (err < 0) 3098c2ecf20Sopenharmony_ci goto error; 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci err = snd_wss_pcm(cmi->wss, 0); 3128c2ecf20Sopenharmony_ci if (err < 0) 3138c2ecf20Sopenharmony_ci goto error; 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ci err = snd_wss_mixer(cmi->wss); 3168c2ecf20Sopenharmony_ci if (err < 0) 3178c2ecf20Sopenharmony_ci goto error; 3188c2ecf20Sopenharmony_ci err = snd_cmi8328_mixer(cmi->wss); 3198c2ecf20Sopenharmony_ci if (err < 0) 3208c2ecf20Sopenharmony_ci goto error; 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci if (snd_wss_timer(cmi->wss, 0) < 0) 3238c2ecf20Sopenharmony_ci snd_printk(KERN_WARNING "error initializing WSS timer\n"); 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci if (mpuport[ndev] == SNDRV_AUTO_PORT) { 3268c2ecf20Sopenharmony_ci mpuport[ndev] = snd_legacy_find_free_ioport(mpu_ports, 2); 3278c2ecf20Sopenharmony_ci if (mpuport[ndev] < 0) 3288c2ecf20Sopenharmony_ci snd_printk(KERN_ERR "unable to find a free MPU401 port\n"); 3298c2ecf20Sopenharmony_ci } 3308c2ecf20Sopenharmony_ci if (mpuirq[ndev] == SNDRV_AUTO_IRQ) { 3318c2ecf20Sopenharmony_ci mpuirq[ndev] = snd_legacy_find_free_irq(mpu_irqs); 3328c2ecf20Sopenharmony_ci if (mpuirq[ndev] < 0) 3338c2ecf20Sopenharmony_ci snd_printk(KERN_ERR "unable to find a free MPU401 IRQ\n"); 3348c2ecf20Sopenharmony_ci } 3358c2ecf20Sopenharmony_ci /* enable and configure MPU401 */ 3368c2ecf20Sopenharmony_ci if (mpuport[ndev] > 0 && mpuirq[ndev] > 0) { 3378c2ecf20Sopenharmony_ci val = CFG2_MPU_ENABLE; 3388c2ecf20Sopenharmony_ci pos = array_find_l(mpu_ports, mpuport[ndev]); 3398c2ecf20Sopenharmony_ci if (pos < 0) 3408c2ecf20Sopenharmony_ci snd_printk(KERN_WARNING "invalid MPU401 port 0x%lx\n", 3418c2ecf20Sopenharmony_ci mpuport[ndev]); 3428c2ecf20Sopenharmony_ci else { 3438c2ecf20Sopenharmony_ci val |= mpu_port_bits[pos] << 5; 3448c2ecf20Sopenharmony_ci pos = array_find(mpu_irqs, mpuirq[ndev]); 3458c2ecf20Sopenharmony_ci if (pos < 0) 3468c2ecf20Sopenharmony_ci snd_printk(KERN_WARNING "invalid MPU401 IRQ %d\n", 3478c2ecf20Sopenharmony_ci mpuirq[ndev]); 3488c2ecf20Sopenharmony_ci else { 3498c2ecf20Sopenharmony_ci val |= mpu_irq_bits[pos] << 3; 3508c2ecf20Sopenharmony_ci snd_cmi8328_cfg_write(port, CFG2, val); 3518c2ecf20Sopenharmony_ci if (snd_mpu401_uart_new(card, 0, 3528c2ecf20Sopenharmony_ci MPU401_HW_MPU401, mpuport[ndev], 3538c2ecf20Sopenharmony_ci 0, mpuirq[ndev], NULL) < 0) 3548c2ecf20Sopenharmony_ci snd_printk(KERN_ERR "error initializing MPU401\n"); 3558c2ecf20Sopenharmony_ci } 3568c2ecf20Sopenharmony_ci } 3578c2ecf20Sopenharmony_ci } 3588c2ecf20Sopenharmony_ci /* OPL3 is hardwired to 0x388 and cannot be disabled */ 3598c2ecf20Sopenharmony_ci if (snd_opl3_create(card, 0x388, 0x38a, OPL3_HW_AUTO, 0, &opl3) < 0) 3608c2ecf20Sopenharmony_ci snd_printk(KERN_ERR "error initializing OPL3\n"); 3618c2ecf20Sopenharmony_ci else 3628c2ecf20Sopenharmony_ci if (snd_opl3_hwdep_new(opl3, 0, 1, NULL) < 0) 3638c2ecf20Sopenharmony_ci snd_printk(KERN_WARNING "error initializing OPL3 hwdep\n"); 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_ci strcpy(card->driver, "CMI8328"); 3668c2ecf20Sopenharmony_ci strcpy(card->shortname, "C-Media CMI8328"); 3678c2ecf20Sopenharmony_ci sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d,%d", 3688c2ecf20Sopenharmony_ci card->shortname, cmi->wss->port, irq[ndev], dma1[ndev], 3698c2ecf20Sopenharmony_ci (dma2[ndev] >= 0) ? dma2[ndev] : dma1[ndev]); 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_ci dev_set_drvdata(pdev, card); 3728c2ecf20Sopenharmony_ci err = snd_card_register(card); 3738c2ecf20Sopenharmony_ci if (err < 0) 3748c2ecf20Sopenharmony_ci goto error; 3758c2ecf20Sopenharmony_ci#ifdef SUPPORT_JOYSTICK 3768c2ecf20Sopenharmony_ci if (!gameport[ndev]) 3778c2ecf20Sopenharmony_ci return 0; 3788c2ecf20Sopenharmony_ci /* gameport is hardwired to 0x200 */ 3798c2ecf20Sopenharmony_ci res = request_region(0x200, 8, "CMI8328 gameport"); 3808c2ecf20Sopenharmony_ci if (!res) 3818c2ecf20Sopenharmony_ci snd_printk(KERN_WARNING "unable to allocate gameport I/O port\n"); 3828c2ecf20Sopenharmony_ci else { 3838c2ecf20Sopenharmony_ci struct gameport *gp = cmi->gameport = gameport_allocate_port(); 3848c2ecf20Sopenharmony_ci if (!cmi->gameport) 3858c2ecf20Sopenharmony_ci release_and_free_resource(res); 3868c2ecf20Sopenharmony_ci else { 3878c2ecf20Sopenharmony_ci gameport_set_name(gp, "CMI8328 Gameport"); 3888c2ecf20Sopenharmony_ci gameport_set_phys(gp, "%s/gameport0", dev_name(pdev)); 3898c2ecf20Sopenharmony_ci gameport_set_dev_parent(gp, pdev); 3908c2ecf20Sopenharmony_ci gp->io = 0x200; 3918c2ecf20Sopenharmony_ci gameport_set_port_data(gp, res); 3928c2ecf20Sopenharmony_ci /* Enable gameport */ 3938c2ecf20Sopenharmony_ci snd_cmi8328_cfg_write(port, CFG1, 3948c2ecf20Sopenharmony_ci CFG1_SB_DISABLE | CFG1_GAMEPORT); 3958c2ecf20Sopenharmony_ci gameport_register_port(gp); 3968c2ecf20Sopenharmony_ci } 3978c2ecf20Sopenharmony_ci } 3988c2ecf20Sopenharmony_ci#endif 3998c2ecf20Sopenharmony_ci return 0; 4008c2ecf20Sopenharmony_cierror: 4018c2ecf20Sopenharmony_ci snd_card_free(card); 4028c2ecf20Sopenharmony_ci 4038c2ecf20Sopenharmony_ci return err; 4048c2ecf20Sopenharmony_ci} 4058c2ecf20Sopenharmony_ci 4068c2ecf20Sopenharmony_cistatic int snd_cmi8328_remove(struct device *pdev, unsigned int dev) 4078c2ecf20Sopenharmony_ci{ 4088c2ecf20Sopenharmony_ci struct snd_card *card = dev_get_drvdata(pdev); 4098c2ecf20Sopenharmony_ci struct snd_cmi8328 *cmi = card->private_data; 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_ci#ifdef SUPPORT_JOYSTICK 4128c2ecf20Sopenharmony_ci if (cmi->gameport) { 4138c2ecf20Sopenharmony_ci struct resource *res = gameport_get_port_data(cmi->gameport); 4148c2ecf20Sopenharmony_ci gameport_unregister_port(cmi->gameport); 4158c2ecf20Sopenharmony_ci release_and_free_resource(res); 4168c2ecf20Sopenharmony_ci } 4178c2ecf20Sopenharmony_ci#endif 4188c2ecf20Sopenharmony_ci /* disable everything */ 4198c2ecf20Sopenharmony_ci snd_cmi8328_cfg_write(cmi->port, CFG1, CFG1_SB_DISABLE); 4208c2ecf20Sopenharmony_ci snd_cmi8328_cfg_write(cmi->port, CFG2, 0); 4218c2ecf20Sopenharmony_ci snd_cmi8328_cfg_write(cmi->port, CFG3, 0); 4228c2ecf20Sopenharmony_ci snd_card_free(card); 4238c2ecf20Sopenharmony_ci return 0; 4248c2ecf20Sopenharmony_ci} 4258c2ecf20Sopenharmony_ci 4268c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 4278c2ecf20Sopenharmony_cistatic int snd_cmi8328_suspend(struct device *pdev, unsigned int n, 4288c2ecf20Sopenharmony_ci pm_message_t state) 4298c2ecf20Sopenharmony_ci{ 4308c2ecf20Sopenharmony_ci struct snd_card *card = dev_get_drvdata(pdev); 4318c2ecf20Sopenharmony_ci struct snd_cmi8328 *cmi; 4328c2ecf20Sopenharmony_ci 4338c2ecf20Sopenharmony_ci if (!card) /* ignore absent devices */ 4348c2ecf20Sopenharmony_ci return 0; 4358c2ecf20Sopenharmony_ci cmi = card->private_data; 4368c2ecf20Sopenharmony_ci snd_cmi8328_cfg_save(cmi->port, cmi->cfg); 4378c2ecf20Sopenharmony_ci snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); 4388c2ecf20Sopenharmony_ci cmi->wss->suspend(cmi->wss); 4398c2ecf20Sopenharmony_ci 4408c2ecf20Sopenharmony_ci return 0; 4418c2ecf20Sopenharmony_ci} 4428c2ecf20Sopenharmony_ci 4438c2ecf20Sopenharmony_cistatic int snd_cmi8328_resume(struct device *pdev, unsigned int n) 4448c2ecf20Sopenharmony_ci{ 4458c2ecf20Sopenharmony_ci struct snd_card *card = dev_get_drvdata(pdev); 4468c2ecf20Sopenharmony_ci struct snd_cmi8328 *cmi; 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci if (!card) /* ignore absent devices */ 4498c2ecf20Sopenharmony_ci return 0; 4508c2ecf20Sopenharmony_ci cmi = card->private_data; 4518c2ecf20Sopenharmony_ci snd_cmi8328_cfg_restore(cmi->port, cmi->cfg); 4528c2ecf20Sopenharmony_ci outb(cmi->wss_cfg, cmi->port); 4538c2ecf20Sopenharmony_ci cmi->wss->resume(cmi->wss); 4548c2ecf20Sopenharmony_ci snd_power_change_state(card, SNDRV_CTL_POWER_D0); 4558c2ecf20Sopenharmony_ci 4568c2ecf20Sopenharmony_ci return 0; 4578c2ecf20Sopenharmony_ci} 4588c2ecf20Sopenharmony_ci#endif 4598c2ecf20Sopenharmony_ci 4608c2ecf20Sopenharmony_cistatic struct isa_driver snd_cmi8328_driver = { 4618c2ecf20Sopenharmony_ci .probe = snd_cmi8328_probe, 4628c2ecf20Sopenharmony_ci .remove = snd_cmi8328_remove, 4638c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 4648c2ecf20Sopenharmony_ci .suspend = snd_cmi8328_suspend, 4658c2ecf20Sopenharmony_ci .resume = snd_cmi8328_resume, 4668c2ecf20Sopenharmony_ci#endif 4678c2ecf20Sopenharmony_ci .driver = { 4688c2ecf20Sopenharmony_ci .name = "cmi8328" 4698c2ecf20Sopenharmony_ci }, 4708c2ecf20Sopenharmony_ci}; 4718c2ecf20Sopenharmony_ci 4728c2ecf20Sopenharmony_cimodule_isa_driver(snd_cmi8328_driver, CMI8328_MAX); 473