Commit | Line | Data |
---|---|---|
1a59d1b8 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
e307258d KH |
2 | /* |
3 | * Driver for Gallant SC-6000 soundcard. This card is also known as | |
4 | * Audio Excel DSP 16 or Zoltrix AV302. | |
5 | * These cards use CompuMedia ASC-9308 chip + AD1848 codec. | |
c2828661 KH |
6 | * SC-6600 and SC-7000 cards are also supported. They are based on |
7 | * CompuMedia ASC-9408 chip and CS4231 codec. | |
e307258d KH |
8 | * |
9 | * Copyright (C) 2007 Krzysztof Helt <krzysztof.h1@wp.pl> | |
10 | * | |
11 | * I don't have documentation for this card. I used the driver | |
12 | * for OSS/Free included in the kernel source as reference. | |
e307258d KH |
13 | */ |
14 | ||
e307258d | 15 | #include <linux/module.h> |
3a5bdee5 | 16 | #include <linux/delay.h> |
e307258d KH |
17 | #include <linux/isa.h> |
18 | #include <linux/io.h> | |
19 | #include <asm/dma.h> | |
20 | #include <sound/core.h> | |
760fc6b8 | 21 | #include <sound/wss.h> |
e307258d KH |
22 | #include <sound/opl3.h> |
23 | #include <sound/mpu401.h> | |
24 | #include <sound/control.h> | |
25 | #define SNDRV_LEGACY_FIND_FREE_IRQ | |
26 | #define SNDRV_LEGACY_FIND_FREE_DMA | |
27 | #include <sound/initval.h> | |
28 | ||
29 | MODULE_AUTHOR("Krzysztof Helt"); | |
30 | MODULE_DESCRIPTION("Gallant SC-6000"); | |
31 | MODULE_LICENSE("GPL"); | |
e307258d KH |
32 | |
33 | static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ | |
34 | static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ | |
a67ff6a5 | 35 | static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ |
e307258d KH |
36 | static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220, 0x240 */ |
37 | static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5, 7, 9, 10, 11 */ | |
38 | static long mss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x530, 0xe80 */ | |
39 | static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; | |
40 | /* 0x300, 0x310, 0x320, 0x330 */ | |
41 | static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5, 7, 9, 10, 0 */ | |
42 | static int dma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0, 1, 3 */ | |
b0ec3a30 | 43 | static bool joystick[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = false }; |
e307258d KH |
44 | |
45 | module_param_array(index, int, NULL, 0444); | |
46 | MODULE_PARM_DESC(index, "Index value for sc-6000 based soundcard."); | |
47 | module_param_array(id, charp, NULL, 0444); | |
48 | MODULE_PARM_DESC(id, "ID string for sc-6000 based soundcard."); | |
49 | module_param_array(enable, bool, NULL, 0444); | |
50 | MODULE_PARM_DESC(enable, "Enable sc-6000 based soundcard."); | |
e992ef57 | 51 | module_param_hw_array(port, long, ioport, NULL, 0444); |
e307258d | 52 | MODULE_PARM_DESC(port, "Port # for sc-6000 driver."); |
e992ef57 | 53 | module_param_hw_array(mss_port, long, ioport, NULL, 0444); |
e307258d | 54 | MODULE_PARM_DESC(mss_port, "MSS Port # for sc-6000 driver."); |
e992ef57 | 55 | module_param_hw_array(mpu_port, long, ioport, NULL, 0444); |
e307258d | 56 | MODULE_PARM_DESC(mpu_port, "MPU-401 port # for sc-6000 driver."); |
e992ef57 | 57 | module_param_hw_array(irq, int, irq, NULL, 0444); |
e307258d | 58 | MODULE_PARM_DESC(irq, "IRQ # for sc-6000 driver."); |
e992ef57 | 59 | module_param_hw_array(mpu_irq, int, irq, NULL, 0444); |
e307258d | 60 | MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for sc-6000 driver."); |
e992ef57 | 61 | module_param_hw_array(dma, int, dma, NULL, 0444); |
e307258d | 62 | MODULE_PARM_DESC(dma, "DMA # for sc-6000 driver."); |
b0ec3a30 KH |
63 | module_param_array(joystick, bool, NULL, 0444); |
64 | MODULE_PARM_DESC(joystick, "Enable gameport."); | |
e307258d KH |
65 | |
66 | /* | |
67 | * Commands of SC6000's DSP (SBPRO+special). | |
68 | * Some of them are COMMAND_xx, in the future they may change. | |
69 | */ | |
70 | #define WRITE_MDIRQ_CFG 0x50 /* Set M&I&DRQ mask (the real config) */ | |
71 | #define COMMAND_52 0x52 /* */ | |
72 | #define READ_HARD_CFG 0x58 /* Read Hardware Config (I/O base etc) */ | |
73 | #define COMMAND_5C 0x5c /* */ | |
74 | #define COMMAND_60 0x60 /* */ | |
75 | #define COMMAND_66 0x66 /* */ | |
76 | #define COMMAND_6C 0x6c /* */ | |
77 | #define COMMAND_6E 0x6e /* */ | |
78 | #define COMMAND_88 0x88 /* Unknown command */ | |
79 | #define DSP_INIT_MSS 0x8c /* Enable Microsoft Sound System mode */ | |
80 | #define COMMAND_C5 0xc5 /* */ | |
81 | #define GET_DSP_VERSION 0xe1 /* Get DSP Version */ | |
82 | #define GET_DSP_COPYRIGHT 0xe3 /* Get DSP Copyright */ | |
83 | ||
84 | /* | |
85 | * Offsets of SC6000 DSP I/O ports. The offset is added to base I/O port | |
86 | * to have the actual I/O port. | |
87 | * Register permissions are: | |
88 | * (wo) == Write Only | |
89 | * (ro) == Read Only | |
90 | * (w-) == Write | |
91 | * (r-) == Read | |
92 | */ | |
93 | #define DSP_RESET 0x06 /* offset of DSP RESET (wo) */ | |
94 | #define DSP_READ 0x0a /* offset of DSP READ (ro) */ | |
95 | #define DSP_WRITE 0x0c /* offset of DSP WRITE (w-) */ | |
96 | #define DSP_COMMAND 0x0c /* offset of DSP COMMAND (w-) */ | |
97 | #define DSP_STATUS 0x0c /* offset of DSP STATUS (r-) */ | |
98 | #define DSP_DATAVAIL 0x0e /* offset of DSP DATA AVAILABLE (ro) */ | |
99 | ||
100 | #define PFX "sc6000: " | |
101 | #define DRV_NAME "SC-6000" | |
102 | ||
103 | /* hardware dependent functions */ | |
104 | ||
105 | /* | |
106 | * sc6000_irq_to_softcfg - Decode irq number into cfg code. | |
107 | */ | |
1bff292e | 108 | static unsigned char sc6000_irq_to_softcfg(int irq) |
e307258d KH |
109 | { |
110 | unsigned char val = 0; | |
111 | ||
112 | switch (irq) { | |
113 | case 5: | |
114 | val = 0x28; | |
115 | break; | |
116 | case 7: | |
117 | val = 0x8; | |
118 | break; | |
119 | case 9: | |
120 | val = 0x10; | |
121 | break; | |
122 | case 10: | |
123 | val = 0x18; | |
124 | break; | |
125 | case 11: | |
126 | val = 0x20; | |
127 | break; | |
128 | default: | |
129 | break; | |
130 | } | |
131 | return val; | |
132 | } | |
133 | ||
134 | /* | |
135 | * sc6000_dma_to_softcfg - Decode dma number into cfg code. | |
136 | */ | |
1bff292e | 137 | static unsigned char sc6000_dma_to_softcfg(int dma) |
e307258d KH |
138 | { |
139 | unsigned char val = 0; | |
140 | ||
141 | switch (dma) { | |
142 | case 0: | |
143 | val = 1; | |
144 | break; | |
145 | case 1: | |
146 | val = 2; | |
147 | break; | |
148 | case 3: | |
149 | val = 3; | |
150 | break; | |
151 | default: | |
152 | break; | |
153 | } | |
154 | return val; | |
155 | } | |
156 | ||
157 | /* | |
158 | * sc6000_mpu_irq_to_softcfg - Decode MPU-401 irq number into cfg code. | |
159 | */ | |
1bff292e | 160 | static unsigned char sc6000_mpu_irq_to_softcfg(int mpu_irq) |
e307258d KH |
161 | { |
162 | unsigned char val = 0; | |
163 | ||
164 | switch (mpu_irq) { | |
165 | case 5: | |
166 | val = 4; | |
167 | break; | |
168 | case 7: | |
169 | val = 0x44; | |
170 | break; | |
171 | case 9: | |
172 | val = 0x84; | |
173 | break; | |
174 | case 10: | |
175 | val = 0xc4; | |
176 | break; | |
177 | default: | |
178 | break; | |
179 | } | |
180 | return val; | |
181 | } | |
182 | ||
c2828661 | 183 | static int sc6000_wait_data(char __iomem *vport) |
e307258d KH |
184 | { |
185 | int loop = 1000; | |
186 | unsigned char val = 0; | |
187 | ||
188 | do { | |
189 | val = ioread8(vport + DSP_DATAVAIL); | |
190 | if (val & 0x80) | |
191 | return 0; | |
192 | cpu_relax(); | |
193 | } while (loop--); | |
194 | ||
195 | return -EAGAIN; | |
196 | } | |
197 | ||
c2828661 | 198 | static int sc6000_read(char __iomem *vport) |
e307258d KH |
199 | { |
200 | if (sc6000_wait_data(vport)) | |
201 | return -EBUSY; | |
202 | ||
203 | return ioread8(vport + DSP_READ); | |
204 | ||
205 | } | |
206 | ||
c2828661 | 207 | static int sc6000_write(char __iomem *vport, int cmd) |
e307258d KH |
208 | { |
209 | unsigned char val; | |
210 | int loop = 500000; | |
211 | ||
212 | do { | |
213 | val = ioread8(vport + DSP_STATUS); | |
214 | /* | |
215 | * DSP ready to receive data if bit 7 of val == 0 | |
216 | */ | |
217 | if (!(val & 0x80)) { | |
218 | iowrite8(cmd, vport + DSP_COMMAND); | |
219 | return 0; | |
220 | } | |
221 | cpu_relax(); | |
222 | } while (loop--); | |
223 | ||
224 | snd_printk(KERN_ERR "DSP Command (0x%x) timeout.\n", cmd); | |
225 | ||
226 | return -EIO; | |
227 | } | |
228 | ||
1bff292e BP |
229 | static int sc6000_dsp_get_answer(char __iomem *vport, int command, |
230 | char *data, int data_len) | |
e307258d KH |
231 | { |
232 | int len = 0; | |
233 | ||
234 | if (sc6000_write(vport, command)) { | |
235 | snd_printk(KERN_ERR "CMD 0x%x: failed!\n", command); | |
236 | return -EIO; | |
237 | } | |
238 | ||
239 | do { | |
240 | int val = sc6000_read(vport); | |
241 | ||
242 | if (val < 0) | |
243 | break; | |
244 | ||
245 | data[len++] = val; | |
246 | ||
247 | } while (len < data_len); | |
248 | ||
249 | /* | |
250 | * If no more data available, return to the caller, no error if len>0. | |
251 | * We have no other way to know when the string is finished. | |
252 | */ | |
253 | return len ? len : -EIO; | |
254 | } | |
255 | ||
1bff292e | 256 | static int sc6000_dsp_reset(char __iomem *vport) |
e307258d KH |
257 | { |
258 | iowrite8(1, vport + DSP_RESET); | |
259 | udelay(10); | |
260 | iowrite8(0, vport + DSP_RESET); | |
261 | udelay(20); | |
262 | if (sc6000_read(vport) == 0xaa) | |
263 | return 0; | |
264 | return -ENODEV; | |
265 | } | |
266 | ||
267 | /* detection and initialization */ | |
1bff292e | 268 | static int sc6000_hw_cfg_write(char __iomem *vport, const int *cfg) |
c2828661 KH |
269 | { |
270 | if (sc6000_write(vport, COMMAND_6C) < 0) { | |
271 | snd_printk(KERN_WARNING "CMD 0x%x: failed!\n", COMMAND_6C); | |
272 | return -EIO; | |
273 | } | |
274 | if (sc6000_write(vport, COMMAND_5C) < 0) { | |
275 | snd_printk(KERN_ERR "CMD 0x%x: failed!\n", COMMAND_5C); | |
276 | return -EIO; | |
277 | } | |
278 | if (sc6000_write(vport, cfg[0]) < 0) { | |
279 | snd_printk(KERN_ERR "DATA 0x%x: failed!\n", cfg[0]); | |
280 | return -EIO; | |
281 | } | |
282 | if (sc6000_write(vport, cfg[1]) < 0) { | |
283 | snd_printk(KERN_ERR "DATA 0x%x: failed!\n", cfg[1]); | |
284 | return -EIO; | |
285 | } | |
286 | if (sc6000_write(vport, COMMAND_C5) < 0) { | |
287 | snd_printk(KERN_ERR "CMD 0x%x: failed!\n", COMMAND_C5); | |
288 | return -EIO; | |
289 | } | |
290 | ||
291 | return 0; | |
292 | } | |
293 | ||
294 | static int sc6000_cfg_write(char __iomem *vport, unsigned char softcfg) | |
e307258d KH |
295 | { |
296 | ||
297 | if (sc6000_write(vport, WRITE_MDIRQ_CFG)) { | |
298 | snd_printk(KERN_ERR "CMD 0x%x: failed!\n", WRITE_MDIRQ_CFG); | |
299 | return -EIO; | |
300 | } | |
301 | if (sc6000_write(vport, softcfg)) { | |
302 | snd_printk(KERN_ERR "sc6000_cfg_write: failed!\n"); | |
303 | return -EIO; | |
304 | } | |
305 | return 0; | |
306 | } | |
307 | ||
c2828661 | 308 | static int sc6000_setup_board(char __iomem *vport, int config) |
e307258d KH |
309 | { |
310 | int loop = 10; | |
311 | ||
312 | do { | |
313 | if (sc6000_write(vport, COMMAND_88)) { | |
314 | snd_printk(KERN_ERR "CMD 0x%x: failed!\n", | |
315 | COMMAND_88); | |
316 | return -EIO; | |
317 | } | |
318 | } while ((sc6000_wait_data(vport) < 0) && loop--); | |
319 | ||
320 | if (sc6000_read(vport) < 0) { | |
321 | snd_printk(KERN_ERR "sc6000_read after CMD 0x%x: failed\n", | |
322 | COMMAND_88); | |
323 | return -EIO; | |
324 | } | |
325 | ||
326 | if (sc6000_cfg_write(vport, config)) | |
327 | return -ENODEV; | |
328 | ||
329 | return 0; | |
330 | } | |
331 | ||
1bff292e BP |
332 | static int sc6000_init_mss(char __iomem *vport, int config, |
333 | char __iomem *vmss_port, int mss_config) | |
e307258d KH |
334 | { |
335 | if (sc6000_write(vport, DSP_INIT_MSS)) { | |
336 | snd_printk(KERN_ERR "sc6000_init_mss [0x%x]: failed!\n", | |
337 | DSP_INIT_MSS); | |
338 | return -EIO; | |
339 | } | |
340 | ||
341 | msleep(10); | |
342 | ||
343 | if (sc6000_cfg_write(vport, config)) | |
344 | return -EIO; | |
345 | ||
346 | iowrite8(mss_config, vmss_port); | |
347 | ||
348 | return 0; | |
349 | } | |
350 | ||
1bff292e BP |
351 | static void sc6000_hw_cfg_encode(char __iomem *vport, int *cfg, |
352 | long xport, long xmpu, | |
353 | long xmss_port, int joystick) | |
c2828661 KH |
354 | { |
355 | cfg[0] = 0; | |
356 | cfg[1] = 0; | |
357 | if (xport == 0x240) | |
358 | cfg[0] |= 1; | |
359 | if (xmpu != SNDRV_AUTO_PORT) { | |
360 | cfg[0] |= (xmpu & 0x30) >> 2; | |
361 | cfg[1] |= 0x20; | |
362 | } | |
363 | if (xmss_port == 0xe80) | |
364 | cfg[0] |= 0x10; | |
365 | cfg[0] |= 0x40; /* always set */ | |
b0ec3a30 KH |
366 | if (!joystick) |
367 | cfg[0] |= 0x02; | |
c2828661 KH |
368 | cfg[1] |= 0x80; /* enable WSS system */ |
369 | cfg[1] &= ~0x40; /* disable IDE */ | |
370 | snd_printd("hw cfg %x, %x\n", cfg[0], cfg[1]); | |
371 | } | |
372 | ||
1bff292e BP |
373 | static int sc6000_init_board(char __iomem *vport, |
374 | char __iomem *vmss_port, int dev) | |
e307258d KH |
375 | { |
376 | char answer[15]; | |
377 | char version[2]; | |
c2828661 KH |
378 | int mss_config = sc6000_irq_to_softcfg(irq[dev]) | |
379 | sc6000_dma_to_softcfg(dma[dev]); | |
e307258d | 380 | int config = mss_config | |
c2828661 | 381 | sc6000_mpu_irq_to_softcfg(mpu_irq[dev]); |
e307258d | 382 | int err; |
c2828661 | 383 | int old = 0; |
e307258d KH |
384 | |
385 | err = sc6000_dsp_reset(vport); | |
386 | if (err < 0) { | |
387 | snd_printk(KERN_ERR "sc6000_dsp_reset: failed!\n"); | |
388 | return err; | |
389 | } | |
390 | ||
1cf0bc7e | 391 | memset(answer, 0, sizeof(answer)); |
e307258d KH |
392 | err = sc6000_dsp_get_answer(vport, GET_DSP_COPYRIGHT, answer, 15); |
393 | if (err <= 0) { | |
394 | snd_printk(KERN_ERR "sc6000_dsp_copyright: failed!\n"); | |
395 | return -ENODEV; | |
396 | } | |
397 | /* | |
398 | * My SC-6000 card return "SC-6000" in DSPCopyright, so | |
399 | * if we have something different, we have to be warned. | |
e307258d KH |
400 | */ |
401 | if (strncmp("SC-6000", answer, 7)) | |
402 | snd_printk(KERN_WARNING "Warning: non SC-6000 audio card!\n"); | |
403 | ||
404 | if (sc6000_dsp_get_answer(vport, GET_DSP_VERSION, version, 2) < 2) { | |
405 | snd_printk(KERN_ERR "sc6000_dsp_version: failed!\n"); | |
406 | return -ENODEV; | |
407 | } | |
408 | printk(KERN_INFO PFX "Detected model: %s, DSP version %d.%d\n", | |
409 | answer, version[0], version[1]); | |
410 | ||
c2828661 | 411 | /* set configuration */ |
0cfcdeda KH |
412 | sc6000_write(vport, COMMAND_5C); |
413 | if (sc6000_read(vport) < 0) | |
414 | old = 1; | |
415 | ||
416 | if (!old) { | |
417 | int cfg[2]; | |
418 | sc6000_hw_cfg_encode(vport, &cfg[0], port[dev], mpu_port[dev], | |
b0ec3a30 | 419 | mss_port[dev], joystick[dev]); |
0cfcdeda KH |
420 | if (sc6000_hw_cfg_write(vport, cfg) < 0) { |
421 | snd_printk(KERN_ERR "sc6000_hw_cfg_write: failed!\n"); | |
422 | return -EIO; | |
423 | } | |
c2828661 KH |
424 | } |
425 | err = sc6000_setup_board(vport, config); | |
e307258d | 426 | if (err < 0) { |
c2828661 KH |
427 | snd_printk(KERN_ERR "sc6000_setup_board: failed!\n"); |
428 | return -ENODEV; | |
429 | } | |
430 | ||
431 | sc6000_dsp_reset(vport); | |
c2828661 KH |
432 | |
433 | if (!old) { | |
434 | sc6000_write(vport, COMMAND_60); | |
435 | sc6000_write(vport, 0x02); | |
436 | sc6000_dsp_reset(vport); | |
e307258d KH |
437 | } |
438 | ||
439 | err = sc6000_setup_board(vport, config); | |
440 | if (err < 0) { | |
441 | snd_printk(KERN_ERR "sc6000_setup_board: failed!\n"); | |
442 | return -ENODEV; | |
443 | } | |
e307258d KH |
444 | err = sc6000_init_mss(vport, config, vmss_port, mss_config); |
445 | if (err < 0) { | |
c2828661 | 446 | snd_printk(KERN_ERR "Cannot initialize " |
e307258d KH |
447 | "Microsoft Sound System mode.\n"); |
448 | return -ENODEV; | |
449 | } | |
450 | ||
451 | return 0; | |
452 | } | |
453 | ||
1bff292e | 454 | static int snd_sc6000_mixer(struct snd_wss *chip) |
e307258d KH |
455 | { |
456 | struct snd_card *card = chip->card; | |
457 | struct snd_ctl_elem_id id1, id2; | |
458 | int err; | |
459 | ||
460 | memset(&id1, 0, sizeof(id1)); | |
461 | memset(&id2, 0, sizeof(id2)); | |
462 | id1.iface = SNDRV_CTL_ELEM_IFACE_MIXER; | |
463 | id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; | |
464 | /* reassign AUX0 to FM */ | |
465 | strcpy(id1.name, "Aux Playback Switch"); | |
466 | strcpy(id2.name, "FM Playback Switch"); | |
467 | err = snd_ctl_rename_id(card, &id1, &id2); | |
468 | if (err < 0) | |
469 | return err; | |
470 | strcpy(id1.name, "Aux Playback Volume"); | |
471 | strcpy(id2.name, "FM Playback Volume"); | |
472 | err = snd_ctl_rename_id(card, &id1, &id2); | |
473 | if (err < 0) | |
474 | return err; | |
475 | /* reassign AUX1 to CD */ | |
476 | strcpy(id1.name, "Aux Playback Switch"); id1.index = 1; | |
477 | strcpy(id2.name, "CD Playback Switch"); | |
478 | err = snd_ctl_rename_id(card, &id1, &id2); | |
479 | if (err < 0) | |
480 | return err; | |
481 | strcpy(id1.name, "Aux Playback Volume"); | |
482 | strcpy(id2.name, "CD Playback Volume"); | |
483 | err = snd_ctl_rename_id(card, &id1, &id2); | |
484 | if (err < 0) | |
485 | return err; | |
486 | return 0; | |
487 | } | |
488 | ||
1bff292e | 489 | static int snd_sc6000_match(struct device *devptr, unsigned int dev) |
e307258d KH |
490 | { |
491 | if (!enable[dev]) | |
492 | return 0; | |
493 | if (port[dev] == SNDRV_AUTO_PORT) { | |
494 | printk(KERN_ERR PFX "specify IO port\n"); | |
495 | return 0; | |
496 | } | |
497 | if (mss_port[dev] == SNDRV_AUTO_PORT) { | |
498 | printk(KERN_ERR PFX "specify MSS port\n"); | |
499 | return 0; | |
500 | } | |
501 | if (port[dev] != 0x220 && port[dev] != 0x240) { | |
502 | printk(KERN_ERR PFX "Port must be 0x220 or 0x240\n"); | |
503 | return 0; | |
504 | } | |
505 | if (mss_port[dev] != 0x530 && mss_port[dev] != 0xe80) { | |
506 | printk(KERN_ERR PFX "MSS port must be 0x530 or 0xe80\n"); | |
507 | return 0; | |
508 | } | |
509 | if (irq[dev] != SNDRV_AUTO_IRQ && !sc6000_irq_to_softcfg(irq[dev])) { | |
510 | printk(KERN_ERR PFX "invalid IRQ %d\n", irq[dev]); | |
511 | return 0; | |
512 | } | |
513 | if (dma[dev] != SNDRV_AUTO_DMA && !sc6000_dma_to_softcfg(dma[dev])) { | |
514 | printk(KERN_ERR PFX "invalid DMA %d\n", dma[dev]); | |
515 | return 0; | |
516 | } | |
517 | if (mpu_port[dev] != SNDRV_AUTO_PORT && | |
518 | (mpu_port[dev] & ~0x30L) != 0x300) { | |
519 | printk(KERN_ERR PFX "invalid MPU-401 port %lx\n", | |
520 | mpu_port[dev]); | |
521 | return 0; | |
522 | } | |
523 | if (mpu_port[dev] != SNDRV_AUTO_PORT && | |
524 | mpu_irq[dev] != SNDRV_AUTO_IRQ && mpu_irq[dev] != 0 && | |
525 | !sc6000_mpu_irq_to_softcfg(mpu_irq[dev])) { | |
526 | printk(KERN_ERR PFX "invalid MPU-401 IRQ %d\n", mpu_irq[dev]); | |
527 | return 0; | |
528 | } | |
529 | return 1; | |
530 | } | |
531 | ||
111601ff TI |
532 | static void snd_sc6000_free(struct snd_card *card) |
533 | { | |
f976e8a9 | 534 | char __iomem *vport = (char __force __iomem *)card->private_data; |
111601ff | 535 | |
9b7843d1 TI |
536 | if (vport) |
537 | sc6000_setup_board(vport, 0); | |
111601ff TI |
538 | } |
539 | ||
d7245807 | 540 | static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) |
e307258d | 541 | { |
8fc17916 TI |
542 | static const int possible_irqs[] = { 5, 7, 9, 10, 11, -1 }; |
543 | static const int possible_dmas[] = { 1, 3, 0, -1 }; | |
e307258d KH |
544 | int err; |
545 | int xirq = irq[dev]; | |
546 | int xdma = dma[dev]; | |
547 | struct snd_card *card; | |
241b3ee7 | 548 | struct snd_wss *chip; |
e307258d | 549 | struct snd_opl3 *opl3; |
9b7843d1 | 550 | char __iomem *vport; |
e307258d KH |
551 | char __iomem *vmss_port; |
552 | ||
111601ff | 553 | err = snd_devm_card_new(devptr, index[dev], id[dev], THIS_MODULE, |
9b7843d1 | 554 | 0, &card); |
c95eadd2 TI |
555 | if (err < 0) |
556 | return err; | |
e307258d KH |
557 | |
558 | if (xirq == SNDRV_AUTO_IRQ) { | |
559 | xirq = snd_legacy_find_free_irq(possible_irqs); | |
560 | if (xirq < 0) { | |
561 | snd_printk(KERN_ERR PFX "unable to find a free IRQ\n"); | |
111601ff | 562 | return -EBUSY; |
e307258d KH |
563 | } |
564 | } | |
565 | ||
566 | if (xdma == SNDRV_AUTO_DMA) { | |
567 | xdma = snd_legacy_find_free_dma(possible_dmas); | |
568 | if (xdma < 0) { | |
569 | snd_printk(KERN_ERR PFX "unable to find a free DMA\n"); | |
111601ff | 570 | return -EBUSY; |
e307258d KH |
571 | } |
572 | } | |
573 | ||
111601ff | 574 | if (!devm_request_region(devptr, port[dev], 0x10, DRV_NAME)) { |
e307258d KH |
575 | snd_printk(KERN_ERR PFX |
576 | "I/O port region is already in use.\n"); | |
111601ff | 577 | return -EBUSY; |
e307258d | 578 | } |
9b7843d1 TI |
579 | vport = devm_ioport_map(devptr, port[dev], 0x10); |
580 | if (!vport) { | |
e307258d | 581 | snd_printk(KERN_ERR PFX |
6a300dc9 | 582 | "I/O port cannot be iomapped.\n"); |
111601ff | 583 | return -EBUSY; |
e307258d | 584 | } |
f976e8a9 | 585 | card->private_data = (void __force *)vport; |
e307258d KH |
586 | |
587 | /* to make it marked as used */ | |
111601ff | 588 | if (!devm_request_region(devptr, mss_port[dev], 4, DRV_NAME)) { |
e307258d KH |
589 | snd_printk(KERN_ERR PFX |
590 | "SC-6000 port I/O port region is already in use.\n"); | |
111601ff | 591 | return -EBUSY; |
e307258d KH |
592 | } |
593 | vmss_port = devm_ioport_map(devptr, mss_port[dev], 4); | |
c2828661 | 594 | if (!vmss_port) { |
e307258d | 595 | snd_printk(KERN_ERR PFX |
6a300dc9 | 596 | "MSS port I/O cannot be iomapped.\n"); |
111601ff | 597 | return -EBUSY; |
e307258d KH |
598 | } |
599 | ||
600 | snd_printd("Initializing BASE[0x%lx] IRQ[%d] DMA[%d] MIRQ[%d]\n", | |
601 | port[dev], xirq, xdma, | |
602 | mpu_irq[dev] == SNDRV_AUTO_IRQ ? 0 : mpu_irq[dev]); | |
603 | ||
9b7843d1 | 604 | err = sc6000_init_board(vport, vmss_port, dev); |
e307258d | 605 | if (err < 0) |
111601ff TI |
606 | return err; |
607 | card->private_free = snd_sc6000_free; | |
e307258d | 608 | |
760fc6b8 KH |
609 | err = snd_wss_create(card, mss_port[dev] + 4, -1, xirq, xdma, -1, |
610 | WSS_HW_DETECT, 0, &chip); | |
e307258d | 611 | if (err < 0) |
111601ff | 612 | return err; |
e307258d | 613 | |
fa60c065 | 614 | err = snd_wss_pcm(chip, 0); |
e307258d KH |
615 | if (err < 0) { |
616 | snd_printk(KERN_ERR PFX | |
ead893c0 | 617 | "error creating new WSS PCM device\n"); |
111601ff | 618 | return err; |
e307258d | 619 | } |
5664daa1 | 620 | err = snd_wss_mixer(chip); |
e307258d | 621 | if (err < 0) { |
5664daa1 | 622 | snd_printk(KERN_ERR PFX "error creating new WSS mixer\n"); |
111601ff | 623 | return err; |
e307258d KH |
624 | } |
625 | err = snd_sc6000_mixer(chip); | |
626 | if (err < 0) { | |
627 | snd_printk(KERN_ERR PFX "the mixer rewrite failed\n"); | |
111601ff | 628 | return err; |
e307258d KH |
629 | } |
630 | if (snd_opl3_create(card, | |
631 | 0x388, 0x388 + 2, | |
632 | OPL3_HW_AUTO, 0, &opl3) < 0) { | |
633 | snd_printk(KERN_ERR PFX "no OPL device at 0x%x-0x%x ?\n", | |
634 | 0x388, 0x388 + 2); | |
635 | } else { | |
e307258d KH |
636 | err = snd_opl3_hwdep_new(opl3, 0, 1, NULL); |
637 | if (err < 0) | |
111601ff | 638 | return err; |
e307258d KH |
639 | } |
640 | ||
641 | if (mpu_port[dev] != SNDRV_AUTO_PORT) { | |
642 | if (mpu_irq[dev] == SNDRV_AUTO_IRQ) | |
643 | mpu_irq[dev] = -1; | |
644 | if (snd_mpu401_uart_new(card, 0, | |
645 | MPU401_HW_MPU401, | |
646 | mpu_port[dev], 0, | |
dba8b469 | 647 | mpu_irq[dev], NULL) < 0) |
e307258d KH |
648 | snd_printk(KERN_ERR "no MPU-401 device at 0x%lx ?\n", |
649 | mpu_port[dev]); | |
650 | } | |
651 | ||
652 | strcpy(card->driver, DRV_NAME); | |
653 | strcpy(card->shortname, "SC-6000"); | |
654 | sprintf(card->longname, "Gallant SC-6000 at 0x%lx, irq %d, dma %d", | |
655 | mss_port[dev], xirq, xdma); | |
656 | ||
e307258d KH |
657 | err = snd_card_register(card); |
658 | if (err < 0) | |
111601ff | 659 | return err; |
e307258d KH |
660 | |
661 | dev_set_drvdata(devptr, card); | |
662 | return 0; | |
e307258d KH |
663 | } |
664 | ||
d7245807 TI |
665 | static int snd_sc6000_probe(struct device *devptr, unsigned int dev) |
666 | { | |
667 | return snd_card_free_on_error(devptr, __snd_sc6000_probe(devptr, dev)); | |
668 | } | |
669 | ||
e307258d KH |
670 | static struct isa_driver snd_sc6000_driver = { |
671 | .match = snd_sc6000_match, | |
672 | .probe = snd_sc6000_probe, | |
e307258d KH |
673 | /* FIXME: suspend/resume */ |
674 | .driver = { | |
675 | .name = DRV_NAME, | |
676 | }, | |
677 | }; | |
678 | ||
679 | ||
af486dd8 | 680 | module_isa_driver(snd_sc6000_driver, SNDRV_CARDS); |