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