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