2 pcboot - bootable PC demo/game kernel
3 Copyright (C) 2018 John Tsiombikas <nuclear@member.fsf.org>
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.
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.
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/>.
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)
35 #define WSTAT_BUSY 0x80
36 #define RSTAT_RDY 0x80
39 #define CMD_SB16_OUT_RATE 0x41
40 #define CMD_SB16_IN_RATE 0x42
41 #define CMD_GET_VER 0xe1
43 /* start DMA playback/recording. combine with fifo/auto/input flags */
44 #define CMD_START_DMA8 0xc0
45 #define CMD_START_DMA16 0xb0
48 #define CMD_INPUT 0x08
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
58 /* end the playback at the end of the current buffer */
59 #define CMD_END_DMA16 0xd9
60 #define CMD_END_DMA8 0xda
62 /* transfer mode commands */
63 #define CMD_MODE_SIGNED 0x10
64 #define CMD_MODE_STEREO 0x20
67 #define MIX_IRQ_SEL 0x80
68 #define MIX_DMA_SEL 0x81
70 #define VER_MAJOR(x) ((x) >> 8)
71 #define VER_MINOR(x) ((x) & 0xff)
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);
82 extern unsigned char low_mem_buffer[];
85 static int irq, dma_chan, dma16_chan;
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};
97 base_port = 0x200 + ((i + 1) << 4);
98 if(sb_reset_dsp() == 0) {
99 ver = get_dsp_version();
100 sb16 = VER_MAJOR(ver) >= 4;
104 irqsel = read_mix(MIX_IRQ_SEL);
106 if(irqsel & (1 << j)) {
112 /* try to force IRQ 5 */
113 write_mix(2, MIX_IRQ_SEL); /* bit1 selects irq 5 */
115 /* re-read to verify */
116 irqsel = read_mix(MIX_IRQ_SEL);
118 printf("sb_detect: failed to configure IRQ\n");
126 dmasel = read_mix(MIX_DMA_SEL);
128 if(dmasel & (1 << j)) {
129 dma_chan = dmatab[j];
134 if(dmasel & (1 << j)) {
135 dma16_chan = dmatab[j];
140 /* try to force DMA 1 */
143 if(dma16_chan == -1) {
144 /* try to force 16bit DMA 5 */
148 if(dma_chan == -1 || dma16_chan == -1) {
149 write_mix(dmasel, MIX_DMA_SEL);
151 /* re-read to verify */
152 tmp = read_mix(MIX_DMA_SEL);
154 printf("sb_detect: failed to configure DMA\n");
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);
166 /* XXX for old sound blasters, hard-code to IRQ 5 DMA 1 for now */
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");
183 int sb_reset_dsp(void)
188 for(i=0; i<3; i++) iodelay();
191 for(i=0; i<128; i++) {
192 if(inb(REG_RSTAT) & RSTAT_RDY) {
193 if(inb(REG_RDATA) == 0xaa) {
202 void sb_set_output_rate(int rate)
205 write_dsp(CMD_SB16_OUT_RATE);
206 write_dsp(rate >> 8);
207 write_dsp(rate & 0xff);
209 int tcon = 256 - 1000000 / rate;
215 void *sb_buffer(int *size)
221 void sb_start(int rate, int nchan)
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.
232 addr = ((uint32_t)low_mem_buffer + 0xffff) & 0xffff0000;
233 buffer = (void*)addr;
235 xfer_mode = CMD_MODE_SIGNED;
237 xfer_mode |= CMD_MODE_STEREO;
240 if(!(size = audio_callback(buffer, 65536))) {
244 interrupt(IRQ_TO_INTR(irq), intr_handler);
246 sb_set_output_rate(rate);
247 start_dma_transfer(addr, size);
248 write_dsp(CMD_ENABLE_OUTPUT);
253 write_dsp(CMD_PAUSE_DMA8);
256 void sb_continue(void)
258 write_dsp(CMD_CONT_DMA8);
263 write_dsp(CMD_END_DMA8);
264 write_dsp(CMD_DISABLE_OUTPUT);
267 void sb_volume(int vol)
272 static void intr_handler()
276 /* ask for more data */
277 if(!(size = audio_callback(buffer, 65536))) {
281 start_dma_transfer((uint32_t)buffer, size);
283 /* acknowledge the interrupt */
287 static void start_dma_transfer(uint32_t addr, int size)
289 /* set up the next DMA transfer */
290 dma_out(dma_chan, addr, size, DMA_SINGLE);
292 /* program the DSP to accept the DMA transfer */
293 write_dsp(CMD_START_DMA8 | CMD_FIFO);
294 write_dsp(xfer_mode);
296 write_dsp(size & 0xff);
297 write_dsp((size >> 8) & 0xff);
300 static void write_dsp(unsigned char val)
302 while(inb(REG_WSTAT) & WSTAT_BUSY);
303 outb(val, REG_WDATA);
306 static unsigned char read_dsp(void)
308 while((inb(REG_RSTAT) & RSTAT_RDY) == 0);
309 return inb(REG_RDATA);
312 static void write_mix(unsigned char val, int reg)
314 outb(reg, REG_MIXPORT);
315 outb(val, REG_MIXDATA);
318 static unsigned char read_mix(int reg)
320 outb(reg, REG_MIXPORT);
321 return inb(REG_MIXDATA);
324 static int get_dsp_version(void)
328 write_dsp(CMD_GET_VER);
332 return (major << 8) | minor;
335 #define V(maj, min) (((maj) << 8) | (min))
337 static const char *sbname(int ver)
339 int major = VER_MAJOR(ver);
340 int minor = VER_MINOR(ver);
345 return "Sound Blaster 1.5";
347 return "Sound Blaster 1.0";
350 if(minor == 1 || minor == 2) {
351 return "Sound Blaster 2.0";
358 return "Sound Blaster Pro";
361 return "Sound Blaster Pro 2";
363 return "Gallant SC-6000";
373 return "Sound Blaster 16";
375 return "Sound Blaster 16 SCSI-2";
377 return "Sound Blaster AWE 32";
379 return "Sound Blaster ViBRA16C";
381 return "Sound Blaster AWE 64";
388 return "Unknown Sound Blaster";