diff options
author | Takashi Iwai <tiwai@suse.de> | 2007-05-24 12:46:54 -0400 |
---|---|---|
committer | Jaroslav Kysela <perex@suse.cz> | 2007-07-20 05:11:19 -0400 |
commit | 621887aee9c7b4b613c12b82b83df7e56877f303 (patch) | |
tree | fa26d2a5a584db9646ec33df6eb3965f2704bc7a /sound/pci/cs5530.c | |
parent | 0ba7962b9f06c02dd1af93002e8d757805d16758 (diff) |
[ALSA] Add support for Cyrix/NatSemi Geode CS5530 (VSA1)
Add support for Cyrix/NatSemi Geode SC5530 (VSA1).
The driver is snd-cs5530.
Signed-off-by Ash Willis <ashwillis@programmer.net>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@suse.cz>
Diffstat (limited to 'sound/pci/cs5530.c')
-rw-r--r-- | sound/pci/cs5530.c | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/sound/pci/cs5530.c b/sound/pci/cs5530.c new file mode 100644 index 000000000000..240a0a462209 --- /dev/null +++ b/sound/pci/cs5530.c | |||
@@ -0,0 +1,306 @@ | |||
1 | /* | ||
2 | * cs5530.c - Initialisation code for Cyrix/NatSemi VSA1 softaudio | ||
3 | * | ||
4 | * (C) Copyright 2007 Ash Willis <ashwillis@programmer.net> | ||
5 | * (C) Copyright 2003 Red Hat Inc <alan@redhat.com> | ||
6 | * | ||
7 | * This driver was ported (shamelessly ripped ;) from oss/kahlua.c but I did | ||
8 | * mess with it a bit. The chip seems to have to have trouble with full duplex | ||
9 | * mode. If we're recording in 8bit 8000kHz, say, and we then attempt to | ||
10 | * simultaneously play back audio at 16bit 44100kHz, the device actually plays | ||
11 | * back in the same format in which it is capturing. By forcing the chip to | ||
12 | * always play/capture in 16/44100, we can let alsa-lib convert the samples and | ||
13 | * that way we can hack up some full duplex audio. | ||
14 | * | ||
15 | * XpressAudio(tm) is used on the Cyrix MediaGX (now NatSemi Geode) systems. | ||
16 | * The older version (VSA1) provides fairly good soundblaster emulation | ||
17 | * although there are a couple of bugs: large DMA buffers break record, | ||
18 | * and the MPU event handling seems suspect. VSA2 allows the native driver | ||
19 | * to control the AC97 audio engine directly and requires a different driver. | ||
20 | * | ||
21 | * Thanks to National Semiconductor for providing the needed information | ||
22 | * on the XpressAudio(tm) internals. | ||
23 | * | ||
24 | * This program is free software; you can redistribute it and/or modify it | ||
25 | * under the terms of the GNU General Public License as published by the | ||
26 | * Free Software Foundation; either version 2, or (at your option) any | ||
27 | * later version. | ||
28 | * | ||
29 | * This program is distributed in the hope that it will be useful, but | ||
30 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
31 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
32 | * General Public License for more details. | ||
33 | * | ||
34 | * TO DO: | ||
35 | * Investigate whether we can portably support Cognac (5520) in the | ||
36 | * same manner. | ||
37 | */ | ||
38 | |||
39 | #include <sound/driver.h> | ||
40 | #include <linux/delay.h> | ||
41 | #include <linux/moduleparam.h> | ||
42 | #include <linux/pci.h> | ||
43 | #include <sound/core.h> | ||
44 | #include <sound/sb.h> | ||
45 | #include <sound/initval.h> | ||
46 | |||
47 | MODULE_AUTHOR("Ash Willis"); | ||
48 | MODULE_DESCRIPTION("CS5530 Audio"); | ||
49 | MODULE_LICENSE("GPL"); | ||
50 | |||
51 | static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; | ||
52 | static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; | ||
53 | static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; | ||
54 | |||
55 | struct snd_cs5530 { | ||
56 | struct snd_card *card; | ||
57 | struct pci_dev *pci; | ||
58 | struct snd_sb *sb; | ||
59 | unsigned long pci_base; | ||
60 | }; | ||
61 | |||
62 | static struct pci_device_id snd_cs5530_ids[] = { | ||
63 | {PCI_VENDOR_ID_CYRIX, PCI_DEVICE_ID_CYRIX_5530_AUDIO, PCI_ANY_ID, | ||
64 | PCI_ANY_ID, 0, 0}, | ||
65 | {0,} | ||
66 | }; | ||
67 | |||
68 | MODULE_DEVICE_TABLE(pci, snd_cs5530_ids); | ||
69 | |||
70 | static int snd_cs5530_free(struct snd_cs5530 *chip) | ||
71 | { | ||
72 | pci_release_regions(chip->pci); | ||
73 | pci_disable_device(chip->pci); | ||
74 | kfree(chip); | ||
75 | return 0; | ||
76 | } | ||
77 | |||
78 | static int snd_cs5530_dev_free(struct snd_device *device) | ||
79 | { | ||
80 | struct snd_cs5530 *chip = device->device_data; | ||
81 | return snd_cs5530_free(chip); | ||
82 | } | ||
83 | |||
84 | static void __devexit snd_cs5530_remove(struct pci_dev *pci) | ||
85 | { | ||
86 | snd_card_free(pci_get_drvdata(pci)); | ||
87 | pci_set_drvdata(pci, NULL); | ||
88 | } | ||
89 | |||
90 | static u8 __devinit snd_cs5530_mixer_read(unsigned long io, u8 reg) | ||
91 | { | ||
92 | outb(reg, io + 4); | ||
93 | udelay(20); | ||
94 | reg = inb(io + 5); | ||
95 | udelay(20); | ||
96 | return reg; | ||
97 | } | ||
98 | |||
99 | static int __devinit snd_cs5530_create(struct snd_card *card, | ||
100 | struct pci_dev *pci, | ||
101 | struct snd_cs5530 **rchip) | ||
102 | { | ||
103 | struct snd_cs5530 *chip; | ||
104 | unsigned long sb_base; | ||
105 | u8 irq, dma8, dma16 = 0; | ||
106 | u16 map; | ||
107 | void __iomem *mem; | ||
108 | int err; | ||
109 | |||
110 | static struct snd_device_ops ops = { | ||
111 | .dev_free = snd_cs5530_dev_free, | ||
112 | }; | ||
113 | *rchip = NULL; | ||
114 | |||
115 | err = pci_enable_device(pci); | ||
116 | if (err < 0) | ||
117 | return err; | ||
118 | |||
119 | chip = kzalloc(sizeof(*chip), GFP_KERNEL); | ||
120 | if (chip == NULL) { | ||
121 | pci_disable_device(pci); | ||
122 | return -ENOMEM; | ||
123 | } | ||
124 | |||
125 | chip->card = card; | ||
126 | chip->pci = pci; | ||
127 | |||
128 | err = pci_request_regions(pci, "CS5530"); | ||
129 | if (err < 0) { | ||
130 | kfree(chip); | ||
131 | pci_disable_device(pci); | ||
132 | return err; | ||
133 | } | ||
134 | chip->pci_base = pci_resource_start(pci, 0); | ||
135 | |||
136 | mem = ioremap_nocache(chip->pci_base, pci_resource_len(pci, 0)); | ||
137 | if (mem == NULL) { | ||
138 | kfree(chip); | ||
139 | pci_disable_device(pci); | ||
140 | return -EBUSY; | ||
141 | } | ||
142 | |||
143 | map = readw(mem + 0x18); | ||
144 | iounmap(mem); | ||
145 | |||
146 | /* Map bits | ||
147 | 0:1 * 0x20 + 0x200 = sb base | ||
148 | 2 sb enable | ||
149 | 3 adlib enable | ||
150 | 5 MPU enable 0x330 | ||
151 | 6 MPU enable 0x300 | ||
152 | |||
153 | The other bits may be used internally so must be masked */ | ||
154 | |||
155 | sb_base = 0x220 + 0x20 * (map & 3); | ||
156 | |||
157 | if (map & (1<<2)) | ||
158 | printk(KERN_INFO "CS5530: XpressAudio at 0x%lx\n", sb_base); | ||
159 | else { | ||
160 | printk(KERN_ERR "Could not find XpressAudio!\n"); | ||
161 | snd_cs5530_free(chip); | ||
162 | return -ENODEV; | ||
163 | } | ||
164 | |||
165 | if (map & (1<<5)) | ||
166 | printk(KERN_INFO "CS5530: MPU at 0x300\n"); | ||
167 | else if (map & (1<<6)) | ||
168 | printk(KERN_INFO "CS5530: MPU at 0x330\n"); | ||
169 | |||
170 | irq = snd_cs5530_mixer_read(sb_base, 0x80) & 0x0F; | ||
171 | dma8 = snd_cs5530_mixer_read(sb_base, 0x81); | ||
172 | |||
173 | if (dma8 & 0x20) | ||
174 | dma16 = 5; | ||
175 | else if (dma8 & 0x40) | ||
176 | dma16 = 6; | ||
177 | else if (dma8 & 0x80) | ||
178 | dma16 = 7; | ||
179 | else { | ||
180 | printk(KERN_ERR "CS5530: No 16bit DMA enabled\n"); | ||
181 | snd_cs5530_free(chip); | ||
182 | return -ENODEV; | ||
183 | } | ||
184 | |||
185 | if (dma8 & 0x01) | ||
186 | dma8 = 0; | ||
187 | else if (dma8 & 02) | ||
188 | dma8 = 1; | ||
189 | else if (dma8 & 0x08) | ||
190 | dma8 = 3; | ||
191 | else { | ||
192 | printk(KERN_ERR "CS5530: No 8bit DMA enabled\n"); | ||
193 | snd_cs5530_free(chip); | ||
194 | return -ENODEV; | ||
195 | } | ||
196 | |||
197 | if (irq & 1) | ||
198 | irq = 9; | ||
199 | else if (irq & 2) | ||
200 | irq = 5; | ||
201 | else if (irq & 4) | ||
202 | irq = 7; | ||
203 | else if (irq & 8) | ||
204 | irq = 10; | ||
205 | else { | ||
206 | printk(KERN_ERR "CS5530: SoundBlaster IRQ not set\n"); | ||
207 | snd_cs5530_free(chip); | ||
208 | return -ENODEV; | ||
209 | } | ||
210 | |||
211 | printk(KERN_INFO "CS5530: IRQ: %d DMA8: %d DMA16: %d\n", irq, dma8, | ||
212 | dma16); | ||
213 | |||
214 | err = snd_sbdsp_create(card, sb_base, irq, snd_sb16dsp_interrupt, dma8, | ||
215 | dma16, SB_HW_CS5530, &chip->sb); | ||
216 | if (err < 0) { | ||
217 | printk(KERN_ERR "CS5530: Could not create SoundBlaster\n"); | ||
218 | snd_cs5530_free(chip); | ||
219 | return err; | ||
220 | } | ||
221 | |||
222 | err = snd_sb16dsp_pcm(chip->sb, 0, &chip->sb->pcm); | ||
223 | if (err < 0) { | ||
224 | printk(KERN_ERR "CS5530: Could not create PCM\n"); | ||
225 | snd_cs5530_free(chip); | ||
226 | return err; | ||
227 | } | ||
228 | |||
229 | err = snd_sbmixer_new(chip->sb); | ||
230 | if (err < 0) { | ||
231 | printk(KERN_ERR "CS5530: Could not create Mixer\n"); | ||
232 | snd_cs5530_free(chip); | ||
233 | return err; | ||
234 | } | ||
235 | |||
236 | err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); | ||
237 | if (err < 0) { | ||
238 | snd_cs5530_free(chip); | ||
239 | return err; | ||
240 | } | ||
241 | |||
242 | snd_card_set_dev(card, &pci->dev); | ||
243 | *rchip = chip; | ||
244 | return 0; | ||
245 | } | ||
246 | |||
247 | static int __devinit snd_cs5530_probe(struct pci_dev *pci, | ||
248 | const struct pci_device_id *pci_id) | ||
249 | { | ||
250 | static int dev; | ||
251 | struct snd_card *card; | ||
252 | struct snd_cs5530 *chip = NULL; | ||
253 | int err; | ||
254 | |||
255 | if (dev >= SNDRV_CARDS) | ||
256 | return -ENODEV; | ||
257 | if (!enable[dev]) { | ||
258 | dev++; | ||
259 | return -ENOENT; | ||
260 | } | ||
261 | |||
262 | card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); | ||
263 | |||
264 | if (card == NULL) | ||
265 | return -ENOMEM; | ||
266 | |||
267 | err = snd_cs5530_create(card, pci, &chip); | ||
268 | if (err < 0) { | ||
269 | snd_card_free(card); | ||
270 | return err; | ||
271 | } | ||
272 | |||
273 | strcpy(card->driver, "CS5530"); | ||
274 | strcpy(card->shortname, "CS5530 Audio"); | ||
275 | sprintf(card->longname, "%s at 0x%lx", card->shortname, chip->pci_base); | ||
276 | |||
277 | err = snd_card_register(card); | ||
278 | if (err < 0) { | ||
279 | snd_card_free(card); | ||
280 | return err; | ||
281 | } | ||
282 | pci_set_drvdata(pci, card); | ||
283 | dev++; | ||
284 | return 0; | ||
285 | } | ||
286 | |||
287 | static struct pci_driver driver = { | ||
288 | .name = "CS5530_Audio", | ||
289 | .id_table = snd_cs5530_ids, | ||
290 | .probe = snd_cs5530_probe, | ||
291 | .remove = __devexit_p(snd_cs5530_remove), | ||
292 | }; | ||
293 | |||
294 | static int __init alsa_card_cs5530_init(void) | ||
295 | { | ||
296 | return pci_register_driver(&driver); | ||
297 | } | ||
298 | |||
299 | static void __exit alsa_card_cs5530_exit(void) | ||
300 | { | ||
301 | pci_unregister_driver(&driver); | ||
302 | } | ||
303 | |||
304 | module_init(alsa_card_cs5530_init) | ||
305 | module_exit(alsa_card_cs5530_exit) | ||
306 | |||