cs5530.c (5654B)
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 38MODULE_AUTHOR("Ash Willis"); 39MODULE_DESCRIPTION("CS5530 Audio"); 40MODULE_LICENSE("GPL"); 41 42static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; 43static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; 44static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; 45 46module_param_array(index, int, NULL, 0444); 47MODULE_PARM_DESC(index, "Index value for CS5530 Audio driver."); 48module_param_array(id, charp, NULL, 0444); 49MODULE_PARM_DESC(id, "ID string for CS5530 Audio driver."); 50module_param_array(enable, bool, NULL, 0444); 51MODULE_PARM_DESC(enable, "Enable CS5530 Audio driver."); 52 53struct snd_cs5530 { 54 struct snd_card *card; 55 struct pci_dev *pci; 56 struct snd_sb *sb; 57 unsigned long pci_base; 58}; 59 60static 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 66MODULE_DEVICE_TABLE(pci, snd_cs5530_ids); 67 68static 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 77static 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 err = pcim_iomap_regions(pci, 1 << 0, "CS5530"); 95 if (err < 0) 96 return err; 97 chip->pci_base = pci_resource_start(pci, 0); 98 mem = pcim_iomap_table(pci)[0]; 99 map = readw(mem + 0x18); 100 101 /* Map bits 102 0:1 * 0x20 + 0x200 = sb base 103 2 sb enable 104 3 adlib enable 105 5 MPU enable 0x330 106 6 MPU enable 0x300 107 108 The other bits may be used internally so must be masked */ 109 110 sb_base = 0x220 + 0x20 * (map & 3); 111 112 if (map & (1<<2)) 113 dev_info(card->dev, "XpressAudio at 0x%lx\n", sb_base); 114 else { 115 dev_err(card->dev, "Could not find XpressAudio!\n"); 116 return -ENODEV; 117 } 118 119 if (map & (1<<5)) 120 dev_info(card->dev, "MPU at 0x300\n"); 121 else if (map & (1<<6)) 122 dev_info(card->dev, "MPU at 0x330\n"); 123 124 irq = snd_cs5530_mixer_read(sb_base, 0x80) & 0x0F; 125 dma8 = snd_cs5530_mixer_read(sb_base, 0x81); 126 127 if (dma8 & 0x20) 128 dma16 = 5; 129 else if (dma8 & 0x40) 130 dma16 = 6; 131 else if (dma8 & 0x80) 132 dma16 = 7; 133 else { 134 dev_err(card->dev, "No 16bit DMA enabled\n"); 135 return -ENODEV; 136 } 137 138 if (dma8 & 0x01) 139 dma8 = 0; 140 else if (dma8 & 02) 141 dma8 = 1; 142 else if (dma8 & 0x08) 143 dma8 = 3; 144 else { 145 dev_err(card->dev, "No 8bit DMA enabled\n"); 146 return -ENODEV; 147 } 148 149 if (irq & 1) 150 irq = 9; 151 else if (irq & 2) 152 irq = 5; 153 else if (irq & 4) 154 irq = 7; 155 else if (irq & 8) 156 irq = 10; 157 else { 158 dev_err(card->dev, "SoundBlaster IRQ not set\n"); 159 return -ENODEV; 160 } 161 162 dev_info(card->dev, "IRQ: %d DMA8: %d DMA16: %d\n", irq, dma8, dma16); 163 164 err = snd_sbdsp_create(card, sb_base, irq, snd_sb16dsp_interrupt, dma8, 165 dma16, SB_HW_CS5530, &chip->sb); 166 if (err < 0) { 167 dev_err(card->dev, "Could not create SoundBlaster\n"); 168 return err; 169 } 170 171 err = snd_sb16dsp_pcm(chip->sb, 0); 172 if (err < 0) { 173 dev_err(card->dev, "Could not create PCM\n"); 174 return err; 175 } 176 177 err = snd_sbmixer_new(chip->sb); 178 if (err < 0) { 179 dev_err(card->dev, "Could not create Mixer\n"); 180 return err; 181 } 182 183 return 0; 184} 185 186static int snd_cs5530_probe(struct pci_dev *pci, 187 const struct pci_device_id *pci_id) 188{ 189 static int dev; 190 struct snd_card *card; 191 struct snd_cs5530 *chip; 192 int err; 193 194 if (dev >= SNDRV_CARDS) 195 return -ENODEV; 196 if (!enable[dev]) { 197 dev++; 198 return -ENOENT; 199 } 200 201 err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, 202 sizeof(*chip), &card); 203 if (err < 0) 204 return err; 205 chip = card->private_data; 206 207 err = snd_cs5530_create(card, pci); 208 if (err < 0) 209 return err; 210 211 strcpy(card->driver, "CS5530"); 212 strcpy(card->shortname, "CS5530 Audio"); 213 sprintf(card->longname, "%s at 0x%lx", card->shortname, chip->pci_base); 214 215 err = snd_card_register(card); 216 if (err < 0) 217 return err; 218 pci_set_drvdata(pci, card); 219 dev++; 220 return 0; 221} 222 223static struct pci_driver cs5530_driver = { 224 .name = KBUILD_MODNAME, 225 .id_table = snd_cs5530_ids, 226 .probe = snd_cs5530_probe, 227}; 228 229module_pci_driver(cs5530_driver);