162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Driver for C-Media CMI8328-based soundcards, such as AudioExcel AV500 462306a36Sopenharmony_ci * Copyright (c) 2012 Ondrej Zary 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * AudioExcel AV500 card consists of: 762306a36Sopenharmony_ci * - CMI8328 - main chip (SB Pro emulation, gameport, OPL3, MPU401, CD-ROM) 862306a36Sopenharmony_ci * - CS4231A - WSS codec 962306a36Sopenharmony_ci * - Dream SAM9233+GMS950400+RAM+ROM: Wavetable MIDI, connected to MPU401 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/init.h> 1362306a36Sopenharmony_ci#include <linux/isa.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/gameport.h> 1662306a36Sopenharmony_ci#include <asm/dma.h> 1762306a36Sopenharmony_ci#include <sound/core.h> 1862306a36Sopenharmony_ci#include <sound/wss.h> 1962306a36Sopenharmony_ci#include <sound/opl3.h> 2062306a36Sopenharmony_ci#include <sound/mpu401.h> 2162306a36Sopenharmony_ci#define SNDRV_LEGACY_FIND_FREE_IOPORT 2262306a36Sopenharmony_ci#define SNDRV_LEGACY_FIND_FREE_IRQ 2362306a36Sopenharmony_ci#define SNDRV_LEGACY_FIND_FREE_DMA 2462306a36Sopenharmony_ci#include <sound/initval.h> 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ciMODULE_AUTHOR("Ondrej Zary <linux@rainbow-software.org>"); 2762306a36Sopenharmony_ciMODULE_DESCRIPTION("C-Media CMI8328"); 2862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_GAMEPORT) 3162306a36Sopenharmony_ci#define SUPPORT_JOYSTICK 1 3262306a36Sopenharmony_ci#endif 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci/* I/O port is configured by jumpers on the card to one of these */ 3562306a36Sopenharmony_cistatic const int cmi8328_ports[] = { 0x530, 0xe80, 0xf40, 0x604 }; 3662306a36Sopenharmony_ci#define CMI8328_MAX ARRAY_SIZE(cmi8328_ports) 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic int index[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = -1}; 3962306a36Sopenharmony_cistatic char *id[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = NULL}; 4062306a36Sopenharmony_cistatic long port[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_PORT}; 4162306a36Sopenharmony_cistatic int irq[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_IRQ}; 4262306a36Sopenharmony_cistatic int dma1[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_DMA}; 4362306a36Sopenharmony_cistatic int dma2[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_DMA}; 4462306a36Sopenharmony_cistatic long mpuport[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_PORT}; 4562306a36Sopenharmony_cistatic int mpuirq[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_IRQ}; 4662306a36Sopenharmony_ci#ifdef SUPPORT_JOYSTICK 4762306a36Sopenharmony_cistatic bool gameport[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = true}; 4862306a36Sopenharmony_ci#endif 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cimodule_param_array(index, int, NULL, 0444); 5162306a36Sopenharmony_ciMODULE_PARM_DESC(index, "Index value for CMI8328 soundcard."); 5262306a36Sopenharmony_cimodule_param_array(id, charp, NULL, 0444); 5362306a36Sopenharmony_ciMODULE_PARM_DESC(id, "ID string for CMI8328 soundcard."); 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cimodule_param_hw_array(port, long, ioport, NULL, 0444); 5662306a36Sopenharmony_ciMODULE_PARM_DESC(port, "Port # for CMI8328 driver."); 5762306a36Sopenharmony_cimodule_param_hw_array(irq, int, irq, NULL, 0444); 5862306a36Sopenharmony_ciMODULE_PARM_DESC(irq, "IRQ # for CMI8328 driver."); 5962306a36Sopenharmony_cimodule_param_hw_array(dma1, int, dma, NULL, 0444); 6062306a36Sopenharmony_ciMODULE_PARM_DESC(dma1, "DMA1 for CMI8328 driver."); 6162306a36Sopenharmony_cimodule_param_hw_array(dma2, int, dma, NULL, 0444); 6262306a36Sopenharmony_ciMODULE_PARM_DESC(dma2, "DMA2 for CMI8328 driver."); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_cimodule_param_hw_array(mpuport, long, ioport, NULL, 0444); 6562306a36Sopenharmony_ciMODULE_PARM_DESC(mpuport, "MPU-401 port # for CMI8328 driver."); 6662306a36Sopenharmony_cimodule_param_hw_array(mpuirq, int, irq, NULL, 0444); 6762306a36Sopenharmony_ciMODULE_PARM_DESC(mpuirq, "IRQ # for CMI8328 MPU-401 port."); 6862306a36Sopenharmony_ci#ifdef SUPPORT_JOYSTICK 6962306a36Sopenharmony_cimodule_param_array(gameport, bool, NULL, 0444); 7062306a36Sopenharmony_ciMODULE_PARM_DESC(gameport, "Enable gameport."); 7162306a36Sopenharmony_ci#endif 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistruct snd_cmi8328 { 7462306a36Sopenharmony_ci u16 port; 7562306a36Sopenharmony_ci u8 cfg[3]; 7662306a36Sopenharmony_ci u8 wss_cfg; 7762306a36Sopenharmony_ci struct snd_card *card; 7862306a36Sopenharmony_ci struct snd_wss *wss; 7962306a36Sopenharmony_ci#ifdef SUPPORT_JOYSTICK 8062306a36Sopenharmony_ci struct gameport *gameport; 8162306a36Sopenharmony_ci#endif 8262306a36Sopenharmony_ci}; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci/* CMI8328 configuration registers */ 8562306a36Sopenharmony_ci#define CFG1 0x61 8662306a36Sopenharmony_ci#define CFG1_SB_DISABLE (1 << 0) 8762306a36Sopenharmony_ci#define CFG1_GAMEPORT (1 << 1) 8862306a36Sopenharmony_ci/* 8962306a36Sopenharmony_ci * bit 0: SB: 0=enabled, 1=disabled 9062306a36Sopenharmony_ci * bit 1: gameport: 0=disabled, 1=enabled 9162306a36Sopenharmony_ci * bits 2-4: SB IRQ: 001=3, 010=5, 011=7, 100=9, 101=10, 110=11 9262306a36Sopenharmony_ci * bits 5-6: SB DMA: 00=disabled (when SB disabled), 01=DMA0, 10=DMA1, 11=DMA3 9362306a36Sopenharmony_ci * bit 7: SB port: 0=0x220, 1=0x240 9462306a36Sopenharmony_ci */ 9562306a36Sopenharmony_ci#define CFG2 0x62 9662306a36Sopenharmony_ci#define CFG2_MPU_ENABLE (1 << 2) 9762306a36Sopenharmony_ci/* 9862306a36Sopenharmony_ci * bits 0-1: CD-ROM mode: 00=disabled, 01=Panasonic, 10=Sony/Mitsumi/Wearnes, 9962306a36Sopenharmony_ci 11=IDE 10062306a36Sopenharmony_ci * bit 2: MPU401: 0=disabled, 1=enabled 10162306a36Sopenharmony_ci * bits 3-4: MPU401 IRQ: 00=3, 01=5, 10=7, 11=9, 10262306a36Sopenharmony_ci * bits 5-7: MPU401 port: 000=0x300, 001=0x310, 010=0x320, 011=0x330, 100=0x332, 10362306a36Sopenharmony_ci 101=0x334, 110=0x336 10462306a36Sopenharmony_ci */ 10562306a36Sopenharmony_ci#define CFG3 0x63 10662306a36Sopenharmony_ci/* 10762306a36Sopenharmony_ci * bits 0-2: CD-ROM IRQ: 000=disabled, 001=3, 010=5, 011=7, 100=9, 101=10, 10862306a36Sopenharmony_ci 110=11 10962306a36Sopenharmony_ci * bits 3-4: CD-ROM DMA: 00=disabled, 01=DMA0, 10=DMA1, 11=DMA3 11062306a36Sopenharmony_ci * bits 5-7: CD-ROM port: 000=0x300, 001=0x310, 010=0x320, 011=0x330, 100=0x340, 11162306a36Sopenharmony_ci 101=0x350, 110=0x360, 111=0x370 11262306a36Sopenharmony_ci */ 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistatic u8 snd_cmi8328_cfg_read(u16 port, u8 reg) 11562306a36Sopenharmony_ci{ 11662306a36Sopenharmony_ci outb(0x43, port + 3); 11762306a36Sopenharmony_ci outb(0x21, port + 3); 11862306a36Sopenharmony_ci outb(reg, port + 3); 11962306a36Sopenharmony_ci return inb(port); 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistatic void snd_cmi8328_cfg_write(u16 port, u8 reg, u8 val) 12362306a36Sopenharmony_ci{ 12462306a36Sopenharmony_ci outb(0x43, port + 3); 12562306a36Sopenharmony_ci outb(0x21, port + 3); 12662306a36Sopenharmony_ci outb(reg, port + 3); 12762306a36Sopenharmony_ci outb(val, port + 3); /* yes, value goes to the same port as index */ 12862306a36Sopenharmony_ci} 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci#ifdef CONFIG_PM 13162306a36Sopenharmony_cistatic void snd_cmi8328_cfg_save(u16 port, u8 cfg[]) 13262306a36Sopenharmony_ci{ 13362306a36Sopenharmony_ci cfg[0] = snd_cmi8328_cfg_read(port, CFG1); 13462306a36Sopenharmony_ci cfg[1] = snd_cmi8328_cfg_read(port, CFG2); 13562306a36Sopenharmony_ci cfg[2] = snd_cmi8328_cfg_read(port, CFG3); 13662306a36Sopenharmony_ci} 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cistatic void snd_cmi8328_cfg_restore(u16 port, u8 cfg[]) 13962306a36Sopenharmony_ci{ 14062306a36Sopenharmony_ci snd_cmi8328_cfg_write(port, CFG1, cfg[0]); 14162306a36Sopenharmony_ci snd_cmi8328_cfg_write(port, CFG2, cfg[1]); 14262306a36Sopenharmony_ci snd_cmi8328_cfg_write(port, CFG3, cfg[2]); 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci#endif /* CONFIG_PM */ 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cistatic int snd_cmi8328_mixer(struct snd_wss *chip) 14762306a36Sopenharmony_ci{ 14862306a36Sopenharmony_ci struct snd_card *card; 14962306a36Sopenharmony_ci struct snd_ctl_elem_id id1, id2; 15062306a36Sopenharmony_ci int err; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci card = chip->card; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci memset(&id1, 0, sizeof(id1)); 15562306a36Sopenharmony_ci memset(&id2, 0, sizeof(id2)); 15662306a36Sopenharmony_ci id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; 15762306a36Sopenharmony_ci /* rename AUX0 switch to CD */ 15862306a36Sopenharmony_ci strcpy(id1.name, "Aux Playback Switch"); 15962306a36Sopenharmony_ci strcpy(id2.name, "CD Playback Switch"); 16062306a36Sopenharmony_ci err = snd_ctl_rename_id(card, &id1, &id2); 16162306a36Sopenharmony_ci if (err < 0) { 16262306a36Sopenharmony_ci snd_printk(KERN_ERR "error renaming control\n"); 16362306a36Sopenharmony_ci return err; 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci /* rename AUX0 volume to CD */ 16662306a36Sopenharmony_ci strcpy(id1.name, "Aux Playback Volume"); 16762306a36Sopenharmony_ci strcpy(id2.name, "CD Playback Volume"); 16862306a36Sopenharmony_ci err = snd_ctl_rename_id(card, &id1, &id2); 16962306a36Sopenharmony_ci if (err < 0) { 17062306a36Sopenharmony_ci snd_printk(KERN_ERR "error renaming control\n"); 17162306a36Sopenharmony_ci return err; 17262306a36Sopenharmony_ci } 17362306a36Sopenharmony_ci /* rename AUX1 switch to Synth */ 17462306a36Sopenharmony_ci strcpy(id1.name, "Aux Playback Switch"); 17562306a36Sopenharmony_ci id1.index = 1; 17662306a36Sopenharmony_ci strcpy(id2.name, "Synth Playback Switch"); 17762306a36Sopenharmony_ci err = snd_ctl_rename_id(card, &id1, &id2); 17862306a36Sopenharmony_ci if (err < 0) { 17962306a36Sopenharmony_ci snd_printk(KERN_ERR "error renaming control\n"); 18062306a36Sopenharmony_ci return err; 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci /* rename AUX1 volume to Synth */ 18362306a36Sopenharmony_ci strcpy(id1.name, "Aux Playback Volume"); 18462306a36Sopenharmony_ci id1.index = 1; 18562306a36Sopenharmony_ci strcpy(id2.name, "Synth Playback Volume"); 18662306a36Sopenharmony_ci err = snd_ctl_rename_id(card, &id1, &id2); 18762306a36Sopenharmony_ci if (err < 0) { 18862306a36Sopenharmony_ci snd_printk(KERN_ERR "error renaming control\n"); 18962306a36Sopenharmony_ci return err; 19062306a36Sopenharmony_ci } 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci return 0; 19362306a36Sopenharmony_ci} 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci/* find index of an item in "-1"-ended array */ 19662306a36Sopenharmony_cistatic int array_find(const int array[], int item) 19762306a36Sopenharmony_ci{ 19862306a36Sopenharmony_ci int i; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci for (i = 0; array[i] != -1; i++) 20162306a36Sopenharmony_ci if (array[i] == item) 20262306a36Sopenharmony_ci return i; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci return -1; 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci/* the same for long */ 20762306a36Sopenharmony_cistatic int array_find_l(const long array[], long item) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci int i; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci for (i = 0; array[i] != -1; i++) 21262306a36Sopenharmony_ci if (array[i] == item) 21362306a36Sopenharmony_ci return i; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci return -1; 21662306a36Sopenharmony_ci} 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_cistatic int snd_cmi8328_probe(struct device *pdev, unsigned int ndev) 21962306a36Sopenharmony_ci{ 22062306a36Sopenharmony_ci struct snd_card *card; 22162306a36Sopenharmony_ci struct snd_opl3 *opl3; 22262306a36Sopenharmony_ci struct snd_cmi8328 *cmi; 22362306a36Sopenharmony_ci#ifdef SUPPORT_JOYSTICK 22462306a36Sopenharmony_ci struct resource *res; 22562306a36Sopenharmony_ci#endif 22662306a36Sopenharmony_ci int err, pos; 22762306a36Sopenharmony_ci static const long mpu_ports[] = { 0x330, 0x300, 0x310, 0x320, 0x332, 0x334, 22862306a36Sopenharmony_ci 0x336, -1 }; 22962306a36Sopenharmony_ci static const u8 mpu_port_bits[] = { 3, 0, 1, 2, 4, 5, 6 }; 23062306a36Sopenharmony_ci static const int mpu_irqs[] = { 9, 7, 5, 3, -1 }; 23162306a36Sopenharmony_ci static const u8 mpu_irq_bits[] = { 3, 2, 1, 0 }; 23262306a36Sopenharmony_ci static const int irqs[] = { 9, 10, 11, 7, -1 }; 23362306a36Sopenharmony_ci static const u8 irq_bits[] = { 2, 3, 4, 1 }; 23462306a36Sopenharmony_ci static const int dma1s[] = { 3, 1, 0, -1 }; 23562306a36Sopenharmony_ci static const u8 dma_bits[] = { 3, 2, 1 }; 23662306a36Sopenharmony_ci static const int dma2s[][2] = { {1, -1}, {0, -1}, {-1, -1}, {0, -1} }; 23762306a36Sopenharmony_ci u16 port = cmi8328_ports[ndev]; 23862306a36Sopenharmony_ci u8 val; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci /* 0xff is invalid configuration (but settable - hope it isn't set) */ 24162306a36Sopenharmony_ci if (snd_cmi8328_cfg_read(port, CFG1) == 0xff) 24262306a36Sopenharmony_ci return -ENODEV; 24362306a36Sopenharmony_ci /* the SB disable bit must NEVER EVER be cleared or the WSS dies */ 24462306a36Sopenharmony_ci snd_cmi8328_cfg_write(port, CFG1, CFG1_SB_DISABLE); 24562306a36Sopenharmony_ci if (snd_cmi8328_cfg_read(port, CFG1) != CFG1_SB_DISABLE) 24662306a36Sopenharmony_ci return -ENODEV; 24762306a36Sopenharmony_ci /* disable everything first */ 24862306a36Sopenharmony_ci snd_cmi8328_cfg_write(port, CFG2, 0); /* disable CDROM and MPU401 */ 24962306a36Sopenharmony_ci snd_cmi8328_cfg_write(port, CFG3, 0); /* disable CDROM IRQ and DMA */ 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci if (irq[ndev] == SNDRV_AUTO_IRQ) { 25262306a36Sopenharmony_ci irq[ndev] = snd_legacy_find_free_irq(irqs); 25362306a36Sopenharmony_ci if (irq[ndev] < 0) { 25462306a36Sopenharmony_ci snd_printk(KERN_ERR "unable to find a free IRQ\n"); 25562306a36Sopenharmony_ci return -EBUSY; 25662306a36Sopenharmony_ci } 25762306a36Sopenharmony_ci } 25862306a36Sopenharmony_ci if (dma1[ndev] == SNDRV_AUTO_DMA) { 25962306a36Sopenharmony_ci dma1[ndev] = snd_legacy_find_free_dma(dma1s); 26062306a36Sopenharmony_ci if (dma1[ndev] < 0) { 26162306a36Sopenharmony_ci snd_printk(KERN_ERR "unable to find a free DMA1\n"); 26262306a36Sopenharmony_ci return -EBUSY; 26362306a36Sopenharmony_ci } 26462306a36Sopenharmony_ci } 26562306a36Sopenharmony_ci if (dma2[ndev] == SNDRV_AUTO_DMA) { 26662306a36Sopenharmony_ci dma2[ndev] = snd_legacy_find_free_dma(dma2s[dma1[ndev] % 4]); 26762306a36Sopenharmony_ci if (dma2[ndev] < 0) { 26862306a36Sopenharmony_ci snd_printk(KERN_WARNING "unable to find a free DMA2, full-duplex will not work\n"); 26962306a36Sopenharmony_ci dma2[ndev] = -1; 27062306a36Sopenharmony_ci } 27162306a36Sopenharmony_ci } 27262306a36Sopenharmony_ci /* configure WSS IRQ... */ 27362306a36Sopenharmony_ci pos = array_find(irqs, irq[ndev]); 27462306a36Sopenharmony_ci if (pos < 0) { 27562306a36Sopenharmony_ci snd_printk(KERN_ERR "invalid IRQ %d\n", irq[ndev]); 27662306a36Sopenharmony_ci return -EINVAL; 27762306a36Sopenharmony_ci } 27862306a36Sopenharmony_ci val = irq_bits[pos] << 3; 27962306a36Sopenharmony_ci /* ...and DMA... */ 28062306a36Sopenharmony_ci pos = array_find(dma1s, dma1[ndev]); 28162306a36Sopenharmony_ci if (pos < 0) { 28262306a36Sopenharmony_ci snd_printk(KERN_ERR "invalid DMA1 %d\n", dma1[ndev]); 28362306a36Sopenharmony_ci return -EINVAL; 28462306a36Sopenharmony_ci } 28562306a36Sopenharmony_ci val |= dma_bits[pos]; 28662306a36Sopenharmony_ci /* ...and DMA2 */ 28762306a36Sopenharmony_ci if (dma2[ndev] >= 0 && dma1[ndev] != dma2[ndev]) { 28862306a36Sopenharmony_ci pos = array_find(dma2s[dma1[ndev]], dma2[ndev]); 28962306a36Sopenharmony_ci if (pos < 0) { 29062306a36Sopenharmony_ci snd_printk(KERN_ERR "invalid DMA2 %d\n", dma2[ndev]); 29162306a36Sopenharmony_ci return -EINVAL; 29262306a36Sopenharmony_ci } 29362306a36Sopenharmony_ci val |= 0x04; /* enable separate capture DMA */ 29462306a36Sopenharmony_ci } 29562306a36Sopenharmony_ci outb(val, port); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci err = snd_devm_card_new(pdev, index[ndev], id[ndev], THIS_MODULE, 29862306a36Sopenharmony_ci sizeof(struct snd_cmi8328), &card); 29962306a36Sopenharmony_ci if (err < 0) 30062306a36Sopenharmony_ci return err; 30162306a36Sopenharmony_ci cmi = card->private_data; 30262306a36Sopenharmony_ci cmi->card = card; 30362306a36Sopenharmony_ci cmi->port = port; 30462306a36Sopenharmony_ci cmi->wss_cfg = val; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci err = snd_wss_create(card, port + 4, -1, irq[ndev], dma1[ndev], 30762306a36Sopenharmony_ci dma2[ndev], WSS_HW_DETECT, 0, &cmi->wss); 30862306a36Sopenharmony_ci if (err < 0) 30962306a36Sopenharmony_ci return err; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci err = snd_wss_pcm(cmi->wss, 0); 31262306a36Sopenharmony_ci if (err < 0) 31362306a36Sopenharmony_ci return err; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci err = snd_wss_mixer(cmi->wss); 31662306a36Sopenharmony_ci if (err < 0) 31762306a36Sopenharmony_ci return err; 31862306a36Sopenharmony_ci err = snd_cmi8328_mixer(cmi->wss); 31962306a36Sopenharmony_ci if (err < 0) 32062306a36Sopenharmony_ci return err; 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci if (snd_wss_timer(cmi->wss, 0) < 0) 32362306a36Sopenharmony_ci snd_printk(KERN_WARNING "error initializing WSS timer\n"); 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci if (mpuport[ndev] == SNDRV_AUTO_PORT) { 32662306a36Sopenharmony_ci mpuport[ndev] = snd_legacy_find_free_ioport(mpu_ports, 2); 32762306a36Sopenharmony_ci if (mpuport[ndev] < 0) 32862306a36Sopenharmony_ci snd_printk(KERN_ERR "unable to find a free MPU401 port\n"); 32962306a36Sopenharmony_ci } 33062306a36Sopenharmony_ci if (mpuirq[ndev] == SNDRV_AUTO_IRQ) { 33162306a36Sopenharmony_ci mpuirq[ndev] = snd_legacy_find_free_irq(mpu_irqs); 33262306a36Sopenharmony_ci if (mpuirq[ndev] < 0) 33362306a36Sopenharmony_ci snd_printk(KERN_ERR "unable to find a free MPU401 IRQ\n"); 33462306a36Sopenharmony_ci } 33562306a36Sopenharmony_ci /* enable and configure MPU401 */ 33662306a36Sopenharmony_ci if (mpuport[ndev] > 0 && mpuirq[ndev] > 0) { 33762306a36Sopenharmony_ci val = CFG2_MPU_ENABLE; 33862306a36Sopenharmony_ci pos = array_find_l(mpu_ports, mpuport[ndev]); 33962306a36Sopenharmony_ci if (pos < 0) 34062306a36Sopenharmony_ci snd_printk(KERN_WARNING "invalid MPU401 port 0x%lx\n", 34162306a36Sopenharmony_ci mpuport[ndev]); 34262306a36Sopenharmony_ci else { 34362306a36Sopenharmony_ci val |= mpu_port_bits[pos] << 5; 34462306a36Sopenharmony_ci pos = array_find(mpu_irqs, mpuirq[ndev]); 34562306a36Sopenharmony_ci if (pos < 0) 34662306a36Sopenharmony_ci snd_printk(KERN_WARNING "invalid MPU401 IRQ %d\n", 34762306a36Sopenharmony_ci mpuirq[ndev]); 34862306a36Sopenharmony_ci else { 34962306a36Sopenharmony_ci val |= mpu_irq_bits[pos] << 3; 35062306a36Sopenharmony_ci snd_cmi8328_cfg_write(port, CFG2, val); 35162306a36Sopenharmony_ci if (snd_mpu401_uart_new(card, 0, 35262306a36Sopenharmony_ci MPU401_HW_MPU401, mpuport[ndev], 35362306a36Sopenharmony_ci 0, mpuirq[ndev], NULL) < 0) 35462306a36Sopenharmony_ci snd_printk(KERN_ERR "error initializing MPU401\n"); 35562306a36Sopenharmony_ci } 35662306a36Sopenharmony_ci } 35762306a36Sopenharmony_ci } 35862306a36Sopenharmony_ci /* OPL3 is hardwired to 0x388 and cannot be disabled */ 35962306a36Sopenharmony_ci if (snd_opl3_create(card, 0x388, 0x38a, OPL3_HW_AUTO, 0, &opl3) < 0) 36062306a36Sopenharmony_ci snd_printk(KERN_ERR "error initializing OPL3\n"); 36162306a36Sopenharmony_ci else 36262306a36Sopenharmony_ci if (snd_opl3_hwdep_new(opl3, 0, 1, NULL) < 0) 36362306a36Sopenharmony_ci snd_printk(KERN_WARNING "error initializing OPL3 hwdep\n"); 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci strcpy(card->driver, "CMI8328"); 36662306a36Sopenharmony_ci strcpy(card->shortname, "C-Media CMI8328"); 36762306a36Sopenharmony_ci sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d,%d", 36862306a36Sopenharmony_ci card->shortname, cmi->wss->port, irq[ndev], dma1[ndev], 36962306a36Sopenharmony_ci (dma2[ndev] >= 0) ? dma2[ndev] : dma1[ndev]); 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci dev_set_drvdata(pdev, card); 37262306a36Sopenharmony_ci err = snd_card_register(card); 37362306a36Sopenharmony_ci if (err < 0) 37462306a36Sopenharmony_ci return err; 37562306a36Sopenharmony_ci#ifdef SUPPORT_JOYSTICK 37662306a36Sopenharmony_ci if (!gameport[ndev]) 37762306a36Sopenharmony_ci return 0; 37862306a36Sopenharmony_ci /* gameport is hardwired to 0x200 */ 37962306a36Sopenharmony_ci res = devm_request_region(pdev, 0x200, 8, "CMI8328 gameport"); 38062306a36Sopenharmony_ci if (!res) 38162306a36Sopenharmony_ci snd_printk(KERN_WARNING "unable to allocate gameport I/O port\n"); 38262306a36Sopenharmony_ci else { 38362306a36Sopenharmony_ci struct gameport *gp = cmi->gameport = gameport_allocate_port(); 38462306a36Sopenharmony_ci if (cmi->gameport) { 38562306a36Sopenharmony_ci gameport_set_name(gp, "CMI8328 Gameport"); 38662306a36Sopenharmony_ci gameport_set_phys(gp, "%s/gameport0", dev_name(pdev)); 38762306a36Sopenharmony_ci gameport_set_dev_parent(gp, pdev); 38862306a36Sopenharmony_ci gp->io = 0x200; 38962306a36Sopenharmony_ci /* Enable gameport */ 39062306a36Sopenharmony_ci snd_cmi8328_cfg_write(port, CFG1, 39162306a36Sopenharmony_ci CFG1_SB_DISABLE | CFG1_GAMEPORT); 39262306a36Sopenharmony_ci gameport_register_port(gp); 39362306a36Sopenharmony_ci } 39462306a36Sopenharmony_ci } 39562306a36Sopenharmony_ci#endif 39662306a36Sopenharmony_ci return 0; 39762306a36Sopenharmony_ci} 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_cistatic void snd_cmi8328_remove(struct device *pdev, unsigned int dev) 40062306a36Sopenharmony_ci{ 40162306a36Sopenharmony_ci struct snd_card *card = dev_get_drvdata(pdev); 40262306a36Sopenharmony_ci struct snd_cmi8328 *cmi = card->private_data; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci#ifdef SUPPORT_JOYSTICK 40562306a36Sopenharmony_ci if (cmi->gameport) 40662306a36Sopenharmony_ci gameport_unregister_port(cmi->gameport); 40762306a36Sopenharmony_ci#endif 40862306a36Sopenharmony_ci /* disable everything */ 40962306a36Sopenharmony_ci snd_cmi8328_cfg_write(cmi->port, CFG1, CFG1_SB_DISABLE); 41062306a36Sopenharmony_ci snd_cmi8328_cfg_write(cmi->port, CFG2, 0); 41162306a36Sopenharmony_ci snd_cmi8328_cfg_write(cmi->port, CFG3, 0); 41262306a36Sopenharmony_ci} 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci#ifdef CONFIG_PM 41562306a36Sopenharmony_cistatic int snd_cmi8328_suspend(struct device *pdev, unsigned int n, 41662306a36Sopenharmony_ci pm_message_t state) 41762306a36Sopenharmony_ci{ 41862306a36Sopenharmony_ci struct snd_card *card = dev_get_drvdata(pdev); 41962306a36Sopenharmony_ci struct snd_cmi8328 *cmi; 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci if (!card) /* ignore absent devices */ 42262306a36Sopenharmony_ci return 0; 42362306a36Sopenharmony_ci cmi = card->private_data; 42462306a36Sopenharmony_ci snd_cmi8328_cfg_save(cmi->port, cmi->cfg); 42562306a36Sopenharmony_ci snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); 42662306a36Sopenharmony_ci cmi->wss->suspend(cmi->wss); 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci return 0; 42962306a36Sopenharmony_ci} 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_cistatic int snd_cmi8328_resume(struct device *pdev, unsigned int n) 43262306a36Sopenharmony_ci{ 43362306a36Sopenharmony_ci struct snd_card *card = dev_get_drvdata(pdev); 43462306a36Sopenharmony_ci struct snd_cmi8328 *cmi; 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci if (!card) /* ignore absent devices */ 43762306a36Sopenharmony_ci return 0; 43862306a36Sopenharmony_ci cmi = card->private_data; 43962306a36Sopenharmony_ci snd_cmi8328_cfg_restore(cmi->port, cmi->cfg); 44062306a36Sopenharmony_ci outb(cmi->wss_cfg, cmi->port); 44162306a36Sopenharmony_ci cmi->wss->resume(cmi->wss); 44262306a36Sopenharmony_ci snd_power_change_state(card, SNDRV_CTL_POWER_D0); 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci return 0; 44562306a36Sopenharmony_ci} 44662306a36Sopenharmony_ci#endif 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_cistatic struct isa_driver snd_cmi8328_driver = { 44962306a36Sopenharmony_ci .probe = snd_cmi8328_probe, 45062306a36Sopenharmony_ci .remove = snd_cmi8328_remove, 45162306a36Sopenharmony_ci#ifdef CONFIG_PM 45262306a36Sopenharmony_ci .suspend = snd_cmi8328_suspend, 45362306a36Sopenharmony_ci .resume = snd_cmi8328_resume, 45462306a36Sopenharmony_ci#endif 45562306a36Sopenharmony_ci .driver = { 45662306a36Sopenharmony_ci .name = "cmi8328" 45762306a36Sopenharmony_ci }, 45862306a36Sopenharmony_ci}; 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_cimodule_isa_driver(snd_cmi8328_driver, CMI8328_MAX); 461