reorganized the soundblaster code a bit, adding support for DSP < 4
[dos_auplay] / src / au_sb.c
1 #include <stdio.h>\r
2 #include <stdlib.h>\r
3 #include <assert.h>\r
4 #include <dos.h>\r
5 #include <conio.h>\r
6 #include "audio.h"\r
7 #include "audrv.h"\r
8 #include "dpmi.h"\r
9 #include "dma.h"\r
10 #include "intr.h"\r
11 \r
12 #define BUFSIZE                 16384\r
13 \r
14 #define REG_MIXPORT             (base_port + 0x4)\r
15 #define REG_MIXDATA             (base_port + 0x5)\r
16 #define REG_RESET               (base_port + 0x6)\r
17 #define REG_RDATA               (base_port + 0xa)\r
18 #define REG_WDATA               (base_port + 0xc)\r
19 #define REG_WSTAT               (base_port + 0xc)\r
20 #define REG_RSTAT               (base_port + 0xe)\r
21 #define REG_INTACK              (base_port + 0xe)\r
22 #define REG_INT16ACK    (base_port + 0xf)\r
23 \r
24 #define WSTAT_BUSY              0x80\r
25 #define RSTAT_RDY               0x80\r
26 \r
27 #define DSP_RATE                        0x40\r
28 #define DSP4_OUT_RATE           0x41\r
29 #define DSP4_IN_RATE            0x42\r
30 #define DSP_BLOCKSZ                     0x48\r
31 #define DSP_GET_VER                     0xe1\r
32 \r
33 /* start DMA playback/recording. combine with fifo/auto/input flags */\r
34 #define DSP4_START_DMA8         0xc0\r
35 #define DSP4_START_DMA16        0xb0\r
36 #define DSP4_FIFO                       0x02\r
37 #define DSP4_AUTO                       0x04\r
38 #define DSP4_INPUT                      0x08\r
39 \r
40 /* transfer mode commands */\r
41 #define DSP4_MODE_SIGNED        0x10\r
42 #define DSP4_MODE_STEREO        0x20\r
43 \r
44 /* immediately pause/continue */\r
45 #define DSP_PAUSE_DMA8          0xd0\r
46 #define DSP_ENABLE_OUTPUT       0xd1\r
47 #define DSP_DISABLE_OUTPUT      0xd3\r
48 #define DSP_CONT_DMA8           0xd4\r
49 #define DSP_PAUSE_DMA16         0xd5\r
50 #define DSP_CONT_DMA16          0xd6\r
51 \r
52 /* end the playback at the end of the current buffer */\r
53 #define DSP_END_DMA16           0xd9\r
54 #define DSP_END_DMA8            0xda\r
55 \r
56 /* mixer registers */\r
57 #define MIX_MASTER                      0x02\r
58 #define MIX_VOICE                       0x0a\r
59 #define MIX_SBPRO_VOICE         0x04\r
60 #define MIX_SBPRO_MASTER        0x22\r
61 #define MIX_SB16_MASTER_L       0x30\r
62 #define MIX_SB16_MASTER_R       0x31\r
63 #define MIX_SB16_VOICE_L        0x32\r
64 #define MIX_SB16_VOICE_R        0x33\r
65 \r
66 #define MIX_SBPRO_STEREO        0x0e\r
67 #define MIX_SB16_IRQ_SEL        0x80\r
68 #define MIX_SB16_DMA_SEL        0x81\r
69 #define MIX_SB16_INTSTAT        0x82\r
70 \r
71 #define INTSTAT_DMA8            0x01\r
72 #define INTSTAT_DMA16           0x02\r
73 #define INTSTAT_MPU401          0x04\r
74 \r
75 #define VER_MAJOR(x)    ((x) >> 8)\r
76 #define VER_MINOR(x)    ((x) & 0xff)\r
77 \r
78 /* delay for about 1us */\r
79 #define iodelay()       outp(0x80, 0)\r
80 \r
81 \r
82 static int reset_dsp(void);\r
83 static void *sb_buffer(int *size);\r
84 static void start(int rate, int bits, int nchan);\r
85 static void pause(void);\r
86 static void cont(void);\r
87 static void stop(void);\r
88 static int isplaying(void);\r
89 static void setvolume(int ctl, int vol);\r
90 static int getvolume(int ctl);\r
91 \r
92 static void set_sbpro_stereo(void);\r
93 static int start_dsp4(int rate, int bits, unsigned int mode, int num_samples);\r
94 static int start_dsp(int rate, int nchan, int num_samples);\r
95 static void INTERRUPT intr_handler();\r
96 static void write_dsp(unsigned char val);\r
97 static unsigned char read_dsp(void);\r
98 static void write_mix(int reg, unsigned char val);\r
99 static unsigned char read_mix(int reg);\r
100 static int get_dsp_version(void);\r
101 static int dsp4_detect_irq(void);\r
102 static int dsp4_detect_dma(void);\r
103 static void print_dsp_info(void);\r
104 static const char *sbname(int ver);\r
105 \r
106 \r
107 static const struct audrv sbdrv = {\r
108         start,\r
109         pause,\r
110         cont,\r
111         stop,\r
112         setvolume,\r
113         getvolume,\r
114         isplaying\r
115 };\r
116 \r
117 \r
118 static int base_port;\r
119 static int irq, dma_chan, dma16_chan, dsp_ver;\r
120 \r
121 static void *buffer[2];\r
122 static volatile int wrbuf;\r
123 \r
124 static volatile int playing;\r
125 static int cur_bits, auto_init, high_speed;\r
126 \r
127 static void (INTERRUPT *prev_intr_handler)();\r
128 \r
129 \r
130 int sb_detect(struct audrv *drv)\r
131 {\r
132         int i;\r
133         char *env;\r
134 \r
135         if((env = getenv("BLASTER"))) {\r
136                 dma16_chan = -1;\r
137                 if(sscanf(env, "A%x I%d D%d H%d", &base_port, &irq, &dma_chan, &dma16_chan) >= 3) {\r
138                         if(reset_dsp() == 0) {\r
139                                 dsp_ver = get_dsp_version();\r
140                                 print_dsp_info();\r
141                                 *drv = sbdrv;\r
142                                 return 1;\r
143                         }\r
144                 } else {\r
145                         printf("sb_detect: malformed BLASTER environment variable. Fallback to probing.\n");\r
146                 }\r
147         }\r
148 \r
149         for(i=0; i<6; i++) {\r
150                 base_port = 0x200 + ((i + 1) << 4);\r
151                 if(reset_dsp() == 0) {\r
152                         dsp_ver = get_dsp_version();\r
153 \r
154                         if(dsp_ver >= 0x400) {\r
155                                 if(dsp4_detect_irq() == -1) {\r
156                                         printf("sb_detect: failed to configure IRQ\n");\r
157                                         return 0;\r
158                                 }\r
159                                 if(dsp4_detect_dma() == -1) {\r
160                                         printf("sb_detect: failed to configure DMA\n");\r
161                                         return 0;\r
162                                 }\r
163 \r
164                         } else {\r
165                                 /* XXX for old sound blasters, hard-code to IRQ 5 DMA 1 for now */\r
166                                 irq = 5;\r
167                                 dma_chan = 1;\r
168                                 dma16_chan = -1;\r
169 \r
170                                 printf("sb_detect: old sound blaster dsp. assuming: irq 5, dma 1\n");\r
171                         }\r
172                         print_dsp_info();\r
173                         *drv = sbdrv;\r
174 \r
175                         return 1;\r
176                 }\r
177         }\r
178 \r
179         return 0;\r
180 }\r
181 \r
182 static int reset_dsp(void)\r
183 {\r
184         int i;\r
185 \r
186         outp(REG_RESET, 1);\r
187         for(i=0; i<3; i++) iodelay();\r
188         outp(REG_RESET, 0);\r
189 \r
190         for(i=0; i<128; i++) {\r
191                 if(inp(REG_RSTAT) & RSTAT_RDY) {\r
192                         if(inp(REG_RDATA) == 0xaa) {\r
193                                 return 0;\r
194                         }\r
195                 }\r
196         }\r
197 \r
198         return -1;\r
199 }\r
200 \r
201 static void start(int rate, int bits, int nchan)\r
202 {\r
203         uint16_t seg, pmsel;\r
204         uint32_t addr, next64k;\r
205         int size, mode, num_samples, dsp_num_samples;\r
206 \r
207         if(!buffer[1]) {\r
208                 /* allocate a buffer in low memory that doesn't cross 64k boundaries */\r
209                 if(!(seg = dpmi_alloc(BUFSIZE * 2 / 16, &pmsel))) {\r
210                         fprintf(stderr, "SB start: failed to allocate DMA buffer\n");\r
211                         return;\r
212                 }\r
213 \r
214                 addr = (uint32_t)seg << 4;\r
215                 next64k = (addr + 0x10000) & 0xffff0000;\r
216                 if(next64k - addr < BUFSIZE) {\r
217                         addr = next64k;\r
218                 }\r
219 \r
220                 buffer[0] = (void*)addr;\r
221                 buffer[1] = (void*)(addr + BUFSIZE / 2);\r
222                 wrbuf = 0;\r
223         }\r
224 \r
225 \r
226         wrbuf = 0;\r
227         if(!(size = audio_callback(buffer[wrbuf], BUFSIZE))) {\r
228                 return;\r
229         }\r
230         addr = (uint32_t)buffer[wrbuf];\r
231         num_samples = bits == 8 ? size : size / 2;\r
232 \r
233         if(size < BUFSIZE) {\r
234                 auto_init = 0;  /* single transfer mode */\r
235                 dsp_num_samples = num_samples;\r
236         } else {\r
237                 /* Auto-init playback mode:\r
238                  * fill the whole buffer with data, program the DMA for BUFSIZE transfer,\r
239                  * and program the DSP for BUFSIZE/2 transfer. We'll get an interrupt in the\r
240                  * middle, while the DSP uses the upper half, and we'll refill the bottom half.\r
241                  * Then continue ping-ponging the two halves of the buffer until we run out of\r
242                  * data.\r
243                  */\r
244                 auto_init = 1;\r
245                 dsp_num_samples = num_samples / 2;\r
246         }\r
247 \r
248         _disable();\r
249         if(!prev_intr_handler) {\r
250                 prev_intr_handler = _dos_getvect(IRQ_TO_INTR(irq));\r
251         }\r
252         _dos_setvect(IRQ_TO_INTR(irq), intr_handler);\r
253         _enable();\r
254 \r
255         unmask_irq(irq);\r
256 \r
257         cur_bits = bits;\r
258 \r
259         write_dsp(DSP_ENABLE_OUTPUT);\r
260         dma_out(cur_bits == 8 ? dma_chan : dma16_chan, addr, BUFSIZE, DMA_SINGLE | auto_init ? DMA_AUTO : 0);\r
261 \r
262         if(dsp_ver >= 0x400) {\r
263                 int mode = bits == 8 ? 0 : DSP4_MODE_SIGNED;\r
264                 if(nchan > 1) {\r
265                         mode |= DSP4_MODE_STEREO;\r
266                 }\r
267                 start_dsp4(rate, cur_bits, mode, dsp_num_samples);\r
268         } else {\r
269                 if(nchan > 1 && dsp_ver >= 0x300) {\r
270                         set_sbpro_stereo();\r
271                 }\r
272                 start_dsp(rate, cur_bits, dsp_num_samples);\r
273         }\r
274         playing = 1;\r
275 }\r
276 \r
277 static void pause(void)\r
278 {\r
279         write_dsp(cur_bits == 8 ? DSP_PAUSE_DMA8 : DSP_PAUSE_DMA16);\r
280 }\r
281 \r
282 static void cont(void)\r
283 {\r
284         write_dsp(cur_bits == 8 ? DSP_CONT_DMA8 : DSP_CONT_DMA16);\r
285 }\r
286 \r
287 static void stop(void)\r
288 {\r
289         write_dsp(DSP_DISABLE_OUTPUT);\r
290 \r
291         mask_irq(irq);\r
292         /* TODO: don't _enable, restore state */\r
293         _disable();\r
294         _dos_setvect(IRQ_TO_INTR(irq), prev_intr_handler);\r
295         _enable();\r
296 \r
297         write_dsp(cur_bits == 8 ? DSP_END_DMA8 : DSP_END_DMA16);\r
298         playing = 0;\r
299 }\r
300 \r
301 static int isplaying(void)\r
302 {\r
303         return playing;\r
304 }\r
305 \r
306 static void setvolume(int ctl, int vol)\r
307 {\r
308         unsigned char val;\r
309 \r
310         if(VER_MAJOR(dsp_ver) >= 4) {\r
311                 /* DSP 4.x - SB16 */\r
312                 val = vol & 0xf8;\r
313                 if(ctl == AUDIO_PCM) {\r
314                         write_mix(MIX_SB16_VOICE_L, val);\r
315                         write_mix(MIX_SB16_VOICE_R, val);\r
316                 } else {\r
317                         write_mix(MIX_SB16_MASTER_L, val);\r
318                         write_mix(MIX_SB16_MASTER_R, val);\r
319                 }\r
320 \r
321         } else if(VER_MAJOR(dsp_ver) >= 3) {\r
322                 /* DSP 3.x - SBPro */\r
323                 val = vol & 0xe0;\r
324                 val |= val >> 4;\r
325 \r
326                 if(ctl == AUDIO_PCM) {\r
327                         write_mix(MIX_SBPRO_VOICE, val);\r
328                 } else {\r
329                         write_mix(MIX_SBPRO_MASTER, val);\r
330                 }\r
331 \r
332         } else {\r
333                 /* DSP 2.x - SB 2.0 */\r
334                 if(ctl == AUDIO_PCM) {\r
335                         val = (vol >> 5) & 6;\r
336                         write_mix(MIX_VOICE, val);\r
337                 } else {\r
338                         val = (vol >> 4) & 0xe;\r
339                         write_mix(MIX_MASTER, val);\r
340                 }\r
341         }\r
342 }\r
343 \r
344 static int getvolume(int ctl)\r
345 {\r
346         int left, right;\r
347         unsigned char val;\r
348 \r
349         if(dsp_ver >= 0x400) {\r
350                 /* DSP 4.x - SB16 */\r
351                 int lreg, rreg;\r
352                 if(ctl == AUDIO_PCM) {\r
353                         lreg = MIX_SB16_VOICE_L;\r
354                         rreg = MIX_SB16_VOICE_R;\r
355                 } else {        /* MASTER or DEFAULT */\r
356                         lreg = MIX_SB16_MASTER_L;\r
357                         rreg = MIX_SB16_MASTER_R;\r
358                 }\r
359 \r
360                 val = read_mix(lreg);\r
361                 left = (val & 0xf8) | (val >> 5);\r
362                 val = read_mix(rreg);\r
363                 right = (val & 0xf8) | (val >> 5);\r
364 \r
365         } else if(dsp_ver >= 0x300) {\r
366                 /* DSP 3.x - SBPro */\r
367                 val = read_mix(ctl == AUDIO_PCM ? MIX_SBPRO_VOICE : MIX_SBPRO_MASTER);\r
368 \r
369                 /* left is top 3 bits, duplicate twice(-ish) */\r
370                 left = (val & 0xe0) | ((val >> 3) & 0x1c) | (val >> 6);\r
371                 /* right is top 3 bits of the lower nibble */\r
372                 right = (val << 4) | ((val << 1) & 0x1c) | ((val >> 2) & 3);\r
373 \r
374         } else {\r
375                 int volume;\r
376 \r
377                 /* DSP 2.x - SB 2.0 */\r
378                 if(ctl == AUDIO_PCM) {\r
379                         /* voice is in bits 1 and 2 */\r
380                         val = read_mix(MIX_VOICE);\r
381                         volume = (val << 5) | ((val << 3) & 0x30) | ((val << 1) & 0xc) | ((val >> 1) & 3);\r
382                 } else {\r
383                         /* master is in the 3 top bits of the lower nibble */\r
384                         val = read_mix(MIX_MASTER);\r
385                         volume = (val << 4) | ((val << 1) & 0x1c) | ((val >> 2) & 3);\r
386                 }\r
387                 return volume;\r
388         }\r
389 \r
390         return (left + right) >> 1;\r
391 }\r
392 \r
393 static void set_sbpro_stereo(void)\r
394 {\r
395 }\r
396 \r
397 static int start_dsp4(int rate, int bits, unsigned int mode, int num_samples)\r
398 {\r
399         unsigned char cmd = bits == 8 ? DSP4_START_DMA8 : DSP4_START_DMA16;\r
400         if(auto_init) {\r
401                 cmd |= DSP4_AUTO | DSP4_FIFO;\r
402         }\r
403 \r
404         /* set output rate */\r
405         write_dsp(DSP4_OUT_RATE);\r
406         write_dsp(rate >> 8);\r
407         write_dsp(rate & 0xff);\r
408 \r
409         /* program the DSP to start the DMA transfer */\r
410         write_dsp(cmd);\r
411         write_dsp(mode);\r
412         num_samples--;\r
413         write_dsp(num_samples & 0xff);\r
414         write_dsp((num_samples >> 8) & 0xff);\r
415 \r
416         return 0;\r
417 }\r
418 \r
419 static int start_dsp(int rate, int nchan, int num_samples)\r
420 {\r
421         /* set time constant */\r
422         int tcon = 256 - 1000000 / rate;\r
423         write_dsp(DSP_RATE);\r
424         write_dsp(tcon);\r
425 \r
426         if(nchan > 1) {\r
427                 /* stereo */\r
428                 if(dsp_ver < 0x300) return -1;  /* need a sb pro for stereo */\r
429 \r
430                 set_sbpro_stereo();\r
431                 high_speed = rate >= 11025;\r
432         } else {\r
433                 /* mono */\r
434                 high_speed = rate >= 23000;\r
435         }\r
436 \r
437         /* program the DSP to start the DMA transfer */\r
438         return -1;      /* TODO */\r
439 }\r
440 \r
441 \r
442 static void INTERRUPT intr_handler()\r
443 {\r
444         int size;\r
445         void *bptr = buffer[wrbuf];\r
446         uint32_t addr = (uint32_t)bptr;\r
447 \r
448         wrbuf ^= 1;\r
449 \r
450         /* ask for more data */\r
451         if(!(size = audio_callback(bptr, BUFSIZE / 2))) {\r
452                 stop();\r
453         }\r
454 \r
455         /* acknowledge the interrupt */\r
456         if(cur_bits == 8) {\r
457                 inp(REG_INTACK);\r
458         } else {\r
459                 unsigned char istat = read_mix(MIX_SB16_INTSTAT);\r
460                 if(istat & INTSTAT_DMA16) {\r
461                         inp(REG_INT16ACK);\r
462                 }\r
463         }\r
464 \r
465         if(irq > 7) {\r
466                 outp(PIC2_CMD, OCW2_EOI);\r
467         }\r
468         outp(PIC1_CMD, OCW2_EOI);\r
469 }\r
470 \r
471 static void write_dsp(unsigned char val)\r
472 {\r
473         while(inp(REG_WSTAT) & WSTAT_BUSY);\r
474         outp(REG_WDATA, val);\r
475 }\r
476 \r
477 static unsigned char read_dsp(void)\r
478 {\r
479         while((inp(REG_RSTAT) & RSTAT_RDY) == 0);\r
480         return inp(REG_RDATA);\r
481 }\r
482 \r
483 static void write_mix(int reg, unsigned char val)\r
484 {\r
485         outp(REG_MIXPORT, reg);\r
486         outp(REG_MIXDATA, val);\r
487 }\r
488 \r
489 static unsigned char read_mix(int reg)\r
490 {\r
491         outp(REG_MIXPORT, reg);\r
492         return inp(REG_MIXDATA);\r
493 }\r
494 \r
495 static int get_dsp_version(void)\r
496 {\r
497         int major, minor;\r
498 \r
499         write_dsp(DSP_GET_VER);\r
500         major = read_dsp();\r
501         minor = read_dsp();\r
502 \r
503         return (major << 8) | minor;\r
504 }\r
505 \r
506 static int dsp4_detect_irq(void)\r
507 {\r
508         int i, irqsel;\r
509         static int irqtab[] = {2, 5, 7, 10};\r
510 \r
511         irq = 0;\r
512         irqsel = read_mix(MIX_SB16_IRQ_SEL);\r
513         for(i=0; i<4; i++) {\r
514                 if(irqsel & (1 << i)) {\r
515                         irq = irqtab[i];\r
516                         break;\r
517                 }\r
518         }\r
519         if(!irq) {\r
520                 /* try to force IRQ 5 */\r
521                 write_mix(MIX_SB16_IRQ_SEL, 2); /* bit1 selects irq 5 */\r
522 \r
523                 /* re-read to verify */\r
524                 irqsel = read_mix(MIX_SB16_IRQ_SEL);\r
525                 if(irqsel != 2) {\r
526                         return -1;\r
527                 }\r
528                 irq = 5;\r
529         }\r
530 \r
531         return irq;\r
532 }\r
533 \r
534 static int dsp4_detect_dma(void)\r
535 {\r
536         int i, dmasel, tmp;\r
537         static int dmatab[] = {0, 1, -1, 3, -1, 5, 6, 7};\r
538 \r
539         dma_chan = -1;\r
540         dma16_chan = -1;\r
541         dmasel = read_mix(MIX_SB16_DMA_SEL);\r
542         for(i=0; i<4; i++) {\r
543                 if(dmasel & (1 << i)) {\r
544                         dma_chan = dmatab[i];\r
545                         break;\r
546                 }\r
547         }\r
548         for(i=5; i<8; i++) {\r
549                 if(dmasel & (1 << i)) {\r
550                         dma16_chan = dmatab[i];\r
551                         break;\r
552                 }\r
553         }\r
554         if(dma_chan == -1) {\r
555                 /* try to force DMA 1 */\r
556                 dmasel |= 2;\r
557         }\r
558         if(dma16_chan == -1) {\r
559                 /* try to force 16bit DMA 5 */\r
560                 dmasel |= 0x20;\r
561         }\r
562 \r
563         if(dma_chan == -1 || dma16_chan == -1) {\r
564                 write_mix(MIX_SB16_DMA_SEL, dmasel);\r
565 \r
566                 /* re-read to verify */\r
567                 tmp = read_mix(MIX_SB16_DMA_SEL);\r
568                 if(tmp != dmasel) {\r
569                         return -1;\r
570                 }\r
571                 dma_chan = 1;\r
572                 dma16_chan = 5;\r
573         }\r
574 \r
575         return dma_chan;\r
576 }\r
577 \r
578 static void print_dsp_info(void)\r
579 {\r
580         printf("sb_detect: %s (DSP v%d.%02d) port %xh, irq %d", sbname(dsp_ver),\r
581                         VER_MAJOR(dsp_ver), VER_MINOR(dsp_ver), base_port, irq);\r
582         if(VER_MAJOR(dsp_ver) >= 4) {\r
583                 printf(" dma %d/%d\n", dma_chan, dma16_chan);\r
584         } else {\r
585                 printf(" dma %d\n", dma_chan);\r
586         }\r
587 }\r
588 \r
589 #define V(maj, min)     (((maj) << 8) | (min))\r
590 \r
591 static const char *sbname(int ver)\r
592 {\r
593         int major = VER_MAJOR(ver);\r
594         int minor = VER_MINOR(ver);\r
595 \r
596         switch(major) {\r
597         case 1:\r
598                 if(minor == 5) {\r
599                         return "Sound Blaster 1.5";\r
600                 }\r
601                 return "Sound Blaster 1.0";\r
602 \r
603         case 2:\r
604                 if(minor == 1 || minor == 2) {\r
605                         return "Sound Blaster 2.0";\r
606                 }\r
607                 break;\r
608 \r
609         case 3:\r
610                 switch(minor) {\r
611                 case 0:\r
612                         return "Sound Blaster Pro";\r
613                 case 1:\r
614                 case 2:\r
615                         return "Sound Blaster Pro 2";\r
616                 case 5:\r
617                         return "Gallant SC-6000";\r
618                 default:\r
619                         break;\r
620                 }\r
621                 break;\r
622 \r
623         case 4:\r
624                 switch(minor) {\r
625                 case 4:\r
626                 case 5:\r
627                         return "Sound Blaster 16";\r
628                 case 11:\r
629                         return "Sound Blaster 16 SCSI-2";\r
630                 case 12:\r
631                         return "Sound Blaster AWE 32";\r
632                 case 13:\r
633                         return "Sound Blaster ViBRA16C";\r
634                 case 16:\r
635                         return "Sound Blaster AWE 64";\r
636                 default:\r
637                         break;\r
638                 }\r
639                 break;\r
640         }\r
641 \r
642         return "Unknown Sound Blaster";\r
643 }\r