fixed by preloading samples. the audio callback is being called from the
[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 "au_sb.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 static void INTERRUPT intr_handler();\r
74 static void start_dsp4(int bits, unsigned int mode, int num_samples);\r
75 static void start_dsp(int nchan, int num_samples);\r
76 static void write_dsp(unsigned char val);\r
77 static unsigned char read_dsp(void);\r
78 static void write_mix(unsigned char val, int reg);\r
79 static unsigned char read_mix(int reg);\r
80 static int get_dsp_version(void);\r
81 static int dsp4_detect_irq(void);\r
82 static int dsp4_detect_dma(void);\r
83 static void print_dsp_info(void);\r
84 static const char *sbname(int ver);\r
85 \r
86 static int base_port;\r
87 static int irq, dma_chan, dma16_chan, dsp_ver;\r
88 static int dsp4;\r
89 \r
90 static void *buffer[2];\r
91 static volatile int wrbuf;\r
92 \r
93 static volatile int isplaying;\r
94 static int cur_bits, cur_mode, auto_init;\r
95 \r
96 static void (INTERRUPT *prev_intr_handler)();\r
97 \r
98 \r
99 int sb_detect(void)\r
100 {\r
101         int i;\r
102         char *env;\r
103 \r
104         if((env = getenv("BLASTER"))) {\r
105                 dma16_chan = -1;\r
106                 if(sscanf(env, "A%x I%d D%d H%d", &base_port, &irq, &dma_chan, &dma16_chan) >= 3) {\r
107                         if(sb_reset_dsp() == 0) {\r
108                                 dsp_ver = get_dsp_version();\r
109                                 dsp4 = VER_MAJOR(dsp_ver) >= 4 ? 1 : 0;\r
110                                 print_dsp_info();\r
111                                 return 1;\r
112                         }\r
113                 } else {\r
114                         printf("sb_detect: malformed BLASTER environment variable. Fallback to probing.\n");\r
115                 }\r
116         }\r
117 \r
118         for(i=0; i<6; i++) {\r
119                 base_port = 0x200 + ((i + 1) << 4);\r
120                 if(sb_reset_dsp() == 0) {\r
121                         dsp_ver = get_dsp_version();\r
122                         dsp4 = VER_MAJOR(dsp_ver) >= 4 ? 1 : 0;\r
123 \r
124                         if(dsp4) {\r
125                                 if(dsp4_detect_irq() == -1) {\r
126                                         printf("sb_detect: failed to configure IRQ\n");\r
127                                         return 0;\r
128                                 }\r
129                                 if(dsp4_detect_dma() == -1) {\r
130                                         printf("sb_detect: failed to configure DMA\n");\r
131                                         return 0;\r
132                                 }\r
133 \r
134                         } else {\r
135                                 /* XXX for old sound blasters, hard-code to IRQ 5 DMA 1 for now */\r
136                                 irq = 5;\r
137                                 dma_chan = 1;\r
138                                 dma16_chan = -1;\r
139 \r
140                                 printf("sb_detect: old sound blaster dsp. assuming: irq 5, dma 1\n");\r
141                         }\r
142                         print_dsp_info();\r
143 \r
144                         return 1;\r
145                 }\r
146         }\r
147 \r
148         return 0;\r
149 }\r
150 \r
151 int sb_reset_dsp(void)\r
152 {\r
153         int i;\r
154 \r
155         outp(REG_RESET, 1);\r
156         for(i=0; i<3; i++) iodelay();\r
157         outp(REG_RESET, 0);\r
158 \r
159         for(i=0; i<128; i++) {\r
160                 if(inp(REG_RSTAT) & RSTAT_RDY) {\r
161                         if(inp(REG_RDATA) == 0xaa) {\r
162                                 return 0;\r
163                         }\r
164                 }\r
165         }\r
166 \r
167         return -1;\r
168 }\r
169 \r
170 void sb_set_output_rate(int rate)\r
171 {\r
172         if(dsp4) {\r
173                 write_dsp(DSP4_OUT_RATE);\r
174                 write_dsp(rate >> 8);\r
175                 write_dsp(rate & 0xff);\r
176         } else {\r
177                 int tcon = 256 - 1000000 / rate;\r
178                 write_dsp(DSP_RATE);\r
179                 write_dsp(tcon);\r
180         }\r
181 }\r
182 \r
183 void *sb_buffer(int *size)\r
184 {\r
185         *size = BUFSIZE / 2;\r
186         return buffer[wrbuf];\r
187 }\r
188 \r
189 void sb_start(int rate, int bits, int nchan)\r
190 {\r
191         uint16_t seg, pmsel;\r
192         uint32_t addr, next64k;\r
193         int size, num_samples;\r
194 \r
195         if(!buffer[1]) {\r
196                 /* allocate a buffer in low memory that doesn't cross 64k boundaries */\r
197                 if(!(seg = dpmi_alloc(BUFSIZE * 2 / 16, &pmsel))) {\r
198                         fprintf(stderr, "sb_start: failed to allocate DMA buffer\n");\r
199                         return;\r
200                 }\r
201 \r
202                 printf("DBG allocated seg: %x, addr: %lx\n", (unsigned int)seg,\r
203                                 (unsigned long)seg << 4);\r
204 \r
205                 addr = (uint32_t)seg << 4;\r
206                 next64k = (addr + 0x10000) & 0xffff0000;\r
207                 if(next64k - addr < BUFSIZE) {\r
208                         addr = next64k;\r
209                 }\r
210 \r
211                 printf("DBG aligned: %lx (mid: %lx)\n", (unsigned long)addr, (unsigned long)(addr + BUFSIZE / 2));\r
212                 buffer[0] = (void*)addr;\r
213                 buffer[1] = (void*)(addr + BUFSIZE / 2);\r
214                 wrbuf = 0;\r
215         }\r
216 \r
217         /* Auto-init playback mode:\r
218          * fill the whole buffer with data, program the DMA for BUFSIZE transfer,\r
219          * and program the DSP for BUFSIZE/2 transfer. We'll get an interrupt in the\r
220          * middle, while the DSP uses the upper half, and we'll refill the bottom half.\r
221          * Then continue ping-ponging the two halves of the buffer until we run out of\r
222          * data.\r
223          */\r
224         auto_init = 1;\r
225 \r
226         /* TODO: if size <= BUFSIZE, do a single transfer instead */\r
227         wrbuf = 0;\r
228         if(!(size = audio_callback(buffer[wrbuf], BUFSIZE))) {\r
229                 return;\r
230         }\r
231         addr = (uint32_t)buffer[wrbuf];\r
232         num_samples = bits == 8 ? size : size / 2;\r
233 \r
234         _disable();\r
235         if(!prev_intr_handler) {\r
236                 prev_intr_handler = _dos_getvect(IRQ_TO_INTR(irq));\r
237         }\r
238         printf("setting interrupt vector: %d\n", IRQ_TO_INTR(irq));\r
239         _dos_setvect(IRQ_TO_INTR(irq), intr_handler);\r
240         _enable();\r
241 \r
242         unmask_irq(irq);\r
243 \r
244         cur_bits = bits;\r
245         cur_mode = bits == 8 ? 0 : DSP4_MODE_SIGNED;\r
246         if(nchan > 1) {\r
247                 cur_mode |= DSP4_MODE_STEREO;\r
248         }\r
249 \r
250         write_dsp(DSP_ENABLE_OUTPUT);\r
251         dma_out(cur_bits == 8 ? dma_chan : dma16_chan, addr, BUFSIZE, DMA_SINGLE | DMA_AUTO);\r
252         sb_set_output_rate(rate);\r
253         start_dsp4(cur_bits, cur_mode, num_samples / 2);\r
254         isplaying = 1;\r
255 }\r
256 \r
257 static void start_dsp4(int bits, unsigned int mode, int num_samples)\r
258 {\r
259         unsigned char cmd = bits == 8 ? DSP4_START_DMA8 : DSP4_START_DMA16;\r
260         cmd |= DSP4_AUTO | DSP4_FIFO;\r
261 \r
262         /* program the DSP to start the DMA transfer */\r
263         write_dsp(cmd);\r
264         write_dsp(mode);\r
265         num_samples--;\r
266         write_dsp(num_samples & 0xff);\r
267         write_dsp((num_samples >> 8) & 0xff);\r
268 }\r
269 \r
270 static void start_dsp(int nchan, int num_samples)\r
271 {\r
272         /* program the DSP to start the DMA transfer */\r
273         /* TODO */\r
274 }\r
275 \r
276 void sb_pause(void)\r
277 {\r
278         write_dsp(cur_bits == 8 ? DSP_PAUSE_DMA8 : DSP_PAUSE_DMA16);\r
279 }\r
280 \r
281 void sb_continue(void)\r
282 {\r
283         write_dsp(cur_bits == 8 ? DSP_CONT_DMA8 : DSP_CONT_DMA16);\r
284 }\r
285 \r
286 void sb_stop(void)\r
287 {\r
288         write_dsp(DSP_DISABLE_OUTPUT);\r
289 \r
290         mask_irq(irq);\r
291         /* TODO: don't _enable, restore state */\r
292         _disable();\r
293         _dos_setvect(IRQ_TO_INTR(irq), prev_intr_handler);\r
294         _enable();\r
295 \r
296         write_dsp(cur_bits == 8 ? DSP_END_DMA8 : DSP_END_DMA16);\r
297         isplaying = 0;\r
298 }\r
299 \r
300 int sb_isplaying(void)\r
301 {\r
302         return isplaying;\r
303 }\r
304 \r
305 void sb_volume(int vol)\r
306 {\r
307         /* TODO */\r
308 }\r
309 \r
310 static void INTERRUPT intr_handler()\r
311 {\r
312         int size;\r
313         void *bptr = buffer[wrbuf];\r
314         uint32_t addr = (uint32_t)bptr;\r
315 \r
316         wrbuf ^= 1;\r
317 \r
318         /* ask for more data */\r
319         if(!(size = audio_callback(bptr, BUFSIZE / 2))) {\r
320                 sb_stop();\r
321         }\r
322 \r
323         /* acknowledge the interrupt */\r
324         if(cur_bits == 8) {\r
325                 inp(REG_INTACK);\r
326         } else {\r
327                 /*\r
328                 unsigned char istat;\r
329                 outp(REG_MIXPORT, 0x82);\r
330                 istat = inp(REG_MIXDATA);\r
331                 if(istat & 2) {\r
332                 */\r
333                         inp(REG_INT16ACK);\r
334                 //}\r
335         }\r
336 \r
337         if(irq > 7) {\r
338                 outp(PIC2_CMD, OCW2_EOI);\r
339         }\r
340         outp(PIC1_CMD, OCW2_EOI);\r
341 }\r
342 \r
343 static void write_dsp(unsigned char val)\r
344 {\r
345         while(inp(REG_WSTAT) & WSTAT_BUSY);\r
346         outp(REG_WDATA, val);\r
347 }\r
348 \r
349 static unsigned char read_dsp(void)\r
350 {\r
351         while((inp(REG_RSTAT) & RSTAT_RDY) == 0);\r
352         return inp(REG_RDATA);\r
353 }\r
354 \r
355 static void write_mix(unsigned char val, int reg)\r
356 {\r
357         outp(REG_MIXPORT, reg);\r
358         outp(REG_MIXDATA, val);\r
359 }\r
360 \r
361 static unsigned char read_mix(int reg)\r
362 {\r
363         outp(REG_MIXPORT, reg);\r
364         return inp(REG_MIXDATA);\r
365 }\r
366 \r
367 static int get_dsp_version(void)\r
368 {\r
369         int major, minor;\r
370 \r
371         write_dsp(DSP_GET_VER);\r
372         major = read_dsp();\r
373         minor = read_dsp();\r
374 \r
375         return (major << 8) | minor;\r
376 }\r
377 \r
378 static int dsp4_detect_irq(void)\r
379 {\r
380         int i, irqsel;\r
381         static int irqtab[] = {2, 5, 7, 10};\r
382 \r
383         irq = 0;\r
384         irqsel = read_mix(MIX_SB16_IRQ_SEL);\r
385         for(i=0; i<4; i++) {\r
386                 if(irqsel & (1 << i)) {\r
387                         irq = irqtab[i];\r
388                         break;\r
389                 }\r
390         }\r
391         if(!irq) {\r
392                 /* try to force IRQ 5 */\r
393                 write_mix(2, MIX_SB16_IRQ_SEL); /* bit1 selects irq 5 */\r
394 \r
395                 /* re-read to verify */\r
396                 irqsel = read_mix(MIX_SB16_IRQ_SEL);\r
397                 if(irqsel != 2) {\r
398                         return -1;\r
399                 }\r
400                 irq = 5;\r
401         }\r
402 \r
403         return irq;\r
404 }\r
405 \r
406 static int dsp4_detect_dma(void)\r
407 {\r
408         int i, dmasel, tmp;\r
409         static int dmatab[] = {0, 1, -1, 3, -1, 5, 6, 7};\r
410 \r
411         dma_chan = -1;\r
412         dma16_chan = -1;\r
413         dmasel = read_mix(MIX_SB16_DMA_SEL);\r
414         for(i=0; i<4; i++) {\r
415                 if(dmasel & (1 << i)) {\r
416                         dma_chan = dmatab[i];\r
417                         break;\r
418                 }\r
419         }\r
420         for(i=5; i<8; i++) {\r
421                 if(dmasel & (1 << i)) {\r
422                         dma16_chan = dmatab[i];\r
423                         break;\r
424                 }\r
425         }\r
426         if(dma_chan == -1) {\r
427                 /* try to force DMA 1 */\r
428                 dmasel |= 2;\r
429         }\r
430         if(dma16_chan == -1) {\r
431                 /* try to force 16bit DMA 5 */\r
432                 dmasel |= 0x20;\r
433         }\r
434 \r
435         if(dma_chan == -1 || dma16_chan == -1) {\r
436                 write_mix(dmasel, MIX_SB16_DMA_SEL);\r
437 \r
438                 /* re-read to verify */\r
439                 tmp = read_mix(MIX_SB16_DMA_SEL);\r
440                 if(tmp != dmasel) {\r
441                         return -1;\r
442                 }\r
443                 dma_chan = 1;\r
444                 dma16_chan = 5;\r
445         }\r
446 \r
447         return dma_chan;\r
448 }\r
449 \r
450 static void print_dsp_info(void)\r
451 {\r
452         int master[2], voice[2];\r
453         int val;\r
454 \r
455         printf("sb_detect: %s (DSP v%d.%02d) port %xh, irq %d", sbname(dsp_ver),\r
456                         VER_MAJOR(dsp_ver), VER_MINOR(dsp_ver), base_port, irq);\r
457         if(VER_MAJOR(dsp_ver) >= 4) {\r
458                 printf(" dma %d/%d\n", dma_chan, dma16_chan);\r
459         } else {\r
460                 printf(" dma %d\n", dma_chan);\r
461         }\r
462 \r
463         if(VER_MAJOR(dsp_ver) >= 4) {\r
464                 master[0] = 100 * (int)(read_mix(MIX_SB16_MASTER_L) >> 3) / 31;\r
465                 master[1] = 100 * (int)(read_mix(MIX_SB16_MASTER_R) >> 3) / 31;\r
466                 voice[0] = 100 * (int)(read_mix(MIX_SB16_VOICE_L) >> 3) / 31;\r
467                 voice[1] = 100 * (int)(read_mix(MIX_SB16_VOICE_R) >> 3) / 31;\r
468         } else if(VER_MAJOR(dsp_ver) >= 3) {\r
469                 val = read_mix(MIX_SBPRO_MASTER);\r
470                 master[0] = 100 * (val >> 5) / 7;\r
471                 master[1] = 100 * ((val >> 1) & 7) / 7;\r
472                 val = read_mix(MIX_SBPRO_VOICE);\r
473                 voice[0] = 100 * (val >> 5) / 7;\r
474                 voice[1] = 100 * ((val >> 1) & 7) / 7;\r
475         } else {\r
476                 val = read_mix(MIX_MASTER);\r
477                 master[0] = master[1] = 100 * ((val >> 1) & 7) / 7;\r
478                 val = read_mix(MIX_VOICE);\r
479                 voice[0] = voice[1] = 100 * ((val >> 1) & 3) / 3;\r
480         }\r
481         printf("  mixer: master(%d|%d) voice(%d|%d)\n", master[0], master[1],\r
482                         voice[0], voice[1]);\r
483 }\r
484 \r
485 #define V(maj, min)     (((maj) << 8) | (min))\r
486 \r
487 static const char *sbname(int ver)\r
488 {\r
489         int major = VER_MAJOR(ver);\r
490         int minor = VER_MINOR(ver);\r
491 \r
492         switch(major) {\r
493         case 1:\r
494                 if(minor == 5) {\r
495                         return "Sound Blaster 1.5";\r
496                 }\r
497                 return "Sound Blaster 1.0";\r
498 \r
499         case 2:\r
500                 if(minor == 1 || minor == 2) {\r
501                         return "Sound Blaster 2.0";\r
502                 }\r
503                 break;\r
504 \r
505         case 3:\r
506                 switch(minor) {\r
507                 case 0:\r
508                         return "Sound Blaster Pro";\r
509                 case 1:\r
510                 case 2:\r
511                         return "Sound Blaster Pro 2";\r
512                 case 5:\r
513                         return "Gallant SC-6000";\r
514                 default:\r
515                         break;\r
516                 }\r
517                 break;\r
518 \r
519         case 4:\r
520                 switch(minor) {\r
521                 case 4:\r
522                 case 5:\r
523                         return "Sound Blaster 16";\r
524                 case 11:\r
525                         return "Sound Blaster 16 SCSI-2";\r
526                 case 12:\r
527                         return "Sound Blaster AWE 32";\r
528                 case 13:\r
529                         return "Sound Blaster ViBRA16C";\r
530                 case 16:\r
531                         return "Sound Blaster AWE 64";\r
532                 default:\r
533                         break;\r
534                 }\r
535                 break;\r
536         }\r
537 \r
538         return "Unknown Sound Blaster";\r
539 }\r