+void sb_set_output_rate(int rate)
+{
+ if(sb16) {
+ write_dsp(CMD_SB16_OUT_RATE);
+ write_dsp(rate >> 8);
+ write_dsp(rate & 0xff);
+ } else {
+ int tcon = 256 - 1000000 / rate;
+ write_dsp(CMD_RATE);
+ write_dsp(tcon);
+ }
+}
+
+void *sb_buffer(int *size)
+{
+ *size = 65536;
+ return buffer;
+}
+
+void sb_start(int rate, int nchan)
+{
+ uint32_t addr;
+ int size;
+
+ /* for now just use the are after boot2. it's only used by the second stage
+ * loader and the VBE init code, none of which should overlap with audio playback.
+ * It's not necessary to use low memory. We can use up to the first 16mb for this,
+ * so if this becomes an issue, I'll make a DMA buffer allocator over 1mb.
+ * start the buffer from the next 64k boundary.
+ */
+ addr = ((uint32_t)low_mem_buffer + 0xffff) & 0xffff0000;
+ buffer = (void*)addr;
+
+ xfer_mode = CMD_MODE_SIGNED;
+ if(nchan > 1) {
+ xfer_mode |= CMD_MODE_STEREO;
+ }
+
+ if(!(size = audio_callback(buffer, 65536))) {
+ return;
+ }
+
+ interrupt(IRQ_TO_INTR(irq), intr_handler);
+
+ start_dma_transfer(addr, size);
+}
+
+void sb_pause(void)
+{
+ write_dsp(CMD_PAUSE_DMA8);
+}
+
+void sb_continue(void)
+{
+ write_dsp(CMD_CONT_DMA8);
+}
+
+void sb_stop(void)
+{
+ write_dsp(CMD_END_DMA8);
+}
+
+void sb_volume(int vol)
+{
+ /* TODO */
+}
+
+static void intr_handler()
+{
+ int size;
+
+ /* ask for more data */
+ if(!(size = audio_callback(buffer, 65536))) {
+ sb_stop();
+ return;
+ }
+ start_dma_transfer((uint32_t)buffer, size);
+
+ /* acknowledge the interrupt */
+ inb(REG_INTACK);
+}
+
+static void start_dma_transfer(uint32_t addr, int size)
+{
+ /* set up the next DMA transfer */
+ dma_out(dma_chan, addr, size, DMA_SINGLE);
+
+ /* program the DSP to accept the DMA transfer */
+ write_dsp(CMD_START_DMA8 | CMD_FIFO);
+ write_dsp(xfer_mode);
+ size--;
+ write_dsp(size & 0xff);
+ write_dsp((size >> 8) & 0xff);
+}
+