162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Driver for Gallant SC-6000 soundcard. This card is also known as 462306a36Sopenharmony_ci * Audio Excel DSP 16 or Zoltrix AV302. 562306a36Sopenharmony_ci * These cards use CompuMedia ASC-9308 chip + AD1848 codec. 662306a36Sopenharmony_ci * SC-6600 and SC-7000 cards are also supported. They are based on 762306a36Sopenharmony_ci * CompuMedia ASC-9408 chip and CS4231 codec. 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Copyright (C) 2007 Krzysztof Helt <krzysztof.h1@wp.pl> 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * I don't have documentation for this card. I used the driver 1262306a36Sopenharmony_ci * for OSS/Free included in the kernel source as reference. 1362306a36Sopenharmony_ci */ 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/delay.h> 1762306a36Sopenharmony_ci#include <linux/isa.h> 1862306a36Sopenharmony_ci#include <linux/io.h> 1962306a36Sopenharmony_ci#include <asm/dma.h> 2062306a36Sopenharmony_ci#include <sound/core.h> 2162306a36Sopenharmony_ci#include <sound/wss.h> 2262306a36Sopenharmony_ci#include <sound/opl3.h> 2362306a36Sopenharmony_ci#include <sound/mpu401.h> 2462306a36Sopenharmony_ci#include <sound/control.h> 2562306a36Sopenharmony_ci#define SNDRV_LEGACY_FIND_FREE_IRQ 2662306a36Sopenharmony_ci#define SNDRV_LEGACY_FIND_FREE_DMA 2762306a36Sopenharmony_ci#include <sound/initval.h> 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ciMODULE_AUTHOR("Krzysztof Helt"); 3062306a36Sopenharmony_ciMODULE_DESCRIPTION("Gallant SC-6000"); 3162306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ 3462306a36Sopenharmony_cistatic char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ 3562306a36Sopenharmony_cistatic bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ 3662306a36Sopenharmony_cistatic long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220, 0x240 */ 3762306a36Sopenharmony_cistatic int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5, 7, 9, 10, 11 */ 3862306a36Sopenharmony_cistatic long mss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x530, 0xe80 */ 3962306a36Sopenharmony_cistatic long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; 4062306a36Sopenharmony_ci /* 0x300, 0x310, 0x320, 0x330 */ 4162306a36Sopenharmony_cistatic int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5, 7, 9, 10, 0 */ 4262306a36Sopenharmony_cistatic int dma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0, 1, 3 */ 4362306a36Sopenharmony_cistatic bool joystick[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = false }; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cimodule_param_array(index, int, NULL, 0444); 4662306a36Sopenharmony_ciMODULE_PARM_DESC(index, "Index value for sc-6000 based soundcard."); 4762306a36Sopenharmony_cimodule_param_array(id, charp, NULL, 0444); 4862306a36Sopenharmony_ciMODULE_PARM_DESC(id, "ID string for sc-6000 based soundcard."); 4962306a36Sopenharmony_cimodule_param_array(enable, bool, NULL, 0444); 5062306a36Sopenharmony_ciMODULE_PARM_DESC(enable, "Enable sc-6000 based soundcard."); 5162306a36Sopenharmony_cimodule_param_hw_array(port, long, ioport, NULL, 0444); 5262306a36Sopenharmony_ciMODULE_PARM_DESC(port, "Port # for sc-6000 driver."); 5362306a36Sopenharmony_cimodule_param_hw_array(mss_port, long, ioport, NULL, 0444); 5462306a36Sopenharmony_ciMODULE_PARM_DESC(mss_port, "MSS Port # for sc-6000 driver."); 5562306a36Sopenharmony_cimodule_param_hw_array(mpu_port, long, ioport, NULL, 0444); 5662306a36Sopenharmony_ciMODULE_PARM_DESC(mpu_port, "MPU-401 port # for sc-6000 driver."); 5762306a36Sopenharmony_cimodule_param_hw_array(irq, int, irq, NULL, 0444); 5862306a36Sopenharmony_ciMODULE_PARM_DESC(irq, "IRQ # for sc-6000 driver."); 5962306a36Sopenharmony_cimodule_param_hw_array(mpu_irq, int, irq, NULL, 0444); 6062306a36Sopenharmony_ciMODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for sc-6000 driver."); 6162306a36Sopenharmony_cimodule_param_hw_array(dma, int, dma, NULL, 0444); 6262306a36Sopenharmony_ciMODULE_PARM_DESC(dma, "DMA # for sc-6000 driver."); 6362306a36Sopenharmony_cimodule_param_array(joystick, bool, NULL, 0444); 6462306a36Sopenharmony_ciMODULE_PARM_DESC(joystick, "Enable gameport."); 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci/* 6762306a36Sopenharmony_ci * Commands of SC6000's DSP (SBPRO+special). 6862306a36Sopenharmony_ci * Some of them are COMMAND_xx, in the future they may change. 6962306a36Sopenharmony_ci */ 7062306a36Sopenharmony_ci#define WRITE_MDIRQ_CFG 0x50 /* Set M&I&DRQ mask (the real config) */ 7162306a36Sopenharmony_ci#define COMMAND_52 0x52 /* */ 7262306a36Sopenharmony_ci#define READ_HARD_CFG 0x58 /* Read Hardware Config (I/O base etc) */ 7362306a36Sopenharmony_ci#define COMMAND_5C 0x5c /* */ 7462306a36Sopenharmony_ci#define COMMAND_60 0x60 /* */ 7562306a36Sopenharmony_ci#define COMMAND_66 0x66 /* */ 7662306a36Sopenharmony_ci#define COMMAND_6C 0x6c /* */ 7762306a36Sopenharmony_ci#define COMMAND_6E 0x6e /* */ 7862306a36Sopenharmony_ci#define COMMAND_88 0x88 /* Unknown command */ 7962306a36Sopenharmony_ci#define DSP_INIT_MSS 0x8c /* Enable Microsoft Sound System mode */ 8062306a36Sopenharmony_ci#define COMMAND_C5 0xc5 /* */ 8162306a36Sopenharmony_ci#define GET_DSP_VERSION 0xe1 /* Get DSP Version */ 8262306a36Sopenharmony_ci#define GET_DSP_COPYRIGHT 0xe3 /* Get DSP Copyright */ 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci/* 8562306a36Sopenharmony_ci * Offsets of SC6000 DSP I/O ports. The offset is added to base I/O port 8662306a36Sopenharmony_ci * to have the actual I/O port. 8762306a36Sopenharmony_ci * Register permissions are: 8862306a36Sopenharmony_ci * (wo) == Write Only 8962306a36Sopenharmony_ci * (ro) == Read Only 9062306a36Sopenharmony_ci * (w-) == Write 9162306a36Sopenharmony_ci * (r-) == Read 9262306a36Sopenharmony_ci */ 9362306a36Sopenharmony_ci#define DSP_RESET 0x06 /* offset of DSP RESET (wo) */ 9462306a36Sopenharmony_ci#define DSP_READ 0x0a /* offset of DSP READ (ro) */ 9562306a36Sopenharmony_ci#define DSP_WRITE 0x0c /* offset of DSP WRITE (w-) */ 9662306a36Sopenharmony_ci#define DSP_COMMAND 0x0c /* offset of DSP COMMAND (w-) */ 9762306a36Sopenharmony_ci#define DSP_STATUS 0x0c /* offset of DSP STATUS (r-) */ 9862306a36Sopenharmony_ci#define DSP_DATAVAIL 0x0e /* offset of DSP DATA AVAILABLE (ro) */ 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci#define PFX "sc6000: " 10162306a36Sopenharmony_ci#define DRV_NAME "SC-6000" 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci/* hardware dependent functions */ 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci/* 10662306a36Sopenharmony_ci * sc6000_irq_to_softcfg - Decode irq number into cfg code. 10762306a36Sopenharmony_ci */ 10862306a36Sopenharmony_cistatic unsigned char sc6000_irq_to_softcfg(int irq) 10962306a36Sopenharmony_ci{ 11062306a36Sopenharmony_ci unsigned char val = 0; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci switch (irq) { 11362306a36Sopenharmony_ci case 5: 11462306a36Sopenharmony_ci val = 0x28; 11562306a36Sopenharmony_ci break; 11662306a36Sopenharmony_ci case 7: 11762306a36Sopenharmony_ci val = 0x8; 11862306a36Sopenharmony_ci break; 11962306a36Sopenharmony_ci case 9: 12062306a36Sopenharmony_ci val = 0x10; 12162306a36Sopenharmony_ci break; 12262306a36Sopenharmony_ci case 10: 12362306a36Sopenharmony_ci val = 0x18; 12462306a36Sopenharmony_ci break; 12562306a36Sopenharmony_ci case 11: 12662306a36Sopenharmony_ci val = 0x20; 12762306a36Sopenharmony_ci break; 12862306a36Sopenharmony_ci default: 12962306a36Sopenharmony_ci break; 13062306a36Sopenharmony_ci } 13162306a36Sopenharmony_ci return val; 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci/* 13562306a36Sopenharmony_ci * sc6000_dma_to_softcfg - Decode dma number into cfg code. 13662306a36Sopenharmony_ci */ 13762306a36Sopenharmony_cistatic unsigned char sc6000_dma_to_softcfg(int dma) 13862306a36Sopenharmony_ci{ 13962306a36Sopenharmony_ci unsigned char val = 0; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci switch (dma) { 14262306a36Sopenharmony_ci case 0: 14362306a36Sopenharmony_ci val = 1; 14462306a36Sopenharmony_ci break; 14562306a36Sopenharmony_ci case 1: 14662306a36Sopenharmony_ci val = 2; 14762306a36Sopenharmony_ci break; 14862306a36Sopenharmony_ci case 3: 14962306a36Sopenharmony_ci val = 3; 15062306a36Sopenharmony_ci break; 15162306a36Sopenharmony_ci default: 15262306a36Sopenharmony_ci break; 15362306a36Sopenharmony_ci } 15462306a36Sopenharmony_ci return val; 15562306a36Sopenharmony_ci} 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci/* 15862306a36Sopenharmony_ci * sc6000_mpu_irq_to_softcfg - Decode MPU-401 irq number into cfg code. 15962306a36Sopenharmony_ci */ 16062306a36Sopenharmony_cistatic unsigned char sc6000_mpu_irq_to_softcfg(int mpu_irq) 16162306a36Sopenharmony_ci{ 16262306a36Sopenharmony_ci unsigned char val = 0; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci switch (mpu_irq) { 16562306a36Sopenharmony_ci case 5: 16662306a36Sopenharmony_ci val = 4; 16762306a36Sopenharmony_ci break; 16862306a36Sopenharmony_ci case 7: 16962306a36Sopenharmony_ci val = 0x44; 17062306a36Sopenharmony_ci break; 17162306a36Sopenharmony_ci case 9: 17262306a36Sopenharmony_ci val = 0x84; 17362306a36Sopenharmony_ci break; 17462306a36Sopenharmony_ci case 10: 17562306a36Sopenharmony_ci val = 0xc4; 17662306a36Sopenharmony_ci break; 17762306a36Sopenharmony_ci default: 17862306a36Sopenharmony_ci break; 17962306a36Sopenharmony_ci } 18062306a36Sopenharmony_ci return val; 18162306a36Sopenharmony_ci} 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_cistatic int sc6000_wait_data(char __iomem *vport) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci int loop = 1000; 18662306a36Sopenharmony_ci unsigned char val = 0; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci do { 18962306a36Sopenharmony_ci val = ioread8(vport + DSP_DATAVAIL); 19062306a36Sopenharmony_ci if (val & 0x80) 19162306a36Sopenharmony_ci return 0; 19262306a36Sopenharmony_ci cpu_relax(); 19362306a36Sopenharmony_ci } while (loop--); 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci return -EAGAIN; 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_cistatic int sc6000_read(char __iomem *vport) 19962306a36Sopenharmony_ci{ 20062306a36Sopenharmony_ci if (sc6000_wait_data(vport)) 20162306a36Sopenharmony_ci return -EBUSY; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci return ioread8(vport + DSP_READ); 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cistatic int sc6000_write(char __iomem *vport, int cmd) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci unsigned char val; 21062306a36Sopenharmony_ci int loop = 500000; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci do { 21362306a36Sopenharmony_ci val = ioread8(vport + DSP_STATUS); 21462306a36Sopenharmony_ci /* 21562306a36Sopenharmony_ci * DSP ready to receive data if bit 7 of val == 0 21662306a36Sopenharmony_ci */ 21762306a36Sopenharmony_ci if (!(val & 0x80)) { 21862306a36Sopenharmony_ci iowrite8(cmd, vport + DSP_COMMAND); 21962306a36Sopenharmony_ci return 0; 22062306a36Sopenharmony_ci } 22162306a36Sopenharmony_ci cpu_relax(); 22262306a36Sopenharmony_ci } while (loop--); 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci snd_printk(KERN_ERR "DSP Command (0x%x) timeout.\n", cmd); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci return -EIO; 22762306a36Sopenharmony_ci} 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_cistatic int sc6000_dsp_get_answer(char __iomem *vport, int command, 23062306a36Sopenharmony_ci char *data, int data_len) 23162306a36Sopenharmony_ci{ 23262306a36Sopenharmony_ci int len = 0; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci if (sc6000_write(vport, command)) { 23562306a36Sopenharmony_ci snd_printk(KERN_ERR "CMD 0x%x: failed!\n", command); 23662306a36Sopenharmony_ci return -EIO; 23762306a36Sopenharmony_ci } 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci do { 24062306a36Sopenharmony_ci int val = sc6000_read(vport); 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci if (val < 0) 24362306a36Sopenharmony_ci break; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci data[len++] = val; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci } while (len < data_len); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci /* 25062306a36Sopenharmony_ci * If no more data available, return to the caller, no error if len>0. 25162306a36Sopenharmony_ci * We have no other way to know when the string is finished. 25262306a36Sopenharmony_ci */ 25362306a36Sopenharmony_ci return len ? len : -EIO; 25462306a36Sopenharmony_ci} 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_cistatic int sc6000_dsp_reset(char __iomem *vport) 25762306a36Sopenharmony_ci{ 25862306a36Sopenharmony_ci iowrite8(1, vport + DSP_RESET); 25962306a36Sopenharmony_ci udelay(10); 26062306a36Sopenharmony_ci iowrite8(0, vport + DSP_RESET); 26162306a36Sopenharmony_ci udelay(20); 26262306a36Sopenharmony_ci if (sc6000_read(vport) == 0xaa) 26362306a36Sopenharmony_ci return 0; 26462306a36Sopenharmony_ci return -ENODEV; 26562306a36Sopenharmony_ci} 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci/* detection and initialization */ 26862306a36Sopenharmony_cistatic int sc6000_hw_cfg_write(char __iomem *vport, const int *cfg) 26962306a36Sopenharmony_ci{ 27062306a36Sopenharmony_ci if (sc6000_write(vport, COMMAND_6C) < 0) { 27162306a36Sopenharmony_ci snd_printk(KERN_WARNING "CMD 0x%x: failed!\n", COMMAND_6C); 27262306a36Sopenharmony_ci return -EIO; 27362306a36Sopenharmony_ci } 27462306a36Sopenharmony_ci if (sc6000_write(vport, COMMAND_5C) < 0) { 27562306a36Sopenharmony_ci snd_printk(KERN_ERR "CMD 0x%x: failed!\n", COMMAND_5C); 27662306a36Sopenharmony_ci return -EIO; 27762306a36Sopenharmony_ci } 27862306a36Sopenharmony_ci if (sc6000_write(vport, cfg[0]) < 0) { 27962306a36Sopenharmony_ci snd_printk(KERN_ERR "DATA 0x%x: failed!\n", cfg[0]); 28062306a36Sopenharmony_ci return -EIO; 28162306a36Sopenharmony_ci } 28262306a36Sopenharmony_ci if (sc6000_write(vport, cfg[1]) < 0) { 28362306a36Sopenharmony_ci snd_printk(KERN_ERR "DATA 0x%x: failed!\n", cfg[1]); 28462306a36Sopenharmony_ci return -EIO; 28562306a36Sopenharmony_ci } 28662306a36Sopenharmony_ci if (sc6000_write(vport, COMMAND_C5) < 0) { 28762306a36Sopenharmony_ci snd_printk(KERN_ERR "CMD 0x%x: failed!\n", COMMAND_C5); 28862306a36Sopenharmony_ci return -EIO; 28962306a36Sopenharmony_ci } 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci return 0; 29262306a36Sopenharmony_ci} 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_cistatic int sc6000_cfg_write(char __iomem *vport, unsigned char softcfg) 29562306a36Sopenharmony_ci{ 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci if (sc6000_write(vport, WRITE_MDIRQ_CFG)) { 29862306a36Sopenharmony_ci snd_printk(KERN_ERR "CMD 0x%x: failed!\n", WRITE_MDIRQ_CFG); 29962306a36Sopenharmony_ci return -EIO; 30062306a36Sopenharmony_ci } 30162306a36Sopenharmony_ci if (sc6000_write(vport, softcfg)) { 30262306a36Sopenharmony_ci snd_printk(KERN_ERR "sc6000_cfg_write: failed!\n"); 30362306a36Sopenharmony_ci return -EIO; 30462306a36Sopenharmony_ci } 30562306a36Sopenharmony_ci return 0; 30662306a36Sopenharmony_ci} 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_cistatic int sc6000_setup_board(char __iomem *vport, int config) 30962306a36Sopenharmony_ci{ 31062306a36Sopenharmony_ci int loop = 10; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci do { 31362306a36Sopenharmony_ci if (sc6000_write(vport, COMMAND_88)) { 31462306a36Sopenharmony_ci snd_printk(KERN_ERR "CMD 0x%x: failed!\n", 31562306a36Sopenharmony_ci COMMAND_88); 31662306a36Sopenharmony_ci return -EIO; 31762306a36Sopenharmony_ci } 31862306a36Sopenharmony_ci } while ((sc6000_wait_data(vport) < 0) && loop--); 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci if (sc6000_read(vport) < 0) { 32162306a36Sopenharmony_ci snd_printk(KERN_ERR "sc6000_read after CMD 0x%x: failed\n", 32262306a36Sopenharmony_ci COMMAND_88); 32362306a36Sopenharmony_ci return -EIO; 32462306a36Sopenharmony_ci } 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci if (sc6000_cfg_write(vport, config)) 32762306a36Sopenharmony_ci return -ENODEV; 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci return 0; 33062306a36Sopenharmony_ci} 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_cistatic int sc6000_init_mss(char __iomem *vport, int config, 33362306a36Sopenharmony_ci char __iomem *vmss_port, int mss_config) 33462306a36Sopenharmony_ci{ 33562306a36Sopenharmony_ci if (sc6000_write(vport, DSP_INIT_MSS)) { 33662306a36Sopenharmony_ci snd_printk(KERN_ERR "sc6000_init_mss [0x%x]: failed!\n", 33762306a36Sopenharmony_ci DSP_INIT_MSS); 33862306a36Sopenharmony_ci return -EIO; 33962306a36Sopenharmony_ci } 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci msleep(10); 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci if (sc6000_cfg_write(vport, config)) 34462306a36Sopenharmony_ci return -EIO; 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci iowrite8(mss_config, vmss_port); 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci return 0; 34962306a36Sopenharmony_ci} 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_cistatic void sc6000_hw_cfg_encode(char __iomem *vport, int *cfg, 35262306a36Sopenharmony_ci long xport, long xmpu, 35362306a36Sopenharmony_ci long xmss_port, int joystick) 35462306a36Sopenharmony_ci{ 35562306a36Sopenharmony_ci cfg[0] = 0; 35662306a36Sopenharmony_ci cfg[1] = 0; 35762306a36Sopenharmony_ci if (xport == 0x240) 35862306a36Sopenharmony_ci cfg[0] |= 1; 35962306a36Sopenharmony_ci if (xmpu != SNDRV_AUTO_PORT) { 36062306a36Sopenharmony_ci cfg[0] |= (xmpu & 0x30) >> 2; 36162306a36Sopenharmony_ci cfg[1] |= 0x20; 36262306a36Sopenharmony_ci } 36362306a36Sopenharmony_ci if (xmss_port == 0xe80) 36462306a36Sopenharmony_ci cfg[0] |= 0x10; 36562306a36Sopenharmony_ci cfg[0] |= 0x40; /* always set */ 36662306a36Sopenharmony_ci if (!joystick) 36762306a36Sopenharmony_ci cfg[0] |= 0x02; 36862306a36Sopenharmony_ci cfg[1] |= 0x80; /* enable WSS system */ 36962306a36Sopenharmony_ci cfg[1] &= ~0x40; /* disable IDE */ 37062306a36Sopenharmony_ci snd_printd("hw cfg %x, %x\n", cfg[0], cfg[1]); 37162306a36Sopenharmony_ci} 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_cistatic int sc6000_init_board(char __iomem *vport, 37462306a36Sopenharmony_ci char __iomem *vmss_port, int dev) 37562306a36Sopenharmony_ci{ 37662306a36Sopenharmony_ci char answer[15]; 37762306a36Sopenharmony_ci char version[2]; 37862306a36Sopenharmony_ci int mss_config = sc6000_irq_to_softcfg(irq[dev]) | 37962306a36Sopenharmony_ci sc6000_dma_to_softcfg(dma[dev]); 38062306a36Sopenharmony_ci int config = mss_config | 38162306a36Sopenharmony_ci sc6000_mpu_irq_to_softcfg(mpu_irq[dev]); 38262306a36Sopenharmony_ci int err; 38362306a36Sopenharmony_ci int old = 0; 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci err = sc6000_dsp_reset(vport); 38662306a36Sopenharmony_ci if (err < 0) { 38762306a36Sopenharmony_ci snd_printk(KERN_ERR "sc6000_dsp_reset: failed!\n"); 38862306a36Sopenharmony_ci return err; 38962306a36Sopenharmony_ci } 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci memset(answer, 0, sizeof(answer)); 39262306a36Sopenharmony_ci err = sc6000_dsp_get_answer(vport, GET_DSP_COPYRIGHT, answer, 15); 39362306a36Sopenharmony_ci if (err <= 0) { 39462306a36Sopenharmony_ci snd_printk(KERN_ERR "sc6000_dsp_copyright: failed!\n"); 39562306a36Sopenharmony_ci return -ENODEV; 39662306a36Sopenharmony_ci } 39762306a36Sopenharmony_ci /* 39862306a36Sopenharmony_ci * My SC-6000 card return "SC-6000" in DSPCopyright, so 39962306a36Sopenharmony_ci * if we have something different, we have to be warned. 40062306a36Sopenharmony_ci */ 40162306a36Sopenharmony_ci if (strncmp("SC-6000", answer, 7)) 40262306a36Sopenharmony_ci snd_printk(KERN_WARNING "Warning: non SC-6000 audio card!\n"); 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci if (sc6000_dsp_get_answer(vport, GET_DSP_VERSION, version, 2) < 2) { 40562306a36Sopenharmony_ci snd_printk(KERN_ERR "sc6000_dsp_version: failed!\n"); 40662306a36Sopenharmony_ci return -ENODEV; 40762306a36Sopenharmony_ci } 40862306a36Sopenharmony_ci printk(KERN_INFO PFX "Detected model: %s, DSP version %d.%d\n", 40962306a36Sopenharmony_ci answer, version[0], version[1]); 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci /* set configuration */ 41262306a36Sopenharmony_ci sc6000_write(vport, COMMAND_5C); 41362306a36Sopenharmony_ci if (sc6000_read(vport) < 0) 41462306a36Sopenharmony_ci old = 1; 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci if (!old) { 41762306a36Sopenharmony_ci int cfg[2]; 41862306a36Sopenharmony_ci sc6000_hw_cfg_encode(vport, &cfg[0], port[dev], mpu_port[dev], 41962306a36Sopenharmony_ci mss_port[dev], joystick[dev]); 42062306a36Sopenharmony_ci if (sc6000_hw_cfg_write(vport, cfg) < 0) { 42162306a36Sopenharmony_ci snd_printk(KERN_ERR "sc6000_hw_cfg_write: failed!\n"); 42262306a36Sopenharmony_ci return -EIO; 42362306a36Sopenharmony_ci } 42462306a36Sopenharmony_ci } 42562306a36Sopenharmony_ci err = sc6000_setup_board(vport, config); 42662306a36Sopenharmony_ci if (err < 0) { 42762306a36Sopenharmony_ci snd_printk(KERN_ERR "sc6000_setup_board: failed!\n"); 42862306a36Sopenharmony_ci return -ENODEV; 42962306a36Sopenharmony_ci } 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci sc6000_dsp_reset(vport); 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci if (!old) { 43462306a36Sopenharmony_ci sc6000_write(vport, COMMAND_60); 43562306a36Sopenharmony_ci sc6000_write(vport, 0x02); 43662306a36Sopenharmony_ci sc6000_dsp_reset(vport); 43762306a36Sopenharmony_ci } 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci err = sc6000_setup_board(vport, config); 44062306a36Sopenharmony_ci if (err < 0) { 44162306a36Sopenharmony_ci snd_printk(KERN_ERR "sc6000_setup_board: failed!\n"); 44262306a36Sopenharmony_ci return -ENODEV; 44362306a36Sopenharmony_ci } 44462306a36Sopenharmony_ci err = sc6000_init_mss(vport, config, vmss_port, mss_config); 44562306a36Sopenharmony_ci if (err < 0) { 44662306a36Sopenharmony_ci snd_printk(KERN_ERR "Cannot initialize " 44762306a36Sopenharmony_ci "Microsoft Sound System mode.\n"); 44862306a36Sopenharmony_ci return -ENODEV; 44962306a36Sopenharmony_ci } 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci return 0; 45262306a36Sopenharmony_ci} 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_cistatic int snd_sc6000_mixer(struct snd_wss *chip) 45562306a36Sopenharmony_ci{ 45662306a36Sopenharmony_ci struct snd_card *card = chip->card; 45762306a36Sopenharmony_ci struct snd_ctl_elem_id id1, id2; 45862306a36Sopenharmony_ci int err; 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci memset(&id1, 0, sizeof(id1)); 46162306a36Sopenharmony_ci memset(&id2, 0, sizeof(id2)); 46262306a36Sopenharmony_ci id1.iface = SNDRV_CTL_ELEM_IFACE_MIXER; 46362306a36Sopenharmony_ci id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; 46462306a36Sopenharmony_ci /* reassign AUX0 to FM */ 46562306a36Sopenharmony_ci strcpy(id1.name, "Aux Playback Switch"); 46662306a36Sopenharmony_ci strcpy(id2.name, "FM Playback Switch"); 46762306a36Sopenharmony_ci err = snd_ctl_rename_id(card, &id1, &id2); 46862306a36Sopenharmony_ci if (err < 0) 46962306a36Sopenharmony_ci return err; 47062306a36Sopenharmony_ci strcpy(id1.name, "Aux Playback Volume"); 47162306a36Sopenharmony_ci strcpy(id2.name, "FM Playback Volume"); 47262306a36Sopenharmony_ci err = snd_ctl_rename_id(card, &id1, &id2); 47362306a36Sopenharmony_ci if (err < 0) 47462306a36Sopenharmony_ci return err; 47562306a36Sopenharmony_ci /* reassign AUX1 to CD */ 47662306a36Sopenharmony_ci strcpy(id1.name, "Aux Playback Switch"); id1.index = 1; 47762306a36Sopenharmony_ci strcpy(id2.name, "CD Playback Switch"); 47862306a36Sopenharmony_ci err = snd_ctl_rename_id(card, &id1, &id2); 47962306a36Sopenharmony_ci if (err < 0) 48062306a36Sopenharmony_ci return err; 48162306a36Sopenharmony_ci strcpy(id1.name, "Aux Playback Volume"); 48262306a36Sopenharmony_ci strcpy(id2.name, "CD Playback Volume"); 48362306a36Sopenharmony_ci err = snd_ctl_rename_id(card, &id1, &id2); 48462306a36Sopenharmony_ci if (err < 0) 48562306a36Sopenharmony_ci return err; 48662306a36Sopenharmony_ci return 0; 48762306a36Sopenharmony_ci} 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_cistatic int snd_sc6000_match(struct device *devptr, unsigned int dev) 49062306a36Sopenharmony_ci{ 49162306a36Sopenharmony_ci if (!enable[dev]) 49262306a36Sopenharmony_ci return 0; 49362306a36Sopenharmony_ci if (port[dev] == SNDRV_AUTO_PORT) { 49462306a36Sopenharmony_ci printk(KERN_ERR PFX "specify IO port\n"); 49562306a36Sopenharmony_ci return 0; 49662306a36Sopenharmony_ci } 49762306a36Sopenharmony_ci if (mss_port[dev] == SNDRV_AUTO_PORT) { 49862306a36Sopenharmony_ci printk(KERN_ERR PFX "specify MSS port\n"); 49962306a36Sopenharmony_ci return 0; 50062306a36Sopenharmony_ci } 50162306a36Sopenharmony_ci if (port[dev] != 0x220 && port[dev] != 0x240) { 50262306a36Sopenharmony_ci printk(KERN_ERR PFX "Port must be 0x220 or 0x240\n"); 50362306a36Sopenharmony_ci return 0; 50462306a36Sopenharmony_ci } 50562306a36Sopenharmony_ci if (mss_port[dev] != 0x530 && mss_port[dev] != 0xe80) { 50662306a36Sopenharmony_ci printk(KERN_ERR PFX "MSS port must be 0x530 or 0xe80\n"); 50762306a36Sopenharmony_ci return 0; 50862306a36Sopenharmony_ci } 50962306a36Sopenharmony_ci if (irq[dev] != SNDRV_AUTO_IRQ && !sc6000_irq_to_softcfg(irq[dev])) { 51062306a36Sopenharmony_ci printk(KERN_ERR PFX "invalid IRQ %d\n", irq[dev]); 51162306a36Sopenharmony_ci return 0; 51262306a36Sopenharmony_ci } 51362306a36Sopenharmony_ci if (dma[dev] != SNDRV_AUTO_DMA && !sc6000_dma_to_softcfg(dma[dev])) { 51462306a36Sopenharmony_ci printk(KERN_ERR PFX "invalid DMA %d\n", dma[dev]); 51562306a36Sopenharmony_ci return 0; 51662306a36Sopenharmony_ci } 51762306a36Sopenharmony_ci if (mpu_port[dev] != SNDRV_AUTO_PORT && 51862306a36Sopenharmony_ci (mpu_port[dev] & ~0x30L) != 0x300) { 51962306a36Sopenharmony_ci printk(KERN_ERR PFX "invalid MPU-401 port %lx\n", 52062306a36Sopenharmony_ci mpu_port[dev]); 52162306a36Sopenharmony_ci return 0; 52262306a36Sopenharmony_ci } 52362306a36Sopenharmony_ci if (mpu_port[dev] != SNDRV_AUTO_PORT && 52462306a36Sopenharmony_ci mpu_irq[dev] != SNDRV_AUTO_IRQ && mpu_irq[dev] != 0 && 52562306a36Sopenharmony_ci !sc6000_mpu_irq_to_softcfg(mpu_irq[dev])) { 52662306a36Sopenharmony_ci printk(KERN_ERR PFX "invalid MPU-401 IRQ %d\n", mpu_irq[dev]); 52762306a36Sopenharmony_ci return 0; 52862306a36Sopenharmony_ci } 52962306a36Sopenharmony_ci return 1; 53062306a36Sopenharmony_ci} 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_cistatic void snd_sc6000_free(struct snd_card *card) 53362306a36Sopenharmony_ci{ 53462306a36Sopenharmony_ci char __iomem *vport = (char __force __iomem *)card->private_data; 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci if (vport) 53762306a36Sopenharmony_ci sc6000_setup_board(vport, 0); 53862306a36Sopenharmony_ci} 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_cistatic int __snd_sc6000_probe(struct device *devptr, unsigned int dev) 54162306a36Sopenharmony_ci{ 54262306a36Sopenharmony_ci static const int possible_irqs[] = { 5, 7, 9, 10, 11, -1 }; 54362306a36Sopenharmony_ci static const int possible_dmas[] = { 1, 3, 0, -1 }; 54462306a36Sopenharmony_ci int err; 54562306a36Sopenharmony_ci int xirq = irq[dev]; 54662306a36Sopenharmony_ci int xdma = dma[dev]; 54762306a36Sopenharmony_ci struct snd_card *card; 54862306a36Sopenharmony_ci struct snd_wss *chip; 54962306a36Sopenharmony_ci struct snd_opl3 *opl3; 55062306a36Sopenharmony_ci char __iomem *vport; 55162306a36Sopenharmony_ci char __iomem *vmss_port; 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci err = snd_devm_card_new(devptr, index[dev], id[dev], THIS_MODULE, 55462306a36Sopenharmony_ci 0, &card); 55562306a36Sopenharmony_ci if (err < 0) 55662306a36Sopenharmony_ci return err; 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci if (xirq == SNDRV_AUTO_IRQ) { 55962306a36Sopenharmony_ci xirq = snd_legacy_find_free_irq(possible_irqs); 56062306a36Sopenharmony_ci if (xirq < 0) { 56162306a36Sopenharmony_ci snd_printk(KERN_ERR PFX "unable to find a free IRQ\n"); 56262306a36Sopenharmony_ci return -EBUSY; 56362306a36Sopenharmony_ci } 56462306a36Sopenharmony_ci } 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci if (xdma == SNDRV_AUTO_DMA) { 56762306a36Sopenharmony_ci xdma = snd_legacy_find_free_dma(possible_dmas); 56862306a36Sopenharmony_ci if (xdma < 0) { 56962306a36Sopenharmony_ci snd_printk(KERN_ERR PFX "unable to find a free DMA\n"); 57062306a36Sopenharmony_ci return -EBUSY; 57162306a36Sopenharmony_ci } 57262306a36Sopenharmony_ci } 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci if (!devm_request_region(devptr, port[dev], 0x10, DRV_NAME)) { 57562306a36Sopenharmony_ci snd_printk(KERN_ERR PFX 57662306a36Sopenharmony_ci "I/O port region is already in use.\n"); 57762306a36Sopenharmony_ci return -EBUSY; 57862306a36Sopenharmony_ci } 57962306a36Sopenharmony_ci vport = devm_ioport_map(devptr, port[dev], 0x10); 58062306a36Sopenharmony_ci if (!vport) { 58162306a36Sopenharmony_ci snd_printk(KERN_ERR PFX 58262306a36Sopenharmony_ci "I/O port cannot be iomapped.\n"); 58362306a36Sopenharmony_ci return -EBUSY; 58462306a36Sopenharmony_ci } 58562306a36Sopenharmony_ci card->private_data = (void __force *)vport; 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci /* to make it marked as used */ 58862306a36Sopenharmony_ci if (!devm_request_region(devptr, mss_port[dev], 4, DRV_NAME)) { 58962306a36Sopenharmony_ci snd_printk(KERN_ERR PFX 59062306a36Sopenharmony_ci "SC-6000 port I/O port region is already in use.\n"); 59162306a36Sopenharmony_ci return -EBUSY; 59262306a36Sopenharmony_ci } 59362306a36Sopenharmony_ci vmss_port = devm_ioport_map(devptr, mss_port[dev], 4); 59462306a36Sopenharmony_ci if (!vmss_port) { 59562306a36Sopenharmony_ci snd_printk(KERN_ERR PFX 59662306a36Sopenharmony_ci "MSS port I/O cannot be iomapped.\n"); 59762306a36Sopenharmony_ci return -EBUSY; 59862306a36Sopenharmony_ci } 59962306a36Sopenharmony_ci 60062306a36Sopenharmony_ci snd_printd("Initializing BASE[0x%lx] IRQ[%d] DMA[%d] MIRQ[%d]\n", 60162306a36Sopenharmony_ci port[dev], xirq, xdma, 60262306a36Sopenharmony_ci mpu_irq[dev] == SNDRV_AUTO_IRQ ? 0 : mpu_irq[dev]); 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci err = sc6000_init_board(vport, vmss_port, dev); 60562306a36Sopenharmony_ci if (err < 0) 60662306a36Sopenharmony_ci return err; 60762306a36Sopenharmony_ci card->private_free = snd_sc6000_free; 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci err = snd_wss_create(card, mss_port[dev] + 4, -1, xirq, xdma, -1, 61062306a36Sopenharmony_ci WSS_HW_DETECT, 0, &chip); 61162306a36Sopenharmony_ci if (err < 0) 61262306a36Sopenharmony_ci return err; 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci err = snd_wss_pcm(chip, 0); 61562306a36Sopenharmony_ci if (err < 0) { 61662306a36Sopenharmony_ci snd_printk(KERN_ERR PFX 61762306a36Sopenharmony_ci "error creating new WSS PCM device\n"); 61862306a36Sopenharmony_ci return err; 61962306a36Sopenharmony_ci } 62062306a36Sopenharmony_ci err = snd_wss_mixer(chip); 62162306a36Sopenharmony_ci if (err < 0) { 62262306a36Sopenharmony_ci snd_printk(KERN_ERR PFX "error creating new WSS mixer\n"); 62362306a36Sopenharmony_ci return err; 62462306a36Sopenharmony_ci } 62562306a36Sopenharmony_ci err = snd_sc6000_mixer(chip); 62662306a36Sopenharmony_ci if (err < 0) { 62762306a36Sopenharmony_ci snd_printk(KERN_ERR PFX "the mixer rewrite failed\n"); 62862306a36Sopenharmony_ci return err; 62962306a36Sopenharmony_ci } 63062306a36Sopenharmony_ci if (snd_opl3_create(card, 63162306a36Sopenharmony_ci 0x388, 0x388 + 2, 63262306a36Sopenharmony_ci OPL3_HW_AUTO, 0, &opl3) < 0) { 63362306a36Sopenharmony_ci snd_printk(KERN_ERR PFX "no OPL device at 0x%x-0x%x ?\n", 63462306a36Sopenharmony_ci 0x388, 0x388 + 2); 63562306a36Sopenharmony_ci } else { 63662306a36Sopenharmony_ci err = snd_opl3_hwdep_new(opl3, 0, 1, NULL); 63762306a36Sopenharmony_ci if (err < 0) 63862306a36Sopenharmony_ci return err; 63962306a36Sopenharmony_ci } 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci if (mpu_port[dev] != SNDRV_AUTO_PORT) { 64262306a36Sopenharmony_ci if (mpu_irq[dev] == SNDRV_AUTO_IRQ) 64362306a36Sopenharmony_ci mpu_irq[dev] = -1; 64462306a36Sopenharmony_ci if (snd_mpu401_uart_new(card, 0, 64562306a36Sopenharmony_ci MPU401_HW_MPU401, 64662306a36Sopenharmony_ci mpu_port[dev], 0, 64762306a36Sopenharmony_ci mpu_irq[dev], NULL) < 0) 64862306a36Sopenharmony_ci snd_printk(KERN_ERR "no MPU-401 device at 0x%lx ?\n", 64962306a36Sopenharmony_ci mpu_port[dev]); 65062306a36Sopenharmony_ci } 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_ci strcpy(card->driver, DRV_NAME); 65362306a36Sopenharmony_ci strcpy(card->shortname, "SC-6000"); 65462306a36Sopenharmony_ci sprintf(card->longname, "Gallant SC-6000 at 0x%lx, irq %d, dma %d", 65562306a36Sopenharmony_ci mss_port[dev], xirq, xdma); 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_ci err = snd_card_register(card); 65862306a36Sopenharmony_ci if (err < 0) 65962306a36Sopenharmony_ci return err; 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ci dev_set_drvdata(devptr, card); 66262306a36Sopenharmony_ci return 0; 66362306a36Sopenharmony_ci} 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_cistatic int snd_sc6000_probe(struct device *devptr, unsigned int dev) 66662306a36Sopenharmony_ci{ 66762306a36Sopenharmony_ci return snd_card_free_on_error(devptr, __snd_sc6000_probe(devptr, dev)); 66862306a36Sopenharmony_ci} 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_cistatic struct isa_driver snd_sc6000_driver = { 67162306a36Sopenharmony_ci .match = snd_sc6000_match, 67262306a36Sopenharmony_ci .probe = snd_sc6000_probe, 67362306a36Sopenharmony_ci /* FIXME: suspend/resume */ 67462306a36Sopenharmony_ci .driver = { 67562306a36Sopenharmony_ci .name = DRV_NAME, 67662306a36Sopenharmony_ci }, 67762306a36Sopenharmony_ci}; 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_ci 68062306a36Sopenharmony_cimodule_isa_driver(snd_sc6000_driver, SNDRV_CARDS); 681