sound blaster code doesn't work yet
[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_CONT_DMA8           0xd4
53 #define CMD_PAUSE_DMA16         0xd5
54 #define CMD_CONT_DMA16          0xd6
55
56 /* end the playback at the end of the current buffer */
57 #define CMD_END_DMA16           0xd9
58 #define CMD_END_DMA8            0xda
59
60 /* transfer mode commands */
61 #define CMD_MODE_SIGNED         0x10
62 #define CMD_MODE_STEREO         0x20
63
64 #define VER_MAJOR(x)    ((x) >> 8)
65 #define VER_MINOR(x)    ((x) & 0xff)
66
67 static void intr_handler();
68 static void start_dma_transfer(uint32_t addr, int size);
69 static void write_dsp(unsigned char val);
70 static unsigned char read_dsp(void);
71 static int get_dsp_version(void);
72 static const char *sbname(int ver);
73
74 extern unsigned char low_mem_buffer[];
75
76 static int base_port;
77 static int irq, dma_chan;
78 static int sb16;
79 static void *buffer;
80 static int xfer_mode;
81
82 int sb_detect(void)
83 {
84         int i, ver;
85
86         for(i=0; i<6; i++) {
87                 base_port = 0x200 + ((i + 1) << 4);
88                 if(sb_reset_dsp() == 0) {
89                         ver = get_dsp_version();
90                         sb16 = VER_MAJOR(ver) >= 4;
91                         printf("sb_detect: found %s (DSP v%d.%02d) at port %xh\n", sbname(ver),
92                                         VER_MAJOR(ver), VER_MINOR(ver), base_port);
93                         return 1;
94                 }
95         }
96
97         /* TODO: detect these somehow? */
98         irq = 5;
99         dma_chan = 1;
100
101         return 0;
102 }
103
104 int sb_reset_dsp(void)
105 {
106         int i;
107
108         outb(1, REG_RESET);
109         for(i=0; i<3; i++) iodelay();
110         outb(0, REG_RESET);
111
112         for(i=0; i<128; i++) {
113                 if(inb(REG_RSTAT) & RSTAT_RDY) {
114                         if(inb(REG_RDATA) == 0xaa) {
115                                 return 0;
116                         }
117                 }
118         }
119
120         return -1;
121 }
122
123 void sb_set_output_rate(int rate)
124 {
125         if(sb16) {
126                 write_dsp(CMD_SB16_OUT_RATE);
127                 write_dsp(rate >> 8);
128                 write_dsp(rate & 0xff);
129         } else {
130                 int tcon = 256 - 1000000 / rate;
131                 write_dsp(CMD_RATE);
132                 write_dsp(tcon);
133         }
134 }
135
136 void *sb_buffer(int *size)
137 {
138         *size = 65536;
139         return buffer;
140 }
141
142 void sb_start(int rate, int nchan)
143 {
144         uint32_t addr;
145         int size;
146
147         /* for now just use the are after boot2. it's only used by the second stage
148          * loader and the VBE init code, none of which should overlap with audio playback.
149          * It's not necessary to use low memory. We can use up to the first 16mb for this,
150          * so if this becomes an issue, I'll make a DMA buffer allocator over 1mb.
151          * start the buffer from the next 64k boundary.
152          */
153         addr = ((uint32_t)low_mem_buffer + 0xffff) & 0xffff0000;
154         buffer = (void*)addr;
155
156         xfer_mode = CMD_MODE_SIGNED;
157         if(nchan > 1) {
158                 xfer_mode |= CMD_MODE_STEREO;
159         }
160
161         if(!(size = audio_callback(buffer, 65536))) {
162                 return;
163         }
164
165         interrupt(IRQ_TO_INTR(irq), intr_handler);
166
167         start_dma_transfer(addr, size);
168 }
169
170 void sb_pause(void)
171 {
172         write_dsp(CMD_PAUSE_DMA8);
173 }
174
175 void sb_continue(void)
176 {
177         write_dsp(CMD_CONT_DMA8);
178 }
179
180 void sb_stop(void)
181 {
182         write_dsp(CMD_END_DMA8);
183 }
184
185 void sb_volume(int vol)
186 {
187         /* TODO */
188 }
189
190 static void intr_handler()
191 {
192         int size;
193
194         /* ask for more data */
195         if(!(size = audio_callback(buffer, 65536))) {
196                 sb_stop();
197                 return;
198         }
199         start_dma_transfer((uint32_t)buffer, size);
200
201         /* acknowledge the interrupt */
202         inb(REG_INTACK);
203 }
204
205 static void start_dma_transfer(uint32_t addr, int size)
206 {
207         /* set up the next DMA transfer */
208         dma_out(dma_chan, addr, size, DMA_SINGLE);
209
210         /* program the DSP to accept the DMA transfer */
211         write_dsp(CMD_START_DMA8 | CMD_FIFO);
212         write_dsp(xfer_mode);
213         size--;
214         write_dsp(size & 0xff);
215         write_dsp((size >> 8) & 0xff);
216 }
217
218 static void write_dsp(unsigned char val)
219 {
220         while(inb(REG_WSTAT) & WSTAT_BUSY);
221         outb(val, REG_WDATA);
222 }
223
224 static unsigned char read_dsp(void)
225 {
226         while((inb(REG_RSTAT) & RSTAT_RDY) == 0);
227         return inb(REG_RDATA);
228 }
229
230 static int get_dsp_version(void)
231 {
232         int major, minor;
233
234         write_dsp(CMD_GET_VER);
235         major = read_dsp();
236         minor = read_dsp();
237
238         return (major << 8) | minor;
239 }
240
241 #define V(maj, min)     (((maj) << 8) | (min))
242
243 static const char *sbname(int ver)
244 {
245         int major = VER_MAJOR(ver);
246         int minor = VER_MINOR(ver);
247
248         switch(major) {
249         case 1:
250                 if(minor == 5) {
251                         return "Sound Blaster 1.5";
252                 }
253                 return "Sound Blaster 1.0";
254
255         case 2:
256                 if(minor == 1 || minor == 2) {
257                         return "Sound Blaster 2.0";
258                 }
259                 break;
260
261         case 3:
262                 switch(minor) {
263                 case 0:
264                         return "Sound Blaster Pro";
265                 case 1:
266                 case 2:
267                         return "Sound Blaster Pro 2";
268                 case 5:
269                         return "Gallant SC-6000";
270                 default:
271                         break;
272                 }
273                 break;
274
275         case 4:
276                 switch(minor) {
277                 case 4:
278                 case 5:
279                         return "Sound Blaster 16";
280                 case 11:
281                         return "Sound Blaster 16 SCSI-2";
282                 case 12:
283                         return "Sound Blaster AWE 32";
284                 case 13:
285                         return "Sound Blaster ViBRA16C";
286                 case 16:
287                         return "Sound Blaster AWE 64";
288                 default:
289                         break;
290                 }
291                 break;
292         }
293
294         return "Unknown Sound Blaster";
295 }