2046f915d515403865417373ac5642d88d390469
[dos_sbtest] / src / au_sb.c
1 #include <stdio.h>
2 #include <dos.h>
3 #include <pc.h>
4 #include <go32.h>
5 #include <dpmi.h>
6 #include <sys/nearptr.h>
7 #include "audio.h"
8 #include "au_sb.h"
9 #include "dma.h"
10
11 #define IRQ_TO_INTR(x)  ((x) + 8)
12
13 /* PIC command and data ports */
14 #define PIC1_CMD        0x20
15 #define PIC1_DATA       0x21
16 #define PIC2_CMD        0xa0
17 #define PIC2_DATA       0xa1
18 /* PIC operation command word 2 bits */
19 #define OCW2_EOI        (1 << 5)
20
21 #define REG_MIXPORT             (base_port + 0x4)
22 #define REG_MIXDATA             (base_port + 0x5)
23 #define REG_RESET               (base_port + 0x6)
24 #define REG_RDATA               (base_port + 0xa)
25 #define REG_WDATA               (base_port + 0xc)
26 #define REG_WSTAT               (base_port + 0xc)
27 #define REG_RSTAT               (base_port + 0xe)
28 #define REG_INTACK              (base_port + 0xe)
29 #define REG_INT16ACK    (base_port + 0xf)
30
31 #define WSTAT_BUSY              0x80
32 #define RSTAT_RDY               0x80
33
34 #define CMD_RATE                        0x40
35 #define CMD_SB16_OUT_RATE       0x41
36 #define CMD_SB16_IN_RATE        0x42
37 #define CMD_GET_VER                     0xe1
38
39 /* start DMA playback/recording. combine with fifo/auto/input flags */
40 #define CMD_START_DMA8          0xc0
41 #define CMD_START_DMA16         0xb0
42 #define CMD_FIFO                        0x02
43 #define CMD_AUTO                        0x04
44 #define CMD_INPUT                       0x08
45
46 /* immediately pause/continue */
47 #define CMD_PAUSE_DMA8          0xd0
48 #define CMD_ENABLE_OUTPUT       0xd1
49 #define CMD_DISABLE_OUTPUT      0xd3
50 #define CMD_CONT_DMA8           0xd4
51 #define CMD_PAUSE_DMA16         0xd5
52 #define CMD_CONT_DMA16          0xd6
53
54 /* end the playback at the end of the current buffer */
55 #define CMD_END_DMA16           0xd9
56 #define CMD_END_DMA8            0xda
57
58 /* transfer mode commands */
59 #define CMD_MODE_SIGNED         0x10
60 #define CMD_MODE_STEREO         0x20
61
62 /* mixer registers */
63 #define MIX_IRQ_SEL                     0x80
64 #define MIX_DMA_SEL                     0x81
65
66 #define VER_MAJOR(x)    ((x) >> 8)
67 #define VER_MINOR(x)    ((x) & 0xff)
68
69 /* delay for about 1us */
70 #define iodelay()       outportb(0x80, 0)
71
72 static void intr_handler();
73 static void start_dma_transfer(uint32_t addr, int size);
74 static void write_dsp(unsigned char val);
75 static unsigned char read_dsp(void);
76 static void write_mix(unsigned char val, int reg);
77 static unsigned char read_mix(int reg);
78 static int get_dsp_version(void);
79 static int sb16_detect_irq(void);
80 static int sb16_detect_dma(void);
81 static const char *sbname(int ver);
82
83 extern unsigned char low_mem_buffer[];
84
85 static int base_port;
86 static int irq, dma_chan, dma16_chan;
87 static int sb16;
88 static void *buffer;
89 static int xfer_mode;
90
91 int sb_detect(void)
92 {
93         int i, ver;
94
95         for(i=0; i<6; i++) {
96                 base_port = 0x200 + ((i + 1) << 4);
97                 if(sb_reset_dsp() == 0) {
98                         ver = get_dsp_version();
99                         sb16 = VER_MAJOR(ver) >= 4;
100
101                         if(sb16) {
102                                 if(sb16_detect_irq() == -1) {
103                                         printf("sb_detect: failed to configure IRQ\n");
104                                         return 0;
105                                 }
106                                 if(sb16_detect_dma() == -1) {
107                                         printf("sb_detect: failed to configure DMA\n");
108                                         return 0;
109                                 }
110
111                                 printf("sb_detect: found %s (DSP v%d.%02d) at port %xh, irq %d, dma %d/%d\n",
112                                                 sbname(ver), VER_MAJOR(ver), VER_MINOR(ver),
113                                                 base_port, irq, dma_chan, dma16_chan);
114
115                         } else {
116                                 /* XXX for old sound blasters, hard-code to IRQ 5 DMA 1 for now */
117                                 irq = 5;
118                                 dma_chan = 1;
119                                 dma16_chan = -1;
120
121                                 printf("sb_detect: found %s (DSP v%d.%02d) at port %xh\n", sbname(ver),
122                                                 VER_MAJOR(ver), VER_MINOR(ver), base_port);
123                                 printf("sb_detect: old sound blaster dsp. assuming: irq 5, dma 1\n");
124                         }
125
126                         return 1;
127                 }
128         }
129
130         return 0;
131 }
132
133 int sb_reset_dsp(void)
134 {
135         int i;
136
137         outportb(REG_RESET, 1);
138         for(i=0; i<3; i++) iodelay();
139         outportb(REG_RESET, 0);
140
141         for(i=0; i<128; i++) {
142                 if(inportb(REG_RSTAT) & RSTAT_RDY) {
143                         if(inportb(REG_RDATA) == 0xaa) {
144                                 return 0;
145                         }
146                 }
147         }
148
149         return -1;
150 }
151
152 void sb_set_output_rate(int rate)
153 {
154         if(sb16) {
155                 write_dsp(CMD_SB16_OUT_RATE);
156                 write_dsp(rate >> 8);
157                 write_dsp(rate & 0xff);
158         } else {
159                 int tcon = 256 - 1000000 / rate;
160                 write_dsp(CMD_RATE);
161                 write_dsp(tcon);
162         }
163 }
164
165 void *sb_buffer(int *size)
166 {
167         *size = 65536;
168         return buffer;
169 }
170
171 void sb_start(int rate, int nchan)
172 {
173         int seg, pmsel;
174         uint32_t addr;
175         int size;
176         _go32_dpmi_seginfo intr;
177
178         if(!buffer) {
179                 __djgpp_nearptr_enable();
180
181                 /* allocate a 64k-aligned 64k buffer in low memory */
182                 if((seg = __dpmi_allocate_dos_memory(65536 * 2 / 16, &pmsel)) <= 0) {
183                         fprintf(stderr, "sb_start: failed to allocate DMA buffer\n");
184                         return;
185                 }
186
187                 printf("allocated seg: %d, addr: %lx\n", seg, (unsigned long)seg << 4);
188
189                 addr = ((uint32_t)(seg << 4) + 0xffff) & 0xffff0000;
190                 printf("aligned: %lx\n", (unsigned long)addr);
191                 buffer = (void*)(addr - __djgpp_base_address);
192         } else {
193                 addr = (uint32_t)buffer;
194         }
195
196         xfer_mode = CMD_MODE_SIGNED;
197         if(nchan > 1) {
198                 xfer_mode |= CMD_MODE_STEREO;
199         }
200
201         if(!(size = audio_callback(buffer, 65536))) {
202                 return;
203         }
204
205         intr.pm_offset = (intptr_t)intr_handler;
206         intr.pm_selector = _go32_my_cs();
207         _disable();
208         _go32_dpmi_set_protected_mode_interrupt_vector(IRQ_TO_INTR(irq), &intr);
209         _enable();
210
211         sb_set_output_rate(rate);
212         start_dma_transfer(addr, size);
213         write_dsp(CMD_ENABLE_OUTPUT);
214 }
215
216 void sb_pause(void)
217 {
218         write_dsp(CMD_PAUSE_DMA8);
219 }
220
221 void sb_continue(void)
222 {
223         write_dsp(CMD_CONT_DMA8);
224 }
225
226 void sb_stop(void)
227 {
228         write_dsp(CMD_END_DMA8);
229         write_dsp(CMD_DISABLE_OUTPUT);
230 }
231
232 void sb_volume(int vol)
233 {
234         /* TODO */
235 }
236
237 static void intr_handler()
238 {
239         int size;
240
241         /* ask for more data */
242         if(!(size = audio_callback(buffer, 65536))) {
243                 sb_stop();
244                 return;
245         }
246         start_dma_transfer((uint32_t)buffer, size);
247
248         /* acknowledge the interrupt */
249         inportb(REG_INTACK);
250
251         if(irq > 7) {
252                 outportb(PIC2_CMD, OCW2_EOI);
253         }
254         outportb(PIC1_CMD, OCW2_EOI);
255 }
256
257 static void start_dma_transfer(uint32_t addr, int size)
258 {
259         /* set up the next DMA transfer */
260         dma_out(dma_chan, addr, size, DMA_SINGLE);
261
262         /* program the DSP to accept the DMA transfer */
263         write_dsp(CMD_START_DMA8 | CMD_FIFO);
264         write_dsp(xfer_mode);
265         size--;
266         write_dsp(size & 0xff);
267         write_dsp((size >> 8) & 0xff);
268 }
269
270 static void write_dsp(unsigned char val)
271 {
272         while(inportb(REG_WSTAT) & WSTAT_BUSY);
273         outportb(REG_WDATA, val);
274 }
275
276 static unsigned char read_dsp(void)
277 {
278         while((inportb(REG_RSTAT) & RSTAT_RDY) == 0);
279         return inportb(REG_RDATA);
280 }
281
282 static void write_mix(unsigned char val, int reg)
283 {
284         outportb(REG_MIXPORT, reg);
285         outportb(REG_MIXDATA, val);
286 }
287
288 static unsigned char read_mix(int reg)
289 {
290         outportb(REG_MIXPORT, reg);
291         return inportb(REG_MIXDATA);
292 }
293
294 static int get_dsp_version(void)
295 {
296         int major, minor;
297
298         write_dsp(CMD_GET_VER);
299         major = read_dsp();
300         minor = read_dsp();
301
302         return (major << 8) | minor;
303 }
304
305 static int sb16_detect_irq(void)
306 {
307         int i, irqsel;
308         static int irqtab[] = {2, 5, 7, 10};
309
310         irq = 0;
311         irqsel = read_mix(MIX_IRQ_SEL);
312         for(i=0; i<4; i++) {
313                 if(irqsel & (1 << i)) {
314                         irq = irqtab[i];
315                         break;
316                 }
317         }
318         if(!irq) {
319                 /* try to force IRQ 5 */
320                 write_mix(2, MIX_IRQ_SEL);      /* bit1 selects irq 5 */
321
322                 /* re-read to verify */
323                 irqsel = read_mix(MIX_IRQ_SEL);
324                 if(irqsel != 2) {
325                         return -1;
326                 }
327                 irq = 5;
328         }
329
330         return irq;
331 }
332
333 static int sb16_detect_dma(void)
334 {
335         int i, dmasel, tmp;
336         static int dmatab[] = {0, 1, -1, 3, -1, 5, 6, 7};
337
338         dma_chan = -1;
339         dma16_chan = -1;
340         dmasel = read_mix(MIX_DMA_SEL);
341         for(i=0; i<4; i++) {
342                 if(dmasel & (1 << i)) {
343                         dma_chan = dmatab[i];
344                         break;
345                 }
346         }
347         for(i=5; i<8; i++) {
348                 if(dmasel & (1 << i)) {
349                         dma16_chan = dmatab[i];
350                         break;
351                 }
352         }
353         if(dma_chan == -1) {
354                 /* try to force DMA 1 */
355                 dmasel |= 2;
356         }
357         if(dma16_chan == -1) {
358                 /* try to force 16bit DMA 5 */
359                 dmasel |= 0x20;
360         }
361
362         if(dma_chan == -1 || dma16_chan == -1) {
363                 write_mix(dmasel, MIX_DMA_SEL);
364
365                 /* re-read to verify */
366                 tmp = read_mix(MIX_DMA_SEL);
367                 if(tmp != dmasel) {
368                         return -1;
369                 }
370                 dma_chan = 1;
371                 dma16_chan = 5;
372         }
373
374         return dma_chan;
375 }
376
377 #define V(maj, min)     (((maj) << 8) | (min))
378
379 static const char *sbname(int ver)
380 {
381         int major = VER_MAJOR(ver);
382         int minor = VER_MINOR(ver);
383
384         switch(major) {
385         case 1:
386                 if(minor == 5) {
387                         return "Sound Blaster 1.5";
388                 }
389                 return "Sound Blaster 1.0";
390
391         case 2:
392                 if(minor == 1 || minor == 2) {
393                         return "Sound Blaster 2.0";
394                 }
395                 break;
396
397         case 3:
398                 switch(minor) {
399                 case 0:
400                         return "Sound Blaster Pro";
401                 case 1:
402                 case 2:
403                         return "Sound Blaster Pro 2";
404                 case 5:
405                         return "Gallant SC-6000";
406                 default:
407                         break;
408                 }
409                 break;
410
411         case 4:
412                 switch(minor) {
413                 case 4:
414                 case 5:
415                         return "Sound Blaster 16";
416                 case 11:
417                         return "Sound Blaster 16 SCSI-2";
418                 case 12:
419                         return "Sound Blaster AWE 32";
420                 case 13:
421                         return "Sound Blaster ViBRA16C";
422                 case 16:
423                         return "Sound Blaster AWE 64";
424                 default:
425                         break;
426                 }
427                 break;
428         }
429
430         return "Unknown Sound Blaster";
431 }