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