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