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 int sb16_detect_irq(void);
81 static int sb16_detect_dma(void);
82 static const char *sbname(int ver);
84 extern unsigned char low_mem_buffer[];
87 static int irq, dma_chan, dma16_chan;
97 base_port = 0x200 + ((i + 1) << 4);
98 if(sb_reset_dsp() == 0) {
99 ver = get_dsp_version();
100 sb16 = VER_MAJOR(ver) >= 4;
103 if(sb16_detect_irq() == -1) {
104 printf("sb_detect: failed to configure IRQ\n");
107 if(sb16_detect_dma() == -1) {
108 printf("sb_detect: failed to configure DMA\n");
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);
117 /* XXX for old sound blasters, hard-code to IRQ 5 DMA 1 for now */
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");
134 int sb_reset_dsp(void)
139 for(i=0; i<3; i++) iodelay();
142 for(i=0; i<128; i++) {
143 if(inb(REG_RSTAT) & RSTAT_RDY) {
144 if(inb(REG_RDATA) == 0xaa) {
153 void sb_set_output_rate(int rate)
156 write_dsp(CMD_SB16_OUT_RATE);
157 write_dsp(rate >> 8);
158 write_dsp(rate & 0xff);
160 int tcon = 256 - 1000000 / rate;
166 void *sb_buffer(int *size)
172 void sb_start(int rate, int nchan)
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.
183 addr = ((uint32_t)low_mem_buffer + 0xffff) & 0xffff0000;
184 buffer = (void*)addr;
186 xfer_mode = CMD_MODE_SIGNED;
188 xfer_mode |= CMD_MODE_STEREO;
191 if(!(size = audio_callback(buffer, 65536))) {
195 interrupt(IRQ_TO_INTR(irq), intr_handler);
197 write_dsp(CMD_ENABLE_OUTPUT);
198 sb_set_output_rate(rate);
199 start_dma_transfer(addr, size);
204 write_dsp(CMD_PAUSE_DMA8);
207 void sb_continue(void)
209 write_dsp(CMD_CONT_DMA8);
214 write_dsp(CMD_END_DMA8);
215 write_dsp(CMD_DISABLE_OUTPUT);
218 void sb_volume(int vol)
223 static void intr_handler()
227 /* ask for more data */
228 if(!(size = audio_callback(buffer, 65536))) {
232 start_dma_transfer((uint32_t)buffer, size);
234 /* acknowledge the interrupt */
238 static void start_dma_transfer(uint32_t addr, int size)
240 /* set up the next DMA transfer */
241 dma_out(dma_chan, addr, size, DMA_SINGLE);
243 /* program the DSP to accept the DMA transfer */
244 write_dsp(CMD_START_DMA8);
245 write_dsp(xfer_mode);
247 write_dsp(size & 0xff);
248 write_dsp((size >> 8) & 0xff);
251 static void write_dsp(unsigned char val)
253 while(inb(REG_WSTAT) & WSTAT_BUSY);
254 outb(val, REG_WDATA);
257 static unsigned char read_dsp(void)
259 while((inb(REG_RSTAT) & RSTAT_RDY) == 0);
260 return inb(REG_RDATA);
263 static void write_mix(unsigned char val, int reg)
265 outb(reg, REG_MIXPORT);
266 outb(val, REG_MIXDATA);
269 static unsigned char read_mix(int reg)
271 outb(reg, REG_MIXPORT);
272 return inb(REG_MIXDATA);
275 static int get_dsp_version(void)
279 write_dsp(CMD_GET_VER);
283 return (major << 8) | minor;
286 static int sb16_detect_irq(void)
289 static int irqtab[] = {2, 5, 7, 10};
292 irqsel = read_mix(MIX_IRQ_SEL);
294 if(irqsel & (1 << i)) {
300 /* try to force IRQ 5 */
301 write_mix(2, MIX_IRQ_SEL); /* bit1 selects irq 5 */
303 /* re-read to verify */
304 irqsel = read_mix(MIX_IRQ_SEL);
314 static int sb16_detect_dma(void)
317 static int dmatab[] = {0, 1, -1, 3, -1, 5, 6, 7};
321 dmasel = read_mix(MIX_DMA_SEL);
323 if(dmasel & (1 << i)) {
324 dma_chan = dmatab[i];
329 if(dmasel & (1 << i)) {
330 dma16_chan = dmatab[i];
335 /* try to force DMA 1 */
338 if(dma16_chan == -1) {
339 /* try to force 16bit DMA 5 */
343 if(dma_chan == -1 || dma16_chan == -1) {
344 write_mix(dmasel, MIX_DMA_SEL);
346 /* re-read to verify */
347 tmp = read_mix(MIX_DMA_SEL);
358 #define V(maj, min) (((maj) << 8) | (min))
360 static const char *sbname(int ver)
362 int major = VER_MAJOR(ver);
363 int minor = VER_MINOR(ver);
368 return "Sound Blaster 1.5";
370 return "Sound Blaster 1.0";
373 if(minor == 1 || minor == 2) {
374 return "Sound Blaster 2.0";
381 return "Sound Blaster Pro";
384 return "Sound Blaster Pro 2";
386 return "Gallant SC-6000";
396 return "Sound Blaster 16";
398 return "Sound Blaster 16 SCSI-2";
400 return "Sound Blaster AWE 32";
402 return "Sound Blaster ViBRA16C";
404 return "Sound Blaster AWE 64";
411 return "Unknown Sound Blaster";