1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Aztech AZT1605/AZT2316 Driver |
4 | * Copyright (C) 2007,2010 Rene Herman |
5 | */ |
6 | |
7 | #include <linux/kernel.h> |
8 | #include <linux/module.h> |
9 | #include <linux/isa.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/io.h> |
12 | #include <asm/processor.h> |
13 | #include <sound/core.h> |
14 | #include <sound/initval.h> |
15 | #include <sound/wss.h> |
16 | #include <sound/mpu401.h> |
17 | #include <sound/opl3.h> |
18 | |
19 | MODULE_DESCRIPTION(CRD_NAME); |
20 | MODULE_AUTHOR("Rene Herman" ); |
21 | MODULE_LICENSE("GPL" ); |
22 | |
23 | static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; |
24 | static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; |
25 | static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; |
26 | |
27 | module_param_array(index, int, NULL, 0444); |
28 | MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard." ); |
29 | module_param_array(id, charp, NULL, 0444); |
30 | MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard." ); |
31 | module_param_array(enable, bool, NULL, 0444); |
32 | MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard." ); |
33 | |
34 | static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; |
35 | static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; |
36 | static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; |
37 | static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; |
38 | static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; |
39 | static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; |
40 | static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; |
41 | static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; |
42 | |
43 | module_param_hw_array(port, long, ioport, NULL, 0444); |
44 | MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver." ); |
45 | module_param_hw_array(wss_port, long, ioport, NULL, 0444); |
46 | MODULE_PARM_DESC(wss_port, "WSS port # for " CRD_NAME " driver." ); |
47 | module_param_hw_array(mpu_port, long, ioport, NULL, 0444); |
48 | MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver." ); |
49 | module_param_hw_array(fm_port, long, ioport, NULL, 0444); |
50 | MODULE_PARM_DESC(fm_port, "FM port # for " CRD_NAME " driver." ); |
51 | module_param_hw_array(irq, int, irq, NULL, 0444); |
52 | MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver." ); |
53 | module_param_hw_array(mpu_irq, int, irq, NULL, 0444); |
54 | MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver." ); |
55 | module_param_hw_array(dma1, int, dma, NULL, 0444); |
56 | MODULE_PARM_DESC(dma1, "Playback DMA # for " CRD_NAME " driver." ); |
57 | module_param_hw_array(dma2, int, dma, NULL, 0444); |
58 | MODULE_PARM_DESC(dma2, "Capture DMA # for " CRD_NAME " driver." ); |
59 | |
60 | /* |
61 | * Generic SB DSP support routines |
62 | */ |
63 | |
64 | #define DSP_PORT_RESET 0x6 |
65 | #define DSP_PORT_READ 0xa |
66 | #define DSP_PORT_COMMAND 0xc |
67 | #define DSP_PORT_STATUS 0xc |
68 | #define DSP_PORT_DATA_AVAIL 0xe |
69 | |
70 | #define DSP_SIGNATURE 0xaa |
71 | |
72 | #define DSP_COMMAND_GET_VERSION 0xe1 |
73 | |
74 | static int dsp_get_byte(void __iomem *port, u8 *val) |
75 | { |
76 | int loops = 1000; |
77 | |
78 | while (!(ioread8(port + DSP_PORT_DATA_AVAIL) & 0x80)) { |
79 | if (!loops--) |
80 | return -EIO; |
81 | cpu_relax(); |
82 | } |
83 | *val = ioread8(port + DSP_PORT_READ); |
84 | return 0; |
85 | } |
86 | |
87 | static int dsp_reset(void __iomem *port) |
88 | { |
89 | u8 val; |
90 | |
91 | iowrite8(1, port + DSP_PORT_RESET); |
92 | udelay(10); |
93 | iowrite8(0, port + DSP_PORT_RESET); |
94 | |
95 | if (dsp_get_byte(port, val: &val) < 0 || val != DSP_SIGNATURE) |
96 | return -ENODEV; |
97 | |
98 | return 0; |
99 | } |
100 | |
101 | static int dsp_command(void __iomem *port, u8 cmd) |
102 | { |
103 | int loops = 1000; |
104 | |
105 | while (ioread8(port + DSP_PORT_STATUS) & 0x80) { |
106 | if (!loops--) |
107 | return -EIO; |
108 | cpu_relax(); |
109 | } |
110 | iowrite8(cmd, port + DSP_PORT_COMMAND); |
111 | return 0; |
112 | } |
113 | |
114 | static int dsp_get_version(void __iomem *port, u8 *major, u8 *minor) |
115 | { |
116 | int err; |
117 | |
118 | err = dsp_command(port, DSP_COMMAND_GET_VERSION); |
119 | if (err < 0) |
120 | return err; |
121 | |
122 | err = dsp_get_byte(port, val: major); |
123 | if (err < 0) |
124 | return err; |
125 | |
126 | err = dsp_get_byte(port, val: minor); |
127 | if (err < 0) |
128 | return err; |
129 | |
130 | return 0; |
131 | } |
132 | |
133 | /* |
134 | * Generic WSS support routines |
135 | */ |
136 | |
137 | #define WSS_CONFIG_DMA_0 (1 << 0) |
138 | #define WSS_CONFIG_DMA_1 (2 << 0) |
139 | #define WSS_CONFIG_DMA_3 (3 << 0) |
140 | #define WSS_CONFIG_DUPLEX (1 << 2) |
141 | #define WSS_CONFIG_IRQ_7 (1 << 3) |
142 | #define WSS_CONFIG_IRQ_9 (2 << 3) |
143 | #define WSS_CONFIG_IRQ_10 (3 << 3) |
144 | #define WSS_CONFIG_IRQ_11 (4 << 3) |
145 | |
146 | #define WSS_PORT_CONFIG 0 |
147 | #define WSS_PORT_SIGNATURE 3 |
148 | |
149 | #define WSS_SIGNATURE 4 |
150 | |
151 | static int wss_detect(void __iomem *wss_port) |
152 | { |
153 | if ((ioread8(wss_port + WSS_PORT_SIGNATURE) & 0x3f) != WSS_SIGNATURE) |
154 | return -ENODEV; |
155 | |
156 | return 0; |
157 | } |
158 | |
159 | static void wss_set_config(void __iomem *wss_port, u8 wss_config) |
160 | { |
161 | iowrite8(wss_config, wss_port + WSS_PORT_CONFIG); |
162 | } |
163 | |
164 | /* |
165 | * Aztech Sound Galaxy specifics |
166 | */ |
167 | |
168 | #define GALAXY_PORT_CONFIG 1024 |
169 | #define CONFIG_PORT_SET 4 |
170 | |
171 | #define DSP_COMMAND_GALAXY_8 8 |
172 | #define GALAXY_COMMAND_GET_TYPE 5 |
173 | |
174 | #define DSP_COMMAND_GALAXY_9 9 |
175 | #define GALAXY_COMMAND_WSSMODE 0 |
176 | #define GALAXY_COMMAND_SB8MODE 1 |
177 | |
178 | #define GALAXY_MODE_WSS GALAXY_COMMAND_WSSMODE |
179 | #define GALAXY_MODE_SB8 GALAXY_COMMAND_SB8MODE |
180 | |
181 | struct snd_galaxy { |
182 | void __iomem *port; |
183 | void __iomem *config_port; |
184 | void __iomem *wss_port; |
185 | u32 config; |
186 | struct resource *res_port; |
187 | struct resource *res_config_port; |
188 | struct resource *res_wss_port; |
189 | }; |
190 | |
191 | static u32 config[SNDRV_CARDS]; |
192 | static u8 wss_config[SNDRV_CARDS]; |
193 | |
194 | static int snd_galaxy_match(struct device *dev, unsigned int n) |
195 | { |
196 | if (!enable[n]) |
197 | return 0; |
198 | |
199 | switch (port[n]) { |
200 | case SNDRV_AUTO_PORT: |
201 | dev_err(dev, "please specify port\n" ); |
202 | return 0; |
203 | case 0x220: |
204 | config[n] |= GALAXY_CONFIG_SBA_220; |
205 | break; |
206 | case 0x240: |
207 | config[n] |= GALAXY_CONFIG_SBA_240; |
208 | break; |
209 | case 0x260: |
210 | config[n] |= GALAXY_CONFIG_SBA_260; |
211 | break; |
212 | case 0x280: |
213 | config[n] |= GALAXY_CONFIG_SBA_280; |
214 | break; |
215 | default: |
216 | dev_err(dev, "invalid port %#lx\n" , port[n]); |
217 | return 0; |
218 | } |
219 | |
220 | switch (wss_port[n]) { |
221 | case SNDRV_AUTO_PORT: |
222 | dev_err(dev, "please specify wss_port\n" ); |
223 | return 0; |
224 | case 0x530: |
225 | config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_530; |
226 | break; |
227 | case 0x604: |
228 | config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_604; |
229 | break; |
230 | case 0xe80: |
231 | config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_E80; |
232 | break; |
233 | case 0xf40: |
234 | config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_F40; |
235 | break; |
236 | default: |
237 | dev_err(dev, "invalid WSS port %#lx\n" , wss_port[n]); |
238 | return 0; |
239 | } |
240 | |
241 | switch (irq[n]) { |
242 | case SNDRV_AUTO_IRQ: |
243 | dev_err(dev, "please specify irq\n" ); |
244 | return 0; |
245 | case 7: |
246 | wss_config[n] |= WSS_CONFIG_IRQ_7; |
247 | break; |
248 | case 2: |
249 | irq[n] = 9; |
250 | fallthrough; |
251 | case 9: |
252 | wss_config[n] |= WSS_CONFIG_IRQ_9; |
253 | break; |
254 | case 10: |
255 | wss_config[n] |= WSS_CONFIG_IRQ_10; |
256 | break; |
257 | case 11: |
258 | wss_config[n] |= WSS_CONFIG_IRQ_11; |
259 | break; |
260 | default: |
261 | dev_err(dev, "invalid IRQ %d\n" , irq[n]); |
262 | return 0; |
263 | } |
264 | |
265 | switch (dma1[n]) { |
266 | case SNDRV_AUTO_DMA: |
267 | dev_err(dev, "please specify dma1\n" ); |
268 | return 0; |
269 | case 0: |
270 | wss_config[n] |= WSS_CONFIG_DMA_0; |
271 | break; |
272 | case 1: |
273 | wss_config[n] |= WSS_CONFIG_DMA_1; |
274 | break; |
275 | case 3: |
276 | wss_config[n] |= WSS_CONFIG_DMA_3; |
277 | break; |
278 | default: |
279 | dev_err(dev, "invalid playback DMA %d\n" , dma1[n]); |
280 | return 0; |
281 | } |
282 | |
283 | if (dma2[n] == SNDRV_AUTO_DMA || dma2[n] == dma1[n]) { |
284 | dma2[n] = -1; |
285 | goto mpu; |
286 | } |
287 | |
288 | wss_config[n] |= WSS_CONFIG_DUPLEX; |
289 | switch (dma2[n]) { |
290 | case 0: |
291 | break; |
292 | case 1: |
293 | if (dma1[n] == 0) |
294 | break; |
295 | fallthrough; |
296 | default: |
297 | dev_err(dev, "invalid capture DMA %d\n" , dma2[n]); |
298 | return 0; |
299 | } |
300 | |
301 | mpu: |
302 | switch (mpu_port[n]) { |
303 | case SNDRV_AUTO_PORT: |
304 | dev_warn(dev, "mpu_port not specified; not using MPU-401\n" ); |
305 | mpu_port[n] = -1; |
306 | goto fm; |
307 | case 0x300: |
308 | config[n] |= GALAXY_CONFIG_MPU_ENABLE | GALAXY_CONFIG_MPUA_300; |
309 | break; |
310 | case 0x330: |
311 | config[n] |= GALAXY_CONFIG_MPU_ENABLE | GALAXY_CONFIG_MPUA_330; |
312 | break; |
313 | default: |
314 | dev_err(dev, "invalid MPU port %#lx\n" , mpu_port[n]); |
315 | return 0; |
316 | } |
317 | |
318 | switch (mpu_irq[n]) { |
319 | case SNDRV_AUTO_IRQ: |
320 | dev_warn(dev, "mpu_irq not specified: using polling mode\n" ); |
321 | mpu_irq[n] = -1; |
322 | break; |
323 | case 2: |
324 | mpu_irq[n] = 9; |
325 | fallthrough; |
326 | case 9: |
327 | config[n] |= GALAXY_CONFIG_MPUIRQ_2; |
328 | break; |
329 | #ifdef AZT1605 |
330 | case 3: |
331 | config[n] |= GALAXY_CONFIG_MPUIRQ_3; |
332 | break; |
333 | #endif |
334 | case 5: |
335 | config[n] |= GALAXY_CONFIG_MPUIRQ_5; |
336 | break; |
337 | case 7: |
338 | config[n] |= GALAXY_CONFIG_MPUIRQ_7; |
339 | break; |
340 | #ifdef AZT2316 |
341 | case 10: |
342 | config[n] |= GALAXY_CONFIG_MPUIRQ_10; |
343 | break; |
344 | #endif |
345 | default: |
346 | dev_err(dev, "invalid MPU IRQ %d\n" , mpu_irq[n]); |
347 | return 0; |
348 | } |
349 | |
350 | if (mpu_irq[n] == irq[n]) { |
351 | dev_err(dev, "cannot share IRQ between WSS and MPU-401\n" ); |
352 | return 0; |
353 | } |
354 | |
355 | fm: |
356 | switch (fm_port[n]) { |
357 | case SNDRV_AUTO_PORT: |
358 | dev_warn(dev, "fm_port not specified: not using OPL3\n" ); |
359 | fm_port[n] = -1; |
360 | break; |
361 | case 0x388: |
362 | break; |
363 | default: |
364 | dev_err(dev, "illegal FM port %#lx\n" , fm_port[n]); |
365 | return 0; |
366 | } |
367 | |
368 | config[n] |= GALAXY_CONFIG_GAME_ENABLE; |
369 | return 1; |
370 | } |
371 | |
372 | static int galaxy_init(struct snd_galaxy *galaxy, u8 *type) |
373 | { |
374 | u8 major; |
375 | u8 minor; |
376 | int err; |
377 | |
378 | err = dsp_reset(port: galaxy->port); |
379 | if (err < 0) |
380 | return err; |
381 | |
382 | err = dsp_get_version(port: galaxy->port, major: &major, minor: &minor); |
383 | if (err < 0) |
384 | return err; |
385 | |
386 | if (major != GALAXY_DSP_MAJOR || minor != GALAXY_DSP_MINOR) |
387 | return -ENODEV; |
388 | |
389 | err = dsp_command(port: galaxy->port, DSP_COMMAND_GALAXY_8); |
390 | if (err < 0) |
391 | return err; |
392 | |
393 | err = dsp_command(port: galaxy->port, GALAXY_COMMAND_GET_TYPE); |
394 | if (err < 0) |
395 | return err; |
396 | |
397 | err = dsp_get_byte(port: galaxy->port, val: type); |
398 | if (err < 0) |
399 | return err; |
400 | |
401 | return 0; |
402 | } |
403 | |
404 | static int galaxy_set_mode(struct snd_galaxy *galaxy, u8 mode) |
405 | { |
406 | int err; |
407 | |
408 | err = dsp_command(port: galaxy->port, DSP_COMMAND_GALAXY_9); |
409 | if (err < 0) |
410 | return err; |
411 | |
412 | err = dsp_command(port: galaxy->port, cmd: mode); |
413 | if (err < 0) |
414 | return err; |
415 | |
416 | #ifdef AZT1605 |
417 | /* |
418 | * Needed for MPU IRQ on AZT1605, but AZT2316 loses WSS again |
419 | */ |
420 | err = dsp_reset(port: galaxy->port); |
421 | if (err < 0) |
422 | return err; |
423 | #endif |
424 | |
425 | return 0; |
426 | } |
427 | |
428 | static void galaxy_set_config(struct snd_galaxy *galaxy, u32 config) |
429 | { |
430 | u8 tmp = ioread8(galaxy->config_port + CONFIG_PORT_SET); |
431 | int i; |
432 | |
433 | iowrite8(tmp | 0x80, galaxy->config_port + CONFIG_PORT_SET); |
434 | for (i = 0; i < GALAXY_CONFIG_SIZE; i++) { |
435 | iowrite8(config, galaxy->config_port + i); |
436 | config >>= 8; |
437 | } |
438 | iowrite8(tmp & 0x7f, galaxy->config_port + CONFIG_PORT_SET); |
439 | msleep(msecs: 10); |
440 | } |
441 | |
442 | static void galaxy_config(struct snd_galaxy *galaxy, u32 config) |
443 | { |
444 | int i; |
445 | |
446 | for (i = GALAXY_CONFIG_SIZE; i; i--) { |
447 | u8 tmp = ioread8(galaxy->config_port + i - 1); |
448 | galaxy->config = (galaxy->config << 8) | tmp; |
449 | } |
450 | config |= galaxy->config & GALAXY_CONFIG_MASK; |
451 | galaxy_set_config(galaxy, config); |
452 | } |
453 | |
454 | static int galaxy_wss_config(struct snd_galaxy *galaxy, u8 wss_config) |
455 | { |
456 | int err; |
457 | |
458 | err = wss_detect(wss_port: galaxy->wss_port); |
459 | if (err < 0) |
460 | return err; |
461 | |
462 | wss_set_config(wss_port: galaxy->wss_port, wss_config); |
463 | |
464 | err = galaxy_set_mode(galaxy, GALAXY_MODE_WSS); |
465 | if (err < 0) |
466 | return err; |
467 | |
468 | return 0; |
469 | } |
470 | |
471 | static void snd_galaxy_free(struct snd_card *card) |
472 | { |
473 | struct snd_galaxy *galaxy = card->private_data; |
474 | |
475 | if (galaxy->wss_port) |
476 | wss_set_config(wss_port: galaxy->wss_port, wss_config: 0); |
477 | if (galaxy->config_port) |
478 | galaxy_set_config(galaxy, config: galaxy->config); |
479 | } |
480 | |
481 | static int __snd_galaxy_probe(struct device *dev, unsigned int n) |
482 | { |
483 | struct snd_galaxy *galaxy; |
484 | struct snd_wss *chip; |
485 | struct snd_card *card; |
486 | u8 type; |
487 | int err; |
488 | |
489 | err = snd_devm_card_new(parent: dev, idx: index[n], xid: id[n], THIS_MODULE, |
490 | extra_size: sizeof(*galaxy), card_ret: &card); |
491 | if (err < 0) |
492 | return err; |
493 | |
494 | card->private_free = snd_galaxy_free; |
495 | galaxy = card->private_data; |
496 | |
497 | galaxy->res_port = devm_request_region(dev, port[n], 16, DRV_NAME); |
498 | if (!galaxy->res_port) { |
499 | dev_err(dev, "could not grab ports %#lx-%#lx\n" , port[n], |
500 | port[n] + 15); |
501 | return -EBUSY; |
502 | } |
503 | galaxy->port = devm_ioport_map(dev, port: port[n], nr: 16); |
504 | if (!galaxy->port) |
505 | return -ENOMEM; |
506 | |
507 | err = galaxy_init(galaxy, type: &type); |
508 | if (err < 0) { |
509 | dev_err(dev, "did not find a Sound Galaxy at %#lx\n" , port[n]); |
510 | return err; |
511 | } |
512 | dev_info(dev, "Sound Galaxy (type %d) found at %#lx\n" , type, port[n]); |
513 | |
514 | galaxy->res_config_port = |
515 | devm_request_region(dev, port[n] + GALAXY_PORT_CONFIG, 16, |
516 | DRV_NAME); |
517 | if (!galaxy->res_config_port) { |
518 | dev_err(dev, "could not grab ports %#lx-%#lx\n" , |
519 | port[n] + GALAXY_PORT_CONFIG, |
520 | port[n] + GALAXY_PORT_CONFIG + 15); |
521 | return -EBUSY; |
522 | } |
523 | galaxy->config_port = |
524 | devm_ioport_map(dev, port: port[n] + GALAXY_PORT_CONFIG, nr: 16); |
525 | if (!galaxy->config_port) |
526 | return -ENOMEM; |
527 | galaxy_config(galaxy, config: config[n]); |
528 | |
529 | galaxy->res_wss_port = devm_request_region(dev, wss_port[n], 4, DRV_NAME); |
530 | if (!galaxy->res_wss_port) { |
531 | dev_err(dev, "could not grab ports %#lx-%#lx\n" , wss_port[n], |
532 | wss_port[n] + 3); |
533 | return -EBUSY; |
534 | } |
535 | galaxy->wss_port = devm_ioport_map(dev, port: wss_port[n], nr: 4); |
536 | if (!galaxy->wss_port) |
537 | return -ENOMEM; |
538 | |
539 | err = galaxy_wss_config(galaxy, wss_config: wss_config[n]); |
540 | if (err < 0) { |
541 | dev_err(dev, "could not configure WSS\n" ); |
542 | return err; |
543 | } |
544 | |
545 | strcpy(p: card->driver, DRV_NAME); |
546 | strcpy(p: card->shortname, DRV_NAME); |
547 | sprintf(buf: card->longname, fmt: "%s at %#lx/%#lx, irq %d, dma %d/%d" , |
548 | card->shortname, port[n], wss_port[n], irq[n], dma1[n], |
549 | dma2[n]); |
550 | |
551 | err = snd_wss_create(card, port: wss_port[n] + 4, cport: -1, irq: irq[n], dma1: dma1[n], |
552 | dma2: dma2[n], WSS_HW_DETECT, hwshare: 0, rchip: &chip); |
553 | if (err < 0) |
554 | return err; |
555 | |
556 | err = snd_wss_pcm(chip, device: 0); |
557 | if (err < 0) |
558 | return err; |
559 | |
560 | err = snd_wss_mixer(chip); |
561 | if (err < 0) |
562 | return err; |
563 | |
564 | err = snd_wss_timer(chip, device: 0); |
565 | if (err < 0) |
566 | return err; |
567 | |
568 | if (mpu_port[n] >= 0) { |
569 | err = snd_mpu401_uart_new(card, device: 0, MPU401_HW_MPU401, |
570 | port: mpu_port[n], info_flags: 0, irq: mpu_irq[n], NULL); |
571 | if (err < 0) |
572 | return err; |
573 | } |
574 | |
575 | if (fm_port[n] >= 0) { |
576 | struct snd_opl3 *opl3; |
577 | |
578 | err = snd_opl3_create(card, l_port: fm_port[n], r_port: fm_port[n] + 2, |
579 | OPL3_HW_AUTO, integrated: 0, opl3: &opl3); |
580 | if (err < 0) { |
581 | dev_err(dev, "no OPL device at %#lx\n" , fm_port[n]); |
582 | return err; |
583 | } |
584 | err = snd_opl3_timer_new(opl3, timer1_dev: 1, timer2_dev: 2); |
585 | if (err < 0) |
586 | return err; |
587 | |
588 | err = snd_opl3_hwdep_new(opl3, device: 0, seq_device: 1, NULL); |
589 | if (err < 0) |
590 | return err; |
591 | } |
592 | |
593 | err = snd_card_register(card); |
594 | if (err < 0) |
595 | return err; |
596 | |
597 | dev_set_drvdata(dev, data: card); |
598 | return 0; |
599 | } |
600 | |
601 | static int snd_galaxy_probe(struct device *dev, unsigned int n) |
602 | { |
603 | return snd_card_free_on_error(dev, ret: __snd_galaxy_probe(dev, n)); |
604 | } |
605 | |
606 | static struct isa_driver snd_galaxy_driver = { |
607 | .match = snd_galaxy_match, |
608 | .probe = snd_galaxy_probe, |
609 | |
610 | .driver = { |
611 | .name = DEV_NAME |
612 | } |
613 | }; |
614 | |
615 | module_isa_driver(snd_galaxy_driver, SNDRV_CARDS); |
616 | |