sound blaster code works, plus auto-detect IRQ/DMA where possible
[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 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, j, ver, tmp, irqsel, dmasel;
93         static int irqtab[] = {2, 5, 7, 10};
94         static int dmatab[] = {0, 1, -1, 3, -1, 5, 6, 7};
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                                 irq = 0;
104                                 irqsel = read_mix(MIX_IRQ_SEL);
105                                 for(j=0; j<4; j++) {
106                                         if(irqsel & (1 << j)) {
107                                                 irq = irqtab[j];
108                                                 break;
109                                         }
110                                 }
111                                 if(!irq) {
112                                         /* try to force IRQ 5 */
113                                         write_mix(2, MIX_IRQ_SEL);      /* bit1 selects irq 5 */
114
115                                         /* re-read to verify */
116                                         irqsel = read_mix(MIX_IRQ_SEL);
117                                         if(irqsel != 2) {
118                                                 printf("sb_detect: failed to configure IRQ\n");
119                                                 return 0;
120                                         }
121                                         irq = 5;
122                                 }
123
124                                 dma_chan = -1;
125                                 dma16_chan = -1;
126                                 dmasel = read_mix(MIX_DMA_SEL);
127                                 for(j=0; j<4; j++) {
128                                         if(dmasel & (1 << j)) {
129                                                 dma_chan = dmatab[j];
130                                                 break;
131                                         }
132                                 }
133                                 for(j=5; j<8; j++) {
134                                         if(dmasel & (1 << j)) {
135                                                 dma16_chan = dmatab[j];
136                                                 break;
137                                         }
138                                 }
139                                 if(dma_chan == -1) {
140                                         /* try to force DMA 1 */
141                                         dmasel |= 2;
142                                 }
143                                 if(dma16_chan == -1) {
144                                         /* try to force 16bit DMA 5 */
145                                         dmasel |= 0x20;
146                                 }
147
148                                 if(dma_chan == -1 || dma16_chan == -1) {
149                                         write_mix(dmasel, MIX_DMA_SEL);
150
151                                         /* re-read to verify */
152                                         tmp = read_mix(MIX_DMA_SEL);
153                                         if(tmp != dmasel) {
154                                                 printf("sb_detect: failed to configure DMA\n");
155                                                 return 0;
156                                         }
157                                         dma_chan = 1;
158                                         dma16_chan = 5;
159                                 }
160
161                                 printf("sb_detect: found %s (DSP v%d.%02d) at port %xh, irq %d, dma %d/%d\n",
162                                                 sbname(ver), VER_MAJOR(ver), VER_MINOR(ver),
163                                                 base_port, irq, dma_chan, dma16_chan);
164
165                         } else {
166                                 /* XXX for old sound blasters, hard-code to IRQ 5 DMA 1 for now */
167                                 irq = 5;
168                                 dma_chan = 1;
169                                 dma16_chan = -1;
170
171                                 printf("sb_detect: found %s (DSP v%d.%02d) at port %xh\n", sbname(ver),
172                                                 VER_MAJOR(ver), VER_MINOR(ver), base_port);
173                                 printf("sb_detect: old sound blaster dsp. assuming: irq 5, dma 1\n");
174                         }
175
176                         return 1;
177                 }
178         }
179
180         return 0;
181 }
182
183 int sb_reset_dsp(void)
184 {
185         int i;
186
187         outb(1, REG_RESET);
188         for(i=0; i<3; i++) iodelay();
189         outb(0, REG_RESET);
190
191         for(i=0; i<128; i++) {
192                 if(inb(REG_RSTAT) & RSTAT_RDY) {
193                         if(inb(REG_RDATA) == 0xaa) {
194                                 return 0;
195                         }
196                 }
197         }
198
199         return -1;
200 }
201
202 void sb_set_output_rate(int rate)
203 {
204         if(sb16) {
205                 write_dsp(CMD_SB16_OUT_RATE);
206                 write_dsp(rate >> 8);
207                 write_dsp(rate & 0xff);
208         } else {
209                 int tcon = 256 - 1000000 / rate;
210                 write_dsp(CMD_RATE);
211                 write_dsp(tcon);
212         }
213 }
214
215 void *sb_buffer(int *size)
216 {
217         *size = 65536;
218         return buffer;
219 }
220
221 void sb_start(int rate, int nchan)
222 {
223         uint32_t addr;
224         int size;
225
226         /* for now just use the are after boot2. it's only used by the second stage
227          * loader and the VBE init code, none of which should overlap with audio playback.
228          * It's not necessary to use low memory. We can use up to the first 16mb for this,
229          * so if this becomes an issue, I'll make a DMA buffer allocator over 1mb.
230          * start the buffer from the next 64k boundary.
231          */
232         addr = ((uint32_t)low_mem_buffer + 0xffff) & 0xffff0000;
233         buffer = (void*)addr;
234
235         xfer_mode = CMD_MODE_SIGNED;
236         if(nchan > 1) {
237                 xfer_mode |= CMD_MODE_STEREO;
238         }
239
240         if(!(size = audio_callback(buffer, 65536))) {
241                 return;
242         }
243
244         interrupt(IRQ_TO_INTR(irq), intr_handler);
245
246         sb_set_output_rate(rate);
247         start_dma_transfer(addr, size);
248         write_dsp(CMD_ENABLE_OUTPUT);
249 }
250
251 void sb_pause(void)
252 {
253         write_dsp(CMD_PAUSE_DMA8);
254 }
255
256 void sb_continue(void)
257 {
258         write_dsp(CMD_CONT_DMA8);
259 }
260
261 void sb_stop(void)
262 {
263         write_dsp(CMD_END_DMA8);
264         write_dsp(CMD_DISABLE_OUTPUT);
265 }
266
267 void sb_volume(int vol)
268 {
269         /* TODO */
270 }
271
272 static void intr_handler()
273 {
274         int size;
275
276         /* ask for more data */
277         if(!(size = audio_callback(buffer, 65536))) {
278                 sb_stop();
279                 return;
280         }
281         start_dma_transfer((uint32_t)buffer, size);
282
283         /* acknowledge the interrupt */
284         inb(REG_INTACK);
285 }
286
287 static void start_dma_transfer(uint32_t addr, int size)
288 {
289         /* set up the next DMA transfer */
290         dma_out(dma_chan, addr, size, DMA_SINGLE);
291
292         /* program the DSP to accept the DMA transfer */
293         write_dsp(CMD_START_DMA8 | CMD_FIFO);
294         write_dsp(xfer_mode);
295         size--;
296         write_dsp(size & 0xff);
297         write_dsp((size >> 8) & 0xff);
298 }
299
300 static void write_dsp(unsigned char val)
301 {
302         while(inb(REG_WSTAT) & WSTAT_BUSY);
303         outb(val, REG_WDATA);
304 }
305
306 static unsigned char read_dsp(void)
307 {
308         while((inb(REG_RSTAT) & RSTAT_RDY) == 0);
309         return inb(REG_RDATA);
310 }
311
312 static void write_mix(unsigned char val, int reg)
313 {
314         outb(reg, REG_MIXPORT);
315         outb(val, REG_MIXDATA);
316 }
317
318 static unsigned char read_mix(int reg)
319 {
320         outb(reg, REG_MIXPORT);
321         return inb(REG_MIXDATA);
322 }
323
324 static int get_dsp_version(void)
325 {
326         int major, minor;
327
328         write_dsp(CMD_GET_VER);
329         major = read_dsp();
330         minor = read_dsp();
331
332         return (major << 8) | minor;
333 }
334
335 #define V(maj, min)     (((maj) << 8) | (min))
336
337 static const char *sbname(int ver)
338 {
339         int major = VER_MAJOR(ver);
340         int minor = VER_MINOR(ver);
341
342         switch(major) {
343         case 1:
344                 if(minor == 5) {
345                         return "Sound Blaster 1.5";
346                 }
347                 return "Sound Blaster 1.0";
348
349         case 2:
350                 if(minor == 1 || minor == 2) {
351                         return "Sound Blaster 2.0";
352                 }
353                 break;
354
355         case 3:
356                 switch(minor) {
357                 case 0:
358                         return "Sound Blaster Pro";
359                 case 1:
360                 case 2:
361                         return "Sound Blaster Pro 2";
362                 case 5:
363                         return "Gallant SC-6000";
364                 default:
365                         break;
366                 }
367                 break;
368
369         case 4:
370                 switch(minor) {
371                 case 4:
372                 case 5:
373                         return "Sound Blaster 16";
374                 case 11:
375                         return "Sound Blaster 16 SCSI-2";
376                 case 12:
377                         return "Sound Blaster AWE 32";
378                 case 13:
379                         return "Sound Blaster ViBRA16C";
380                 case 16:
381                         return "Sound Blaster AWE 64";
382                 default:
383                         break;
384                 }
385                 break;
386         }
387
388         return "Unknown Sound Blaster";
389 }