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