1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
4 * Uros Bizjak <uros@kss-loka.si>
5 *
6 * Routines for control of 8-bit SoundBlaster cards and clones
7 * Please note: I don't have access to old SB8 soundcards.
8 *
9 * --
10 *
11 * Thu Apr 29 20:36:17 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk>
12 * DSP can't respond to commands whilst in "high speed" mode. Caused
13 * glitching during playback. Fixed.
14 *
15 * Wed Jul 12 22:02:55 CEST 2000 Uros Bizjak <uros@kss-loka.si>
16 * Cleaned up and rewrote lowlevel routines.
17 */
18
19#include <linux/io.h>
20#include <asm/dma.h>
21#include <linux/init.h>
22#include <linux/time.h>
23#include <linux/module.h>
24#include <sound/core.h>
25#include <sound/sb.h>
26
27MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, Uros Bizjak <uros@kss-loka.si>");
28MODULE_DESCRIPTION("Routines for control of 8-bit SoundBlaster cards and clones");
29MODULE_LICENSE("GPL");
30
31#define SB8_CLOCK 1000000
32#define SB8_DEN(v) ((SB8_CLOCK + (v) / 2) / (v))
33#define SB8_RATE(v) (SB8_CLOCK / SB8_DEN(v))
34
35static const struct snd_ratnum clock = {
36 .num = SB8_CLOCK,
37 .den_min = 1,
38 .den_max = 256,
39 .den_step = 1,
40};
41
42static const struct snd_pcm_hw_constraint_ratnums hw_constraints_clock = {
43 .nrats = 1,
44 .rats = &clock,
45};
46
47static const struct snd_ratnum stereo_clocks[] = {
48 {
49 .num = SB8_CLOCK,
50 .den_min = SB8_DEN(22050),
51 .den_max = SB8_DEN(22050),
52 .den_step = 1,
53 },
54 {
55 .num = SB8_CLOCK,
56 .den_min = SB8_DEN(11025),
57 .den_max = SB8_DEN(11025),
58 .den_step = 1,
59 }
60};
61
62static int snd_sb8_hw_constraint_rate_channels(struct snd_pcm_hw_params *params,
63 struct snd_pcm_hw_rule *rule)
64{
65 struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
66 if (c->min > 1) {
67 unsigned int num = 0, den = 0;
68 int err = snd_interval_ratnum(i: hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE),
69 rats_count: 2, rats: stereo_clocks, nump: &num, denp: &den);
70 if (err >= 0 && den) {
71 params->rate_num = num;
72 params->rate_den = den;
73 }
74 return err;
75 }
76 return 0;
77}
78
79static int snd_sb8_hw_constraint_channels_rate(struct snd_pcm_hw_params *params,
80 struct snd_pcm_hw_rule *rule)
81{
82 struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
83 if (r->min > SB8_RATE(22050) || r->max <= SB8_RATE(11025)) {
84 struct snd_interval t = { .min = 1, .max = 1 };
85 return snd_interval_refine(i: hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS), v: &t);
86 }
87 return 0;
88}
89
90static int snd_sb8_playback_prepare(struct snd_pcm_substream *substream)
91{
92 struct snd_sb *chip = snd_pcm_substream_chip(substream);
93 struct snd_pcm_runtime *runtime = substream->runtime;
94 unsigned int mixreg, rate, size, count;
95 unsigned char format;
96 unsigned char stereo = runtime->channels > 1;
97 int dma;
98
99 rate = runtime->rate;
100 switch (chip->hardware) {
101 case SB_HW_JAZZ16:
102 if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) {
103 if (chip->mode & SB_MODE_CAPTURE_16)
104 return -EBUSY;
105 else
106 chip->mode |= SB_MODE_PLAYBACK_16;
107 }
108 chip->playback_format = SB_DSP_LO_OUTPUT_AUTO;
109 break;
110 case SB_HW_PRO:
111 if (runtime->channels > 1) {
112 if (snd_BUG_ON(rate != SB8_RATE(11025) &&
113 rate != SB8_RATE(22050)))
114 return -EINVAL;
115 chip->playback_format = SB_DSP_HI_OUTPUT_AUTO;
116 break;
117 }
118 fallthrough;
119 case SB_HW_201:
120 if (rate > 23000) {
121 chip->playback_format = SB_DSP_HI_OUTPUT_AUTO;
122 break;
123 }
124 fallthrough;
125 case SB_HW_20:
126 chip->playback_format = SB_DSP_LO_OUTPUT_AUTO;
127 break;
128 case SB_HW_10:
129 chip->playback_format = SB_DSP_OUTPUT;
130 break;
131 default:
132 return -EINVAL;
133 }
134 if (chip->mode & SB_MODE_PLAYBACK_16) {
135 format = stereo ? SB_DSP_STEREO_16BIT : SB_DSP_MONO_16BIT;
136 dma = chip->dma16;
137 } else {
138 format = stereo ? SB_DSP_STEREO_8BIT : SB_DSP_MONO_8BIT;
139 chip->mode |= SB_MODE_PLAYBACK_8;
140 dma = chip->dma8;
141 }
142 size = chip->p_dma_size = snd_pcm_lib_buffer_bytes(substream);
143 count = chip->p_period_size = snd_pcm_lib_period_bytes(substream);
144 scoped_guard(spinlock_irqsave, &chip->reg_lock) {
145 snd_sbdsp_command(chip, SB_DSP_SPEAKER_ON);
146 if (chip->hardware == SB_HW_JAZZ16)
147 snd_sbdsp_command(chip, val: format);
148 else if (stereo) {
149 /* set playback stereo mode */
150 scoped_guard(spinlock, &chip->mixer_lock) {
151 mixreg = snd_sbmixer_read(chip, SB_DSP_STEREO_SW);
152 snd_sbmixer_write(chip, SB_DSP_STEREO_SW, data: mixreg | 0x02);
153 }
154
155 /* Soundblaster hardware programming reference guide, 3-23 */
156 snd_sbdsp_command(chip, SB_DSP_DMA8_EXIT);
157 runtime->dma_area[0] = 0x80;
158 snd_dma_program(dma, addr: runtime->dma_addr, size: 1, DMA_MODE_WRITE);
159 /* force interrupt */
160 snd_sbdsp_command(chip, SB_DSP_OUTPUT);
161 snd_sbdsp_command(chip, val: 0);
162 snd_sbdsp_command(chip, val: 0);
163 }
164 snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE);
165 if (stereo) {
166 snd_sbdsp_command(chip, val: 256 - runtime->rate_den / 2);
167 scoped_guard(spinlock, &chip->mixer_lock) {
168 /* save output filter status and turn it off */
169 mixreg = snd_sbmixer_read(chip, SB_DSP_PLAYBACK_FILT);
170 snd_sbmixer_write(chip, SB_DSP_PLAYBACK_FILT, data: mixreg | 0x20);
171 }
172 /* just use force_mode16 for temporary storate... */
173 chip->force_mode16 = mixreg;
174 } else {
175 snd_sbdsp_command(chip, val: 256 - runtime->rate_den);
176 }
177 if (chip->playback_format != SB_DSP_OUTPUT) {
178 if (chip->mode & SB_MODE_PLAYBACK_16)
179 count /= 2;
180 count--;
181 snd_sbdsp_command(chip, SB_DSP_BLOCK_SIZE);
182 snd_sbdsp_command(chip, val: count & 0xff);
183 snd_sbdsp_command(chip, val: count >> 8);
184 }
185 }
186 snd_dma_program(dma, addr: runtime->dma_addr,
187 size, DMA_MODE_WRITE | DMA_AUTOINIT);
188 return 0;
189}
190
191static int snd_sb8_playback_trigger(struct snd_pcm_substream *substream,
192 int cmd)
193{
194 struct snd_sb *chip = snd_pcm_substream_chip(substream);
195 unsigned int count;
196
197 guard(spinlock_irqsave)(l: &chip->reg_lock);
198 switch (cmd) {
199 case SNDRV_PCM_TRIGGER_START:
200 snd_sbdsp_command(chip, val: chip->playback_format);
201 if (chip->playback_format == SB_DSP_OUTPUT) {
202 count = chip->p_period_size - 1;
203 snd_sbdsp_command(chip, val: count & 0xff);
204 snd_sbdsp_command(chip, val: count >> 8);
205 }
206 break;
207 case SNDRV_PCM_TRIGGER_STOP:
208 if (chip->playback_format == SB_DSP_HI_OUTPUT_AUTO) {
209 struct snd_pcm_runtime *runtime = substream->runtime;
210 snd_sbdsp_reset(chip);
211 if (runtime->channels > 1) {
212 guard(spinlock)(l: &chip->mixer_lock);
213 /* restore output filter and set hardware to mono mode */
214 snd_sbmixer_write(chip, SB_DSP_STEREO_SW, data: chip->force_mode16 & ~0x02);
215 }
216 } else {
217 snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
218 }
219 snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
220 }
221 return 0;
222}
223
224static int snd_sb8_capture_prepare(struct snd_pcm_substream *substream)
225{
226 struct snd_sb *chip = snd_pcm_substream_chip(substream);
227 struct snd_pcm_runtime *runtime = substream->runtime;
228 unsigned int mixreg, rate, size, count;
229 unsigned char format;
230 unsigned char stereo = runtime->channels > 1;
231 int dma;
232
233 rate = runtime->rate;
234 switch (chip->hardware) {
235 case SB_HW_JAZZ16:
236 if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) {
237 if (chip->mode & SB_MODE_PLAYBACK_16)
238 return -EBUSY;
239 else
240 chip->mode |= SB_MODE_CAPTURE_16;
241 }
242 chip->capture_format = SB_DSP_LO_INPUT_AUTO;
243 break;
244 case SB_HW_PRO:
245 if (runtime->channels > 1) {
246 if (snd_BUG_ON(rate != SB8_RATE(11025) &&
247 rate != SB8_RATE(22050)))
248 return -EINVAL;
249 chip->capture_format = SB_DSP_HI_INPUT_AUTO;
250 break;
251 }
252 chip->capture_format = (rate > 23000) ? SB_DSP_HI_INPUT_AUTO : SB_DSP_LO_INPUT_AUTO;
253 break;
254 case SB_HW_201:
255 if (rate > 13000) {
256 chip->capture_format = SB_DSP_HI_INPUT_AUTO;
257 break;
258 }
259 fallthrough;
260 case SB_HW_20:
261 chip->capture_format = SB_DSP_LO_INPUT_AUTO;
262 break;
263 case SB_HW_10:
264 chip->capture_format = SB_DSP_INPUT;
265 break;
266 default:
267 return -EINVAL;
268 }
269 if (chip->mode & SB_MODE_CAPTURE_16) {
270 format = stereo ? SB_DSP_STEREO_16BIT : SB_DSP_MONO_16BIT;
271 dma = chip->dma16;
272 } else {
273 format = stereo ? SB_DSP_STEREO_8BIT : SB_DSP_MONO_8BIT;
274 chip->mode |= SB_MODE_CAPTURE_8;
275 dma = chip->dma8;
276 }
277 size = chip->c_dma_size = snd_pcm_lib_buffer_bytes(substream);
278 count = chip->c_period_size = snd_pcm_lib_period_bytes(substream);
279 scoped_guard(spinlock_irqsave, &chip->reg_lock) {
280 snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
281 if (chip->hardware == SB_HW_JAZZ16)
282 snd_sbdsp_command(chip, val: format);
283 else if (stereo)
284 snd_sbdsp_command(chip, SB_DSP_STEREO_8BIT);
285 snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE);
286 if (stereo) {
287 snd_sbdsp_command(chip, val: 256 - runtime->rate_den / 2);
288 scoped_guard(spinlock, &chip->mixer_lock) {
289 /* save input filter status and turn it off */
290 mixreg = snd_sbmixer_read(chip, SB_DSP_CAPTURE_FILT);
291 snd_sbmixer_write(chip, SB_DSP_CAPTURE_FILT, data: mixreg | 0x20);
292 }
293 /* just use force_mode16 for temporary storate... */
294 chip->force_mode16 = mixreg;
295 } else {
296 snd_sbdsp_command(chip, val: 256 - runtime->rate_den);
297 }
298 if (chip->capture_format != SB_DSP_INPUT) {
299 if (chip->mode & SB_MODE_PLAYBACK_16)
300 count /= 2;
301 count--;
302 snd_sbdsp_command(chip, SB_DSP_BLOCK_SIZE);
303 snd_sbdsp_command(chip, val: count & 0xff);
304 snd_sbdsp_command(chip, val: count >> 8);
305 }
306 }
307 snd_dma_program(dma, addr: runtime->dma_addr,
308 size, DMA_MODE_READ | DMA_AUTOINIT);
309 return 0;
310}
311
312static int snd_sb8_capture_trigger(struct snd_pcm_substream *substream,
313 int cmd)
314{
315 struct snd_sb *chip = snd_pcm_substream_chip(substream);
316 unsigned int count;
317
318 guard(spinlock_irqsave)(l: &chip->reg_lock);
319 switch (cmd) {
320 case SNDRV_PCM_TRIGGER_START:
321 snd_sbdsp_command(chip, val: chip->capture_format);
322 if (chip->capture_format == SB_DSP_INPUT) {
323 count = chip->c_period_size - 1;
324 snd_sbdsp_command(chip, val: count & 0xff);
325 snd_sbdsp_command(chip, val: count >> 8);
326 }
327 break;
328 case SNDRV_PCM_TRIGGER_STOP:
329 if (chip->capture_format == SB_DSP_HI_INPUT_AUTO) {
330 struct snd_pcm_runtime *runtime = substream->runtime;
331 snd_sbdsp_reset(chip);
332 if (runtime->channels > 1) {
333 /* restore input filter status */
334 scoped_guard(spinlock, &chip->mixer_lock) {
335 snd_sbmixer_write(chip, SB_DSP_CAPTURE_FILT, data: chip->force_mode16);
336 }
337 /* set hardware to mono mode */
338 snd_sbdsp_command(chip, SB_DSP_MONO_8BIT);
339 }
340 } else {
341 snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
342 }
343 snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
344 }
345 return 0;
346}
347
348irqreturn_t snd_sb8dsp_interrupt(struct snd_sb *chip)
349{
350 struct snd_pcm_substream *substream;
351
352 snd_sb_ack_8bit(chip);
353 switch (chip->mode) {
354 case SB_MODE_PLAYBACK_16: /* ok.. playback is active */
355 if (chip->hardware != SB_HW_JAZZ16)
356 break;
357 fallthrough;
358 case SB_MODE_PLAYBACK_8:
359 substream = chip->playback_substream;
360 if (chip->playback_format == SB_DSP_OUTPUT)
361 snd_sb8_playback_trigger(substream, SNDRV_PCM_TRIGGER_START);
362 snd_pcm_period_elapsed(substream);
363 break;
364 case SB_MODE_CAPTURE_16:
365 if (chip->hardware != SB_HW_JAZZ16)
366 break;
367 fallthrough;
368 case SB_MODE_CAPTURE_8:
369 substream = chip->capture_substream;
370 if (chip->capture_format == SB_DSP_INPUT)
371 snd_sb8_capture_trigger(substream, SNDRV_PCM_TRIGGER_START);
372 snd_pcm_period_elapsed(substream);
373 break;
374 }
375 return IRQ_HANDLED;
376}
377
378static snd_pcm_uframes_t snd_sb8_playback_pointer(struct snd_pcm_substream *substream)
379{
380 struct snd_sb *chip = snd_pcm_substream_chip(substream);
381 size_t ptr;
382 int dma;
383
384 if (chip->mode & SB_MODE_PLAYBACK_8)
385 dma = chip->dma8;
386 else if (chip->mode & SB_MODE_PLAYBACK_16)
387 dma = chip->dma16;
388 else
389 return 0;
390 ptr = snd_dma_pointer(dma, size: chip->p_dma_size);
391 return bytes_to_frames(runtime: substream->runtime, size: ptr);
392}
393
394static snd_pcm_uframes_t snd_sb8_capture_pointer(struct snd_pcm_substream *substream)
395{
396 struct snd_sb *chip = snd_pcm_substream_chip(substream);
397 size_t ptr;
398 int dma;
399
400 if (chip->mode & SB_MODE_CAPTURE_8)
401 dma = chip->dma8;
402 else if (chip->mode & SB_MODE_CAPTURE_16)
403 dma = chip->dma16;
404 else
405 return 0;
406 ptr = snd_dma_pointer(dma, size: chip->c_dma_size);
407 return bytes_to_frames(runtime: substream->runtime, size: ptr);
408}
409
410/*
411
412 */
413
414static const struct snd_pcm_hardware snd_sb8_playback =
415{
416 .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
417 SNDRV_PCM_INFO_MMAP_VALID),
418 .formats = SNDRV_PCM_FMTBIT_U8,
419 .rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000 |
420 SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_22050),
421 .rate_min = 4000,
422 .rate_max = 23000,
423 .channels_min = 1,
424 .channels_max = 1,
425 .buffer_bytes_max = 65536,
426 .period_bytes_min = 64,
427 .period_bytes_max = 65536,
428 .periods_min = 1,
429 .periods_max = 1024,
430 .fifo_size = 0,
431};
432
433static const struct snd_pcm_hardware snd_sb8_capture =
434{
435 .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
436 SNDRV_PCM_INFO_MMAP_VALID),
437 .formats = SNDRV_PCM_FMTBIT_U8,
438 .rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000 |
439 SNDRV_PCM_RATE_11025),
440 .rate_min = 4000,
441 .rate_max = 13000,
442 .channels_min = 1,
443 .channels_max = 1,
444 .buffer_bytes_max = 65536,
445 .period_bytes_min = 64,
446 .period_bytes_max = 65536,
447 .periods_min = 1,
448 .periods_max = 1024,
449 .fifo_size = 0,
450};
451
452/*
453 *
454 */
455
456static int snd_sb8_open(struct snd_pcm_substream *substream)
457{
458 struct snd_sb *chip = snd_pcm_substream_chip(substream);
459 struct snd_pcm_runtime *runtime = substream->runtime;
460
461 scoped_guard(spinlock_irqsave, &chip->open_lock) {
462 if (chip->open)
463 return -EAGAIN;
464 chip->open |= SB_OPEN_PCM;
465 }
466 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
467 chip->playback_substream = substream;
468 runtime->hw = snd_sb8_playback;
469 } else {
470 chip->capture_substream = substream;
471 runtime->hw = snd_sb8_capture;
472 }
473 switch (chip->hardware) {
474 case SB_HW_JAZZ16:
475 if (chip->dma16 == 5 || chip->dma16 == 7)
476 runtime->hw.formats |= SNDRV_PCM_FMTBIT_S16_LE;
477 runtime->hw.rates |= SNDRV_PCM_RATE_8000_48000;
478 runtime->hw.rate_min = 4000;
479 runtime->hw.rate_max = 50000;
480 runtime->hw.channels_max = 2;
481 break;
482 case SB_HW_PRO:
483 runtime->hw.rate_max = 44100;
484 runtime->hw.channels_max = 2;
485 snd_pcm_hw_rule_add(runtime, cond: 0, SNDRV_PCM_HW_PARAM_RATE,
486 func: snd_sb8_hw_constraint_rate_channels, NULL,
487 SNDRV_PCM_HW_PARAM_CHANNELS,
488 SNDRV_PCM_HW_PARAM_RATE, -1);
489 snd_pcm_hw_rule_add(runtime, cond: 0, SNDRV_PCM_HW_PARAM_CHANNELS,
490 func: snd_sb8_hw_constraint_channels_rate, NULL,
491 SNDRV_PCM_HW_PARAM_RATE, -1);
492 break;
493 case SB_HW_201:
494 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
495 runtime->hw.rate_max = 44100;
496 } else {
497 runtime->hw.rate_max = 15000;
498 }
499 break;
500 default:
501 break;
502 }
503 snd_pcm_hw_constraint_ratnums(runtime, cond: 0, SNDRV_PCM_HW_PARAM_RATE,
504 r: &hw_constraints_clock);
505 if (chip->dma8 > 3 || chip->dma16 >= 0) {
506 snd_pcm_hw_constraint_step(runtime, cond: 0,
507 SNDRV_PCM_HW_PARAM_BUFFER_BYTES, step: 2);
508 snd_pcm_hw_constraint_step(runtime, cond: 0,
509 SNDRV_PCM_HW_PARAM_PERIOD_BYTES, step: 2);
510 runtime->hw.buffer_bytes_max = 128 * 1024 * 1024;
511 runtime->hw.period_bytes_max = 128 * 1024 * 1024;
512 }
513 return 0;
514}
515
516static int snd_sb8_close(struct snd_pcm_substream *substream)
517{
518 struct snd_sb *chip = snd_pcm_substream_chip(substream);
519
520 chip->playback_substream = NULL;
521 chip->capture_substream = NULL;
522 guard(spinlock_irqsave)(l: &chip->open_lock);
523 chip->open &= ~SB_OPEN_PCM;
524 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
525 chip->mode &= ~SB_MODE_PLAYBACK;
526 else
527 chip->mode &= ~SB_MODE_CAPTURE;
528 return 0;
529}
530
531/*
532 * Initialization part
533 */
534
535static const struct snd_pcm_ops snd_sb8_playback_ops = {
536 .open = snd_sb8_open,
537 .close = snd_sb8_close,
538 .prepare = snd_sb8_playback_prepare,
539 .trigger = snd_sb8_playback_trigger,
540 .pointer = snd_sb8_playback_pointer,
541};
542
543static const struct snd_pcm_ops snd_sb8_capture_ops = {
544 .open = snd_sb8_open,
545 .close = snd_sb8_close,
546 .prepare = snd_sb8_capture_prepare,
547 .trigger = snd_sb8_capture_trigger,
548 .pointer = snd_sb8_capture_pointer,
549};
550
551int snd_sb8dsp_pcm(struct snd_sb *chip, int device)
552{
553 struct snd_card *card = chip->card;
554 struct snd_pcm *pcm;
555 int err;
556 size_t max_prealloc = 64 * 1024;
557
558 err = snd_pcm_new(card, id: "SB8 DSP", device, playback_count: 1, capture_count: 1, rpcm: &pcm);
559 if (err < 0)
560 return err;
561 sprintf(buf: pcm->name, fmt: "DSP v%i.%i", chip->version >> 8, chip->version & 0xff);
562 pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
563 pcm->private_data = chip;
564
565 snd_pcm_set_ops(pcm, direction: SNDRV_PCM_STREAM_PLAYBACK, ops: &snd_sb8_playback_ops);
566 snd_pcm_set_ops(pcm, direction: SNDRV_PCM_STREAM_CAPTURE, ops: &snd_sb8_capture_ops);
567
568 if (chip->dma8 > 3 || chip->dma16 >= 0)
569 max_prealloc = 128 * 1024;
570 snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
571 data: card->dev, size: 64*1024, max: max_prealloc);
572
573 return 0;
574}
575
576EXPORT_SYMBOL(snd_sb8dsp_pcm);
577EXPORT_SYMBOL(snd_sb8dsp_interrupt);
578 /* sb8_midi.c */
579EXPORT_SYMBOL(snd_sb8dsp_midi_interrupt);
580EXPORT_SYMBOL(snd_sb8dsp_midi);
581

source code of linux/sound/isa/sb/sb8_main.c