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