1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * cs5530.c - Initialisation code for Cyrix/NatSemi VSA1 softaudio 4 * 5 * (C) Copyright 2007 Ash Willis <ashwillis@programmer.net> 6 * (C) Copyright 2003 Red Hat Inc <alan@lxorguk.ukuu.org.uk> 7 * 8 * This driver was ported (shamelessly ripped ;) from oss/kahlua.c but I did 9 * mess with it a bit. The chip seems to have to have trouble with full duplex 10 * mode. If we're recording in 8bit 8000kHz, say, and we then attempt to 11 * simultaneously play back audio at 16bit 44100kHz, the device actually plays 12 * back in the same format in which it is capturing. By forcing the chip to 13 * always play/capture in 16/44100, we can let alsa-lib convert the samples and 14 * that way we can hack up some full duplex audio. 15 * 16 * XpressAudio(tm) is used on the Cyrix MediaGX (now NatSemi Geode) systems. 17 * The older version (VSA1) provides fairly good soundblaster emulation 18 * although there are a couple of bugs: large DMA buffers break record, 19 * and the MPU event handling seems suspect. VSA2 allows the native driver 20 * to control the AC97 audio engine directly and requires a different driver. 21 * 22 * Thanks to National Semiconductor for providing the needed information 23 * on the XpressAudio(tm) internals. 24 * 25 * TO DO: 26 * Investigate whether we can portably support Cognac (5520) in the 27 * same manner. 28 */ 29 30 #include <linux/delay.h> 31 #include <linux/module.h> 32 #include <linux/pci.h> 33 #include <linux/slab.h> 34 #include <sound/core.h> 35 #include <sound/sb.h> 36 #include <sound/initval.h> 37 38 MODULE_AUTHOR("Ash Willis"); 39 MODULE_DESCRIPTION("CS5530 Audio"); 40 MODULE_LICENSE("GPL"); 41 42 static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; 43 static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; 44 static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; 45 46 module_param_array(index, int, NULL, 0444); 47 MODULE_PARM_DESC(index, "Index value for CS5530 Audio driver."); 48 module_param_array(id, charp, NULL, 0444); 49 MODULE_PARM_DESC(id, "ID string for CS5530 Audio driver."); 50 module_param_array(enable, bool, NULL, 0444); 51 MODULE_PARM_DESC(enable, "Enable CS5530 Audio driver."); 52 53 struct snd_cs5530 { 54 struct snd_card *card; 55 struct pci_dev *pci; 56 struct snd_sb *sb; 57 unsigned long pci_base; 58 }; 59 60 static const struct pci_device_id snd_cs5530_ids[] = { 61 {PCI_VENDOR_ID_CYRIX, PCI_DEVICE_ID_CYRIX_5530_AUDIO, PCI_ANY_ID, 62 PCI_ANY_ID, 0, 0}, 63 {0,} 64 }; 65 66 MODULE_DEVICE_TABLE(pci, snd_cs5530_ids); 67 68 static u8 snd_cs5530_mixer_read(unsigned long io, u8 reg) 69 { 70 outb(reg, io + 4); 71 udelay(20); 72 reg = inb(io + 5); 73 udelay(20); 74 return reg; 75 } 76 77 static int snd_cs5530_create(struct snd_card *card, 78 struct pci_dev *pci) 79 { 80 struct snd_cs5530 *chip = card->private_data; 81 unsigned long sb_base; 82 u8 irq, dma8, dma16 = 0; 83 u16 map; 84 void __iomem *mem; 85 int err; 86 87 err = pcim_enable_device(pci); 88 if (err < 0) 89 return err; 90 91 chip->card = card; 92 chip->pci = pci; 93 94 mem = pcim_iomap_region(pci, 0, "CS5530"); 95 if (IS_ERR(mem)) 96 return PTR_ERR(mem); 97 chip->pci_base = pci_resource_start(pci, 0); 98 map = readw(mem + 0x18); 99 100 /* Map bits 101 0:1 * 0x20 + 0x200 = sb base 102 2 sb enable 103 3 adlib enable 104 5 MPU enable 0x330 105 6 MPU enable 0x300 106 107 The other bits may be used internally so must be masked */ 108 109 sb_base = 0x220 + 0x20 * (map & 3); 110 111 if (map & (1<<2)) 112 dev_info(card->dev, "XpressAudio at 0x%lx\n", sb_base); 113 else { 114 dev_err(card->dev, "Could not find XpressAudio!\n"); 115 return -ENODEV; 116 } 117 118 if (map & (1<<5)) 119 dev_info(card->dev, "MPU at 0x300\n"); 120 else if (map & (1<<6)) 121 dev_info(card->dev, "MPU at 0x330\n"); 122 123 irq = snd_cs5530_mixer_read(sb_base, 0x80) & 0x0F; 124 dma8 = snd_cs5530_mixer_read(sb_base, 0x81); 125 126 if (dma8 & 0x20) 127 dma16 = 5; 128 else if (dma8 & 0x40) 129 dma16 = 6; 130 else if (dma8 & 0x80) 131 dma16 = 7; 132 else { 133 dev_err(card->dev, "No 16bit DMA enabled\n"); 134 return -ENODEV; 135 } 136 137 if (dma8 & 0x01) 138 dma8 = 0; 139 else if (dma8 & 02) 140 dma8 = 1; 141 else if (dma8 & 0x08) 142 dma8 = 3; 143 else { 144 dev_err(card->dev, "No 8bit DMA enabled\n"); 145 return -ENODEV; 146 } 147 148 if (irq & 1) 149 irq = 9; 150 else if (irq & 2) 151 irq = 5; 152 else if (irq & 4) 153 irq = 7; 154 else if (irq & 8) 155 irq = 10; 156 else { 157 dev_err(card->dev, "SoundBlaster IRQ not set\n"); 158 return -ENODEV; 159 } 160 161 dev_info(card->dev, "IRQ: %d DMA8: %d DMA16: %d\n", irq, dma8, dma16); 162 163 err = snd_sbdsp_create(card, sb_base, irq, snd_sb16dsp_interrupt, dma8, 164 dma16, SB_HW_CS5530, &chip->sb); 165 if (err < 0) { 166 dev_err(card->dev, "Could not create SoundBlaster\n"); 167 return err; 168 } 169 170 err = snd_sb16dsp_pcm(chip->sb, 0); 171 if (err < 0) { 172 dev_err(card->dev, "Could not create PCM\n"); 173 return err; 174 } 175 176 err = snd_sbmixer_new(chip->sb); 177 if (err < 0) { 178 dev_err(card->dev, "Could not create Mixer\n"); 179 return err; 180 } 181 182 return 0; 183 } 184 185 static int snd_cs5530_probe(struct pci_dev *pci, 186 const struct pci_device_id *pci_id) 187 { 188 static int dev; 189 struct snd_card *card; 190 struct snd_cs5530 *chip; 191 int err; 192 193 if (dev >= SNDRV_CARDS) 194 return -ENODEV; 195 if (!enable[dev]) { 196 dev++; 197 return -ENOENT; 198 } 199 200 err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, 201 sizeof(*chip), &card); 202 if (err < 0) 203 return err; 204 chip = card->private_data; 205 206 err = snd_cs5530_create(card, pci); 207 if (err < 0) 208 return err; 209 210 strcpy(card->driver, "CS5530"); 211 strcpy(card->shortname, "CS5530 Audio"); 212 sprintf(card->longname, "%s at 0x%lx", card->shortname, chip->pci_base); 213 214 err = snd_card_register(card); 215 if (err < 0) 216 return err; 217 pci_set_drvdata(pci, card); 218 dev++; 219 return 0; 220 } 221 222 static struct pci_driver cs5530_driver = { 223 .name = KBUILD_MODNAME, 224 .id_table = snd_cs5530_ids, 225 .probe = snd_cs5530_probe, 226 }; 227 228 module_pci_driver(cs5530_driver); 229