ALSA: hda: HP laptop quirk
[linux-2.6] / sound / pci / cs5530.c
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 <linux/delay.h>
40 #include <linux/moduleparam.h>
41 #include <linux/pci.h>
42 #include <sound/core.h>
43 #include <sound/sb.h>
44 #include <sound/initval.h>
45
46 MODULE_AUTHOR("Ash Willis");
47 MODULE_DESCRIPTION("CS5530 Audio");
48 MODULE_LICENSE("GPL");
49
50 static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
51 static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
52 static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
53
54 struct snd_cs5530 {
55         struct snd_card *card;
56         struct pci_dev *pci;
57         struct snd_sb *sb;
58         unsigned long pci_base;
59 };
60
61 static struct pci_device_id snd_cs5530_ids[] = {
62         {PCI_VENDOR_ID_CYRIX, PCI_DEVICE_ID_CYRIX_5530_AUDIO, PCI_ANY_ID,
63                                                         PCI_ANY_ID, 0, 0},
64         {0,}
65 };
66
67 MODULE_DEVICE_TABLE(pci, snd_cs5530_ids);
68
69 static int snd_cs5530_free(struct snd_cs5530 *chip)
70 {
71         pci_release_regions(chip->pci);
72         pci_disable_device(chip->pci);
73         kfree(chip);
74         return 0;
75 }
76
77 static int snd_cs5530_dev_free(struct snd_device *device)
78 {
79         struct snd_cs5530 *chip = device->device_data;
80         return snd_cs5530_free(chip);
81 }
82
83 static void __devexit snd_cs5530_remove(struct pci_dev *pci)
84 {
85         snd_card_free(pci_get_drvdata(pci));
86         pci_set_drvdata(pci, NULL);
87 }
88
89 static u8 __devinit snd_cs5530_mixer_read(unsigned long io, u8 reg)
90 {
91         outb(reg, io + 4);
92         udelay(20);
93         reg = inb(io + 5);
94         udelay(20);
95         return reg;
96 }
97
98 static int __devinit snd_cs5530_create(struct snd_card *card,
99                                        struct pci_dev *pci,
100                                        struct snd_cs5530 **rchip)
101 {
102         struct snd_cs5530 *chip;
103         unsigned long sb_base;
104         u8 irq, dma8, dma16 = 0;
105         u16 map;
106         void __iomem *mem;
107         int err;
108
109         static struct snd_device_ops ops = {
110                 .dev_free = snd_cs5530_dev_free,
111         };
112         *rchip = NULL;
113
114         err = pci_enable_device(pci);
115         if (err < 0)
116                 return err;
117
118         chip = kzalloc(sizeof(*chip), GFP_KERNEL);
119         if (chip == NULL) {
120                 pci_disable_device(pci);
121                 return -ENOMEM;
122         }
123
124         chip->card = card;
125         chip->pci = pci;
126
127         err = pci_request_regions(pci, "CS5530");
128         if (err < 0) {
129                 kfree(chip); 
130                 pci_disable_device(pci);
131                 return err;
132         }
133         chip->pci_base = pci_resource_start(pci, 0);
134
135         mem = ioremap_nocache(chip->pci_base, pci_resource_len(pci, 0));
136         if (mem == NULL) {
137                 kfree(chip);
138                 pci_disable_device(pci);
139                 return -EBUSY;
140         }
141
142         map = readw(mem + 0x18);
143         iounmap(mem);
144
145         /* Map bits
146                 0:1     * 0x20 + 0x200 = sb base
147                 2       sb enable
148                 3       adlib enable
149                 5       MPU enable 0x330
150                 6       MPU enable 0x300
151
152            The other bits may be used internally so must be masked */
153
154         sb_base = 0x220 + 0x20 * (map & 3);
155
156         if (map & (1<<2))
157                 printk(KERN_INFO "CS5530: XpressAudio at 0x%lx\n", sb_base);
158         else {
159                 printk(KERN_ERR "Could not find XpressAudio!\n");
160                 snd_cs5530_free(chip);
161                 return -ENODEV;
162         }
163
164         if (map & (1<<5))
165                 printk(KERN_INFO "CS5530: MPU at 0x300\n");
166         else if (map & (1<<6))
167                 printk(KERN_INFO "CS5530: MPU at 0x330\n");
168
169         irq = snd_cs5530_mixer_read(sb_base, 0x80) & 0x0F;
170         dma8 = snd_cs5530_mixer_read(sb_base, 0x81);
171
172         if (dma8 & 0x20)
173                 dma16 = 5;
174         else if (dma8 & 0x40)
175                 dma16 = 6;
176         else if (dma8 & 0x80)
177                 dma16 = 7;
178         else {
179                 printk(KERN_ERR "CS5530: No 16bit DMA enabled\n");
180                 snd_cs5530_free(chip);
181                 return -ENODEV;
182         }
183
184         if (dma8 & 0x01)
185                 dma8 = 0;
186         else if (dma8 & 02)
187                 dma8 = 1;
188         else if (dma8 & 0x08)
189                 dma8 = 3;
190         else {
191                 printk(KERN_ERR "CS5530: No 8bit DMA enabled\n");
192                 snd_cs5530_free(chip);
193                 return -ENODEV;
194         }
195
196         if (irq & 1)
197                 irq = 9;
198         else if (irq & 2)
199                 irq = 5;
200         else if (irq & 4)
201                 irq = 7;
202         else if (irq & 8)
203                 irq = 10;
204         else {
205                 printk(KERN_ERR "CS5530: SoundBlaster IRQ not set\n");
206                 snd_cs5530_free(chip);
207                 return -ENODEV;
208         }
209
210         printk(KERN_INFO "CS5530: IRQ: %d DMA8: %d DMA16: %d\n", irq, dma8, 
211                                                                         dma16);
212
213         err = snd_sbdsp_create(card, sb_base, irq, snd_sb16dsp_interrupt, dma8,
214                                                 dma16, SB_HW_CS5530, &chip->sb);
215         if (err < 0) {
216                 printk(KERN_ERR "CS5530: Could not create SoundBlaster\n");
217                 snd_cs5530_free(chip);
218                 return err;
219         }
220
221         err = snd_sb16dsp_pcm(chip->sb, 0, &chip->sb->pcm);
222         if (err < 0) {
223                 printk(KERN_ERR "CS5530: Could not create PCM\n");
224                 snd_cs5530_free(chip);
225                 return err;
226         }
227
228         err = snd_sbmixer_new(chip->sb);
229         if (err < 0) {
230                 printk(KERN_ERR "CS5530: Could not create Mixer\n");
231                 snd_cs5530_free(chip);
232                 return err;
233         }
234
235         err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
236         if (err < 0) {
237                 snd_cs5530_free(chip);
238                 return err;
239         }
240
241         snd_card_set_dev(card, &pci->dev);
242         *rchip = chip;
243         return 0;
244 }
245
246 static int __devinit snd_cs5530_probe(struct pci_dev *pci,
247                                         const struct pci_device_id *pci_id)
248 {
249         static int dev;
250         struct snd_card *card;
251         struct snd_cs5530 *chip = NULL;
252         int err;
253
254         if (dev >= SNDRV_CARDS)
255                 return -ENODEV;
256         if (!enable[dev]) {
257                 dev++;
258                 return -ENOENT;
259         }
260
261         card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
262
263         if (card == NULL)
264                 return -ENOMEM;
265
266         err = snd_cs5530_create(card, pci, &chip);
267         if (err < 0) {
268                 snd_card_free(card);
269                 return err;
270         }
271
272         strcpy(card->driver, "CS5530");
273         strcpy(card->shortname, "CS5530 Audio");
274         sprintf(card->longname, "%s at 0x%lx", card->shortname, chip->pci_base);
275
276         err = snd_card_register(card);
277         if (err < 0) {
278                 snd_card_free(card);
279                 return err;
280         }
281         pci_set_drvdata(pci, card);
282         dev++;
283         return 0;
284 }
285
286 static struct pci_driver driver = {
287         .name = "CS5530_Audio",
288         .id_table = snd_cs5530_ids,
289         .probe = snd_cs5530_probe,
290         .remove = __devexit_p(snd_cs5530_remove),
291 };
292
293 static int __init alsa_card_cs5530_init(void)
294 {
295         return pci_register_driver(&driver);
296 }
297
298 static void __exit alsa_card_cs5530_exit(void)
299 {
300         pci_unregister_driver(&driver);
301 }
302
303 module_init(alsa_card_cs5530_init)
304 module_exit(alsa_card_cs5530_exit)
305