diff options
author | Krzysztof Helt <krzysztof.h1@wp.pl> | 2007-09-11 15:53:05 -0400 |
---|---|---|
committer | Jaroslav Kysela <perex@perex.cz> | 2007-10-16 10:50:21 -0400 |
commit | e307258d5b27e3f9cde56df4038c89470710e2e4 (patch) | |
tree | d8610808966833d40a49c322712eed45c7559b9c /sound | |
parent | e8a7f136f5edb6ae83b14faaa0da2a3c4558f431 (diff) |
[ALSA] Gallant SC-6000 driver
This is port of the Gallant SC-6000 driver from the OSS aedsp16 driver.
This card was also sold as AudioExcel DSP 16 and Zoltrix AV302 (Audio
Plus True 16).
Signed-off-by: Krzysztof Helt <krzysztof.h1@wp.pl>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@suse.cz>
Diffstat (limited to 'sound')
-rw-r--r-- | sound/isa/Kconfig | 13 | ||||
-rw-r--r-- | sound/isa/Makefile | 2 | ||||
-rw-r--r-- | sound/isa/sc6000.c | 654 |
3 files changed, 669 insertions, 0 deletions
diff --git a/sound/isa/Kconfig b/sound/isa/Kconfig index 6b6aa2c3b85c..08a26d8bce13 100644 --- a/sound/isa/Kconfig +++ b/sound/isa/Kconfig | |||
@@ -191,6 +191,19 @@ config SND_ES18XX | |||
191 | To compile this driver as a module, choose M here: the module | 191 | To compile this driver as a module, choose M here: the module |
192 | will be called snd-es18xx. | 192 | will be called snd-es18xx. |
193 | 193 | ||
194 | config SND_SC6000 | ||
195 | tristate "Gallant SC-6000, Audio Excel DSP 16" | ||
196 | depends on SND | ||
197 | select SND_AD1848_LIB | ||
198 | select SND_OPL3_LIB | ||
199 | select SND_MPU401_UART | ||
200 | help | ||
201 | Say Y here to include support for Gallant SC-6000 card and clones: | ||
202 | Audio Excel DSP 16 and Zoltrix AV302. | ||
203 | |||
204 | To compile this driver as a module, choose M here: the module | ||
205 | will be called snd-sc6000. | ||
206 | |||
194 | config SND_GUS_SYNTH | 207 | config SND_GUS_SYNTH |
195 | tristate | 208 | tristate |
196 | 209 | ||
diff --git a/sound/isa/Makefile b/sound/isa/Makefile index bb317ccc170f..5378d981f6d7 100644 --- a/sound/isa/Makefile +++ b/sound/isa/Makefile | |||
@@ -10,6 +10,7 @@ snd-cmi8330-objs := cmi8330.o | |||
10 | snd-dt019x-objs := dt019x.o | 10 | snd-dt019x-objs := dt019x.o |
11 | snd-es18xx-objs := es18xx.o | 11 | snd-es18xx-objs := es18xx.o |
12 | snd-opl3sa2-objs := opl3sa2.o | 12 | snd-opl3sa2-objs := opl3sa2.o |
13 | snd-sc6000-objs := sc6000.o | ||
13 | snd-sgalaxy-objs := sgalaxy.o | 14 | snd-sgalaxy-objs := sgalaxy.o |
14 | snd-sscape-objs := sscape.o | 15 | snd-sscape-objs := sscape.o |
15 | 16 | ||
@@ -21,6 +22,7 @@ obj-$(CONFIG_SND_CMI8330) += snd-cmi8330.o | |||
21 | obj-$(CONFIG_SND_DT019X) += snd-dt019x.o | 22 | obj-$(CONFIG_SND_DT019X) += snd-dt019x.o |
22 | obj-$(CONFIG_SND_ES18XX) += snd-es18xx.o | 23 | obj-$(CONFIG_SND_ES18XX) += snd-es18xx.o |
23 | obj-$(CONFIG_SND_OPL3SA2) += snd-opl3sa2.o | 24 | obj-$(CONFIG_SND_OPL3SA2) += snd-opl3sa2.o |
25 | obj-$(CONFIG_SND_SC6000) += snd-sc6000.o | ||
24 | obj-$(CONFIG_SND_SGALAXY) += snd-sgalaxy.o | 26 | obj-$(CONFIG_SND_SGALAXY) += snd-sgalaxy.o |
25 | obj-$(CONFIG_SND_SSCAPE) += snd-sscape.o | 27 | obj-$(CONFIG_SND_SSCAPE) += snd-sscape.o |
26 | 28 | ||
diff --git a/sound/isa/sc6000.c b/sound/isa/sc6000.c new file mode 100644 index 000000000000..d4a4ccfc2fd7 --- /dev/null +++ b/sound/isa/sc6000.c | |||
@@ -0,0 +1,654 @@ | |||
1 | /* | ||
2 | * Driver for Gallant SC-6000 soundcard. This card is also known as | ||
3 | * Audio Excel DSP 16 or Zoltrix AV302. | ||
4 | * These cards use CompuMedia ASC-9308 chip + AD1848 codec. | ||
5 | * | ||
6 | * Copyright (C) 2007 Krzysztof Helt <krzysztof.h1@wp.pl> | ||
7 | * | ||
8 | * I don't have documentation for this card. I used the driver | ||
9 | * for OSS/Free included in the kernel source as reference. | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify | ||
12 | * it under the terms of the GNU General Public License as published by | ||
13 | * the Free Software Foundation; either version 2 of the License, or | ||
14 | * (at your option) any later version. | ||
15 | * | ||
16 | * This program is distributed in the hope that it will be useful, | ||
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
19 | * GNU General Public License for more details. | ||
20 | * | ||
21 | * You should have received a copy of the GNU General Public License | ||
22 | * along with this program; if not, write to the Free Software | ||
23 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
24 | */ | ||
25 | |||
26 | #include <sound/driver.h> | ||
27 | #include <linux/module.h> | ||
28 | #include <linux/isa.h> | ||
29 | #include <linux/io.h> | ||
30 | #include <asm/dma.h> | ||
31 | #include <sound/core.h> | ||
32 | #include <sound/ad1848.h> | ||
33 | #include <sound/opl3.h> | ||
34 | #include <sound/mpu401.h> | ||
35 | #include <sound/control.h> | ||
36 | #define SNDRV_LEGACY_FIND_FREE_IRQ | ||
37 | #define SNDRV_LEGACY_FIND_FREE_DMA | ||
38 | #include <sound/initval.h> | ||
39 | |||
40 | MODULE_AUTHOR("Krzysztof Helt"); | ||
41 | MODULE_DESCRIPTION("Gallant SC-6000"); | ||
42 | MODULE_LICENSE("GPL"); | ||
43 | MODULE_SUPPORTED_DEVICE("{{Gallant, SC-6000}," | ||
44 | "{AudioExcel, Audio Excel DSP 16}," | ||
45 | "{Zoltrix, AV302}}"); | ||
46 | |||
47 | static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ | ||
48 | static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ | ||
49 | static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ | ||
50 | static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220, 0x240 */ | ||
51 | static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5, 7, 9, 10, 11 */ | ||
52 | static long mss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x530, 0xe80 */ | ||
53 | static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; | ||
54 | /* 0x300, 0x310, 0x320, 0x330 */ | ||
55 | static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5, 7, 9, 10, 0 */ | ||
56 | static int dma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0, 1, 3 */ | ||
57 | |||
58 | module_param_array(index, int, NULL, 0444); | ||
59 | MODULE_PARM_DESC(index, "Index value for sc-6000 based soundcard."); | ||
60 | module_param_array(id, charp, NULL, 0444); | ||
61 | MODULE_PARM_DESC(id, "ID string for sc-6000 based soundcard."); | ||
62 | module_param_array(enable, bool, NULL, 0444); | ||
63 | MODULE_PARM_DESC(enable, "Enable sc-6000 based soundcard."); | ||
64 | module_param_array(port, long, NULL, 0444); | ||
65 | MODULE_PARM_DESC(port, "Port # for sc-6000 driver."); | ||
66 | module_param_array(mss_port, long, NULL, 0444); | ||
67 | MODULE_PARM_DESC(mss_port, "MSS Port # for sc-6000 driver."); | ||
68 | module_param_array(mpu_port, long, NULL, 0444); | ||
69 | MODULE_PARM_DESC(mpu_port, "MPU-401 port # for sc-6000 driver."); | ||
70 | module_param_array(irq, int, NULL, 0444); | ||
71 | MODULE_PARM_DESC(irq, "IRQ # for sc-6000 driver."); | ||
72 | module_param_array(mpu_irq, int, NULL, 0444); | ||
73 | MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for sc-6000 driver."); | ||
74 | module_param_array(dma, int, NULL, 0444); | ||
75 | MODULE_PARM_DESC(dma, "DMA # for sc-6000 driver."); | ||
76 | |||
77 | /* | ||
78 | * Commands of SC6000's DSP (SBPRO+special). | ||
79 | * Some of them are COMMAND_xx, in the future they may change. | ||
80 | */ | ||
81 | #define WRITE_MDIRQ_CFG 0x50 /* Set M&I&DRQ mask (the real config) */ | ||
82 | #define COMMAND_52 0x52 /* */ | ||
83 | #define READ_HARD_CFG 0x58 /* Read Hardware Config (I/O base etc) */ | ||
84 | #define COMMAND_5C 0x5c /* */ | ||
85 | #define COMMAND_60 0x60 /* */ | ||
86 | #define COMMAND_66 0x66 /* */ | ||
87 | #define COMMAND_6C 0x6c /* */ | ||
88 | #define COMMAND_6E 0x6e /* */ | ||
89 | #define COMMAND_88 0x88 /* Unknown command */ | ||
90 | #define DSP_INIT_MSS 0x8c /* Enable Microsoft Sound System mode */ | ||
91 | #define COMMAND_C5 0xc5 /* */ | ||
92 | #define GET_DSP_VERSION 0xe1 /* Get DSP Version */ | ||
93 | #define GET_DSP_COPYRIGHT 0xe3 /* Get DSP Copyright */ | ||
94 | |||
95 | /* | ||
96 | * Offsets of SC6000 DSP I/O ports. The offset is added to base I/O port | ||
97 | * to have the actual I/O port. | ||
98 | * Register permissions are: | ||
99 | * (wo) == Write Only | ||
100 | * (ro) == Read Only | ||
101 | * (w-) == Write | ||
102 | * (r-) == Read | ||
103 | */ | ||
104 | #define DSP_RESET 0x06 /* offset of DSP RESET (wo) */ | ||
105 | #define DSP_READ 0x0a /* offset of DSP READ (ro) */ | ||
106 | #define DSP_WRITE 0x0c /* offset of DSP WRITE (w-) */ | ||
107 | #define DSP_COMMAND 0x0c /* offset of DSP COMMAND (w-) */ | ||
108 | #define DSP_STATUS 0x0c /* offset of DSP STATUS (r-) */ | ||
109 | #define DSP_DATAVAIL 0x0e /* offset of DSP DATA AVAILABLE (ro) */ | ||
110 | |||
111 | #define PFX "sc6000: " | ||
112 | #define DRV_NAME "SC-6000" | ||
113 | |||
114 | /* hardware dependent functions */ | ||
115 | |||
116 | /* | ||
117 | * sc6000_irq_to_softcfg - Decode irq number into cfg code. | ||
118 | */ | ||
119 | static __devinit unsigned char sc6000_irq_to_softcfg(int irq) | ||
120 | { | ||
121 | unsigned char val = 0; | ||
122 | |||
123 | switch (irq) { | ||
124 | case 5: | ||
125 | val = 0x28; | ||
126 | break; | ||
127 | case 7: | ||
128 | val = 0x8; | ||
129 | break; | ||
130 | case 9: | ||
131 | val = 0x10; | ||
132 | break; | ||
133 | case 10: | ||
134 | val = 0x18; | ||
135 | break; | ||
136 | case 11: | ||
137 | val = 0x20; | ||
138 | break; | ||
139 | default: | ||
140 | break; | ||
141 | } | ||
142 | return val; | ||
143 | } | ||
144 | |||
145 | /* | ||
146 | * sc6000_dma_to_softcfg - Decode dma number into cfg code. | ||
147 | */ | ||
148 | static __devinit unsigned char sc6000_dma_to_softcfg(int dma) | ||
149 | { | ||
150 | unsigned char val = 0; | ||
151 | |||
152 | switch (dma) { | ||
153 | case 0: | ||
154 | val = 1; | ||
155 | break; | ||
156 | case 1: | ||
157 | val = 2; | ||
158 | break; | ||
159 | case 3: | ||
160 | val = 3; | ||
161 | break; | ||
162 | default: | ||
163 | break; | ||
164 | } | ||
165 | return val; | ||
166 | } | ||
167 | |||
168 | /* | ||
169 | * sc6000_mpu_irq_to_softcfg - Decode MPU-401 irq number into cfg code. | ||
170 | */ | ||
171 | static __devinit unsigned char sc6000_mpu_irq_to_softcfg(int mpu_irq) | ||
172 | { | ||
173 | unsigned char val = 0; | ||
174 | |||
175 | switch (mpu_irq) { | ||
176 | case 5: | ||
177 | val = 4; | ||
178 | break; | ||
179 | case 7: | ||
180 | val = 0x44; | ||
181 | break; | ||
182 | case 9: | ||
183 | val = 0x84; | ||
184 | break; | ||
185 | case 10: | ||
186 | val = 0xc4; | ||
187 | break; | ||
188 | default: | ||
189 | break; | ||
190 | } | ||
191 | return val; | ||
192 | } | ||
193 | |||
194 | static __devinit int sc6000_wait_data(char __iomem *vport) | ||
195 | { | ||
196 | int loop = 1000; | ||
197 | unsigned char val = 0; | ||
198 | |||
199 | do { | ||
200 | val = ioread8(vport + DSP_DATAVAIL); | ||
201 | if (val & 0x80) | ||
202 | return 0; | ||
203 | cpu_relax(); | ||
204 | } while (loop--); | ||
205 | |||
206 | return -EAGAIN; | ||
207 | } | ||
208 | |||
209 | static __devinit int sc6000_read(char __iomem *vport) | ||
210 | { | ||
211 | if (sc6000_wait_data(vport)) | ||
212 | return -EBUSY; | ||
213 | |||
214 | return ioread8(vport + DSP_READ); | ||
215 | |||
216 | } | ||
217 | |||
218 | static __devinit int sc6000_write(char __iomem *vport, int cmd) | ||
219 | { | ||
220 | unsigned char val; | ||
221 | int loop = 500000; | ||
222 | |||
223 | do { | ||
224 | val = ioread8(vport + DSP_STATUS); | ||
225 | /* | ||
226 | * DSP ready to receive data if bit 7 of val == 0 | ||
227 | */ | ||
228 | if (!(val & 0x80)) { | ||
229 | iowrite8(cmd, vport + DSP_COMMAND); | ||
230 | return 0; | ||
231 | } | ||
232 | cpu_relax(); | ||
233 | } while (loop--); | ||
234 | |||
235 | snd_printk(KERN_ERR "DSP Command (0x%x) timeout.\n", cmd); | ||
236 | |||
237 | return -EIO; | ||
238 | } | ||
239 | |||
240 | static int __devinit sc6000_dsp_get_answer(char __iomem *vport, int command, | ||
241 | char *data, int data_len) | ||
242 | { | ||
243 | int len = 0; | ||
244 | |||
245 | if (sc6000_write(vport, command)) { | ||
246 | snd_printk(KERN_ERR "CMD 0x%x: failed!\n", command); | ||
247 | return -EIO; | ||
248 | } | ||
249 | |||
250 | do { | ||
251 | int val = sc6000_read(vport); | ||
252 | |||
253 | if (val < 0) | ||
254 | break; | ||
255 | |||
256 | data[len++] = val; | ||
257 | |||
258 | } while (len < data_len); | ||
259 | |||
260 | /* | ||
261 | * If no more data available, return to the caller, no error if len>0. | ||
262 | * We have no other way to know when the string is finished. | ||
263 | */ | ||
264 | return len ? len : -EIO; | ||
265 | } | ||
266 | |||
267 | static int __devinit sc6000_dsp_reset(char __iomem *vport) | ||
268 | { | ||
269 | iowrite8(1, vport + DSP_RESET); | ||
270 | udelay(10); | ||
271 | iowrite8(0, vport + DSP_RESET); | ||
272 | udelay(20); | ||
273 | if (sc6000_read(vport) == 0xaa) | ||
274 | return 0; | ||
275 | return -ENODEV; | ||
276 | } | ||
277 | |||
278 | /* detection and initialization */ | ||
279 | static int __devinit sc6000_cfg_write(char __iomem *vport, | ||
280 | unsigned char softcfg) | ||
281 | { | ||
282 | |||
283 | if (sc6000_write(vport, WRITE_MDIRQ_CFG)) { | ||
284 | snd_printk(KERN_ERR "CMD 0x%x: failed!\n", WRITE_MDIRQ_CFG); | ||
285 | return -EIO; | ||
286 | } | ||
287 | if (sc6000_write(vport, softcfg)) { | ||
288 | snd_printk(KERN_ERR "sc6000_cfg_write: failed!\n"); | ||
289 | return -EIO; | ||
290 | } | ||
291 | return 0; | ||
292 | } | ||
293 | |||
294 | static int __devinit sc6000_setup_board(char __iomem *vport, int config) | ||
295 | { | ||
296 | int loop = 10; | ||
297 | |||
298 | do { | ||
299 | if (sc6000_write(vport, COMMAND_88)) { | ||
300 | snd_printk(KERN_ERR "CMD 0x%x: failed!\n", | ||
301 | COMMAND_88); | ||
302 | return -EIO; | ||
303 | } | ||
304 | } while ((sc6000_wait_data(vport) < 0) && loop--); | ||
305 | |||
306 | if (sc6000_read(vport) < 0) { | ||
307 | snd_printk(KERN_ERR "sc6000_read after CMD 0x%x: failed\n", | ||
308 | COMMAND_88); | ||
309 | return -EIO; | ||
310 | } | ||
311 | |||
312 | if (sc6000_cfg_write(vport, config)) | ||
313 | return -ENODEV; | ||
314 | |||
315 | return 0; | ||
316 | } | ||
317 | |||
318 | static int __devinit sc6000_init_mss(char __iomem *vport, int config, | ||
319 | char __iomem *vmss_port, int mss_config) | ||
320 | { | ||
321 | if (sc6000_write(vport, DSP_INIT_MSS)) { | ||
322 | snd_printk(KERN_ERR "sc6000_init_mss [0x%x]: failed!\n", | ||
323 | DSP_INIT_MSS); | ||
324 | return -EIO; | ||
325 | } | ||
326 | |||
327 | msleep(10); | ||
328 | |||
329 | if (sc6000_cfg_write(vport, config)) | ||
330 | return -EIO; | ||
331 | |||
332 | iowrite8(mss_config, vmss_port); | ||
333 | |||
334 | return 0; | ||
335 | } | ||
336 | |||
337 | static int __devinit sc6000_init_board(char __iomem *vport, int irq, int dma, | ||
338 | char __iomem *vmss_port, int mpu_irq) | ||
339 | { | ||
340 | char answer[15]; | ||
341 | char version[2]; | ||
342 | int mss_config = sc6000_irq_to_softcfg(irq) | | ||
343 | sc6000_dma_to_softcfg(dma); | ||
344 | int config = mss_config | | ||
345 | sc6000_mpu_irq_to_softcfg(mpu_irq); | ||
346 | int err; | ||
347 | |||
348 | err = sc6000_dsp_reset(vport); | ||
349 | if (err < 0) { | ||
350 | snd_printk(KERN_ERR "sc6000_dsp_reset: failed!\n"); | ||
351 | return err; | ||
352 | } | ||
353 | |||
354 | err = sc6000_dsp_get_answer(vport, GET_DSP_COPYRIGHT, answer, 15); | ||
355 | if (err <= 0) { | ||
356 | snd_printk(KERN_ERR "sc6000_dsp_copyright: failed!\n"); | ||
357 | return -ENODEV; | ||
358 | } | ||
359 | /* | ||
360 | * My SC-6000 card return "SC-6000" in DSPCopyright, so | ||
361 | * if we have something different, we have to be warned. | ||
362 | * Mine returns "SC-6000A " - KH | ||
363 | */ | ||
364 | if (strncmp("SC-6000", answer, 7)) | ||
365 | snd_printk(KERN_WARNING "Warning: non SC-6000 audio card!\n"); | ||
366 | |||
367 | if (sc6000_dsp_get_answer(vport, GET_DSP_VERSION, version, 2) < 2) { | ||
368 | snd_printk(KERN_ERR "sc6000_dsp_version: failed!\n"); | ||
369 | return -ENODEV; | ||
370 | } | ||
371 | printk(KERN_INFO PFX "Detected model: %s, DSP version %d.%d\n", | ||
372 | answer, version[0], version[1]); | ||
373 | |||
374 | /* | ||
375 | * 0x0A == (IRQ 7, DMA 1, MIRQ 0) | ||
376 | */ | ||
377 | err = sc6000_cfg_write(vport, 0x0a); | ||
378 | if (err < 0) { | ||
379 | snd_printk(KERN_ERR "sc6000_cfg_write: failed!\n"); | ||
380 | return -EFAULT; | ||
381 | } | ||
382 | |||
383 | err = sc6000_setup_board(vport, config); | ||
384 | if (err < 0) { | ||
385 | snd_printk(KERN_ERR "sc6000_setup_board: failed!\n"); | ||
386 | return -ENODEV; | ||
387 | } | ||
388 | |||
389 | err = sc6000_init_mss(vport, config, vmss_port, mss_config); | ||
390 | if (err < 0) { | ||
391 | snd_printk(KERN_ERR "Can not initialize" | ||
392 | "Microsoft Sound System mode.\n"); | ||
393 | return -ENODEV; | ||
394 | } | ||
395 | |||
396 | return 0; | ||
397 | } | ||
398 | |||
399 | static int __devinit snd_sc6000_mixer(struct snd_ad1848 *chip) | ||
400 | { | ||
401 | struct snd_card *card = chip->card; | ||
402 | struct snd_ctl_elem_id id1, id2; | ||
403 | int err; | ||
404 | |||
405 | memset(&id1, 0, sizeof(id1)); | ||
406 | memset(&id2, 0, sizeof(id2)); | ||
407 | id1.iface = SNDRV_CTL_ELEM_IFACE_MIXER; | ||
408 | id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; | ||
409 | /* reassign AUX0 to FM */ | ||
410 | strcpy(id1.name, "Aux Playback Switch"); | ||
411 | strcpy(id2.name, "FM Playback Switch"); | ||
412 | err = snd_ctl_rename_id(card, &id1, &id2); | ||
413 | if (err < 0) | ||
414 | return err; | ||
415 | strcpy(id1.name, "Aux Playback Volume"); | ||
416 | strcpy(id2.name, "FM Playback Volume"); | ||
417 | err = snd_ctl_rename_id(card, &id1, &id2); | ||
418 | if (err < 0) | ||
419 | return err; | ||
420 | /* reassign AUX1 to CD */ | ||
421 | strcpy(id1.name, "Aux Playback Switch"); id1.index = 1; | ||
422 | strcpy(id2.name, "CD Playback Switch"); | ||
423 | err = snd_ctl_rename_id(card, &id1, &id2); | ||
424 | if (err < 0) | ||
425 | return err; | ||
426 | strcpy(id1.name, "Aux Playback Volume"); | ||
427 | strcpy(id2.name, "CD Playback Volume"); | ||
428 | err = snd_ctl_rename_id(card, &id1, &id2); | ||
429 | if (err < 0) | ||
430 | return err; | ||
431 | return 0; | ||
432 | } | ||
433 | |||
434 | static int __devinit snd_sc6000_match(struct device *devptr, unsigned int dev) | ||
435 | { | ||
436 | if (!enable[dev]) | ||
437 | return 0; | ||
438 | if (port[dev] == SNDRV_AUTO_PORT) { | ||
439 | printk(KERN_ERR PFX "specify IO port\n"); | ||
440 | return 0; | ||
441 | } | ||
442 | if (mss_port[dev] == SNDRV_AUTO_PORT) { | ||
443 | printk(KERN_ERR PFX "specify MSS port\n"); | ||
444 | return 0; | ||
445 | } | ||
446 | if (port[dev] != 0x220 && port[dev] != 0x240) { | ||
447 | printk(KERN_ERR PFX "Port must be 0x220 or 0x240\n"); | ||
448 | return 0; | ||
449 | } | ||
450 | if (mss_port[dev] != 0x530 && mss_port[dev] != 0xe80) { | ||
451 | printk(KERN_ERR PFX "MSS port must be 0x530 or 0xe80\n"); | ||
452 | return 0; | ||
453 | } | ||
454 | if (irq[dev] != SNDRV_AUTO_IRQ && !sc6000_irq_to_softcfg(irq[dev])) { | ||
455 | printk(KERN_ERR PFX "invalid IRQ %d\n", irq[dev]); | ||
456 | return 0; | ||
457 | } | ||
458 | if (dma[dev] != SNDRV_AUTO_DMA && !sc6000_dma_to_softcfg(dma[dev])) { | ||
459 | printk(KERN_ERR PFX "invalid DMA %d\n", dma[dev]); | ||
460 | return 0; | ||
461 | } | ||
462 | if (mpu_port[dev] != SNDRV_AUTO_PORT && | ||
463 | (mpu_port[dev] & ~0x30L) != 0x300) { | ||
464 | printk(KERN_ERR PFX "invalid MPU-401 port %lx\n", | ||
465 | mpu_port[dev]); | ||
466 | return 0; | ||
467 | } | ||
468 | if (mpu_port[dev] != SNDRV_AUTO_PORT && | ||
469 | mpu_irq[dev] != SNDRV_AUTO_IRQ && mpu_irq[dev] != 0 && | ||
470 | !sc6000_mpu_irq_to_softcfg(mpu_irq[dev])) { | ||
471 | printk(KERN_ERR PFX "invalid MPU-401 IRQ %d\n", mpu_irq[dev]); | ||
472 | return 0; | ||
473 | } | ||
474 | return 1; | ||
475 | } | ||
476 | |||
477 | static int __devinit snd_sc6000_probe(struct device *devptr, unsigned int dev) | ||
478 | { | ||
479 | static int possible_irqs[] = { 5, 7, 9, 10, 11, -1 }; | ||
480 | static int possible_dmas[] = { 1, 3, 0, -1 }; | ||
481 | int err; | ||
482 | int xirq = irq[dev]; | ||
483 | int xdma = dma[dev]; | ||
484 | struct snd_card *card; | ||
485 | struct snd_ad1848 *chip; | ||
486 | struct snd_opl3 *opl3; | ||
487 | char __iomem *vport; | ||
488 | char __iomem *vmss_port; | ||
489 | |||
490 | |||
491 | card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); | ||
492 | if (!card) | ||
493 | return -ENOMEM; | ||
494 | |||
495 | if (xirq == SNDRV_AUTO_IRQ) { | ||
496 | xirq = snd_legacy_find_free_irq(possible_irqs); | ||
497 | if (xirq < 0) { | ||
498 | snd_printk(KERN_ERR PFX "unable to find a free IRQ\n"); | ||
499 | err = -EBUSY; | ||
500 | goto err_exit; | ||
501 | } | ||
502 | } | ||
503 | |||
504 | if (xdma == SNDRV_AUTO_DMA) { | ||
505 | xdma = snd_legacy_find_free_dma(possible_dmas); | ||
506 | if (xdma < 0) { | ||
507 | snd_printk(KERN_ERR PFX "unable to find a free DMA\n"); | ||
508 | err = -EBUSY; | ||
509 | goto err_exit; | ||
510 | } | ||
511 | } | ||
512 | |||
513 | if (!request_region(port[dev], 0x10, DRV_NAME)) { | ||
514 | snd_printk(KERN_ERR PFX | ||
515 | "I/O port region is already in use.\n"); | ||
516 | err = -EBUSY; | ||
517 | goto err_exit; | ||
518 | } | ||
519 | vport = devm_ioport_map(devptr, port[dev], 0x10); | ||
520 | if (!vport) { | ||
521 | snd_printk(KERN_ERR PFX | ||
522 | "I/O port cannot be iomaped.\n"); | ||
523 | err = -EBUSY; | ||
524 | goto err_unmap1; | ||
525 | } | ||
526 | |||
527 | /* to make it marked as used */ | ||
528 | if (!request_region(mss_port[dev], 4, DRV_NAME)) { | ||
529 | snd_printk(KERN_ERR PFX | ||
530 | "SC-6000 port I/O port region is already in use.\n"); | ||
531 | err = -EBUSY; | ||
532 | goto err_unmap2; | ||
533 | } | ||
534 | vmss_port = devm_ioport_map(devptr, mss_port[dev], 4); | ||
535 | if (!vport) { | ||
536 | snd_printk(KERN_ERR PFX | ||
537 | "MSS port I/O cannot be iomaped.\n"); | ||
538 | err = -EBUSY; | ||
539 | goto err_unmap2; | ||
540 | } | ||
541 | |||
542 | snd_printd("Initializing BASE[0x%lx] IRQ[%d] DMA[%d] MIRQ[%d]\n", | ||
543 | port[dev], xirq, xdma, | ||
544 | mpu_irq[dev] == SNDRV_AUTO_IRQ ? 0 : mpu_irq[dev]); | ||
545 | |||
546 | err = sc6000_init_board(vport, xirq, xdma, vmss_port, mpu_irq[dev]); | ||
547 | if (err < 0) | ||
548 | goto err_unmap2; | ||
549 | |||
550 | err = snd_ad1848_create(card, mss_port[dev] + 4, xirq, xdma, | ||
551 | AD1848_HW_DETECT, &chip); | ||
552 | if (err < 0) | ||
553 | goto err_unmap2; | ||
554 | card->private_data = chip; | ||
555 | |||
556 | err = snd_ad1848_pcm(chip, 0, NULL); | ||
557 | if (err < 0) { | ||
558 | snd_printk(KERN_ERR PFX | ||
559 | "error creating new ad1848 PCM device\n"); | ||
560 | goto err_unmap2; | ||
561 | } | ||
562 | err = snd_ad1848_mixer(chip); | ||
563 | if (err < 0) { | ||
564 | snd_printk(KERN_ERR PFX "error creating new ad1848 mixer\n"); | ||
565 | goto err_unmap2; | ||
566 | } | ||
567 | err = snd_sc6000_mixer(chip); | ||
568 | if (err < 0) { | ||
569 | snd_printk(KERN_ERR PFX "the mixer rewrite failed\n"); | ||
570 | goto err_unmap2; | ||
571 | } | ||
572 | if (snd_opl3_create(card, | ||
573 | 0x388, 0x388 + 2, | ||
574 | OPL3_HW_AUTO, 0, &opl3) < 0) { | ||
575 | snd_printk(KERN_ERR PFX "no OPL device at 0x%x-0x%x ?\n", | ||
576 | 0x388, 0x388 + 2); | ||
577 | } else { | ||
578 | err = snd_opl3_timer_new(opl3, 0, 1); | ||
579 | if (err < 0) | ||
580 | goto err_unmap2; | ||
581 | |||
582 | err = snd_opl3_hwdep_new(opl3, 0, 1, NULL); | ||
583 | if (err < 0) | ||
584 | goto err_unmap2; | ||
585 | } | ||
586 | |||
587 | if (mpu_port[dev] != SNDRV_AUTO_PORT) { | ||
588 | if (mpu_irq[dev] == SNDRV_AUTO_IRQ) | ||
589 | mpu_irq[dev] = -1; | ||
590 | if (snd_mpu401_uart_new(card, 0, | ||
591 | MPU401_HW_MPU401, | ||
592 | mpu_port[dev], 0, | ||
593 | mpu_irq[dev], IRQF_DISABLED, | ||
594 | NULL) < 0) | ||
595 | snd_printk(KERN_ERR "no MPU-401 device at 0x%lx ?\n", | ||
596 | mpu_port[dev]); | ||
597 | } | ||
598 | |||
599 | strcpy(card->driver, DRV_NAME); | ||
600 | strcpy(card->shortname, "SC-6000"); | ||
601 | sprintf(card->longname, "Gallant SC-6000 at 0x%lx, irq %d, dma %d", | ||
602 | mss_port[dev], xirq, xdma); | ||
603 | |||
604 | snd_card_set_dev(card, devptr); | ||
605 | |||
606 | err = snd_card_register(card); | ||
607 | if (err < 0) | ||
608 | goto err_unmap2; | ||
609 | |||
610 | dev_set_drvdata(devptr, card); | ||
611 | return 0; | ||
612 | |||
613 | err_unmap2: | ||
614 | release_region(mss_port[dev], 4); | ||
615 | err_unmap1: | ||
616 | release_region(port[dev], 0x10); | ||
617 | err_exit: | ||
618 | snd_card_free(card); | ||
619 | return err; | ||
620 | } | ||
621 | |||
622 | static int __devexit snd_sc6000_remove(struct device *devptr, unsigned int dev) | ||
623 | { | ||
624 | release_region(port[dev], 0x10); | ||
625 | release_region(mss_port[dev], 4); | ||
626 | |||
627 | snd_card_free(dev_get_drvdata(devptr)); | ||
628 | dev_set_drvdata(devptr, NULL); | ||
629 | return 0; | ||
630 | } | ||
631 | |||
632 | static struct isa_driver snd_sc6000_driver = { | ||
633 | .match = snd_sc6000_match, | ||
634 | .probe = snd_sc6000_probe, | ||
635 | .remove = __devexit_p(snd_sc6000_remove), | ||
636 | /* FIXME: suspend/resume */ | ||
637 | .driver = { | ||
638 | .name = DRV_NAME, | ||
639 | }, | ||
640 | }; | ||
641 | |||
642 | |||
643 | static int __init alsa_card_sc6000_init(void) | ||
644 | { | ||
645 | return isa_register_driver(&snd_sc6000_driver, SNDRV_CARDS); | ||
646 | } | ||
647 | |||
648 | static void __exit alsa_card_sc6000_exit(void) | ||
649 | { | ||
650 | isa_unregister_driver(&snd_sc6000_driver); | ||
651 | } | ||
652 | |||
653 | module_init(alsa_card_sc6000_init) | ||
654 | module_exit(alsa_card_sc6000_exit) | ||