census logo
[bootcensus] / src / au_sb.c
index b42a603..fc9761d 100644 (file)
@@ -49,6 +49,8 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 /* immediately pause/continue */
 #define CMD_PAUSE_DMA8         0xd0
+#define CMD_ENABLE_OUTPUT      0xd1
+#define CMD_DISABLE_OUTPUT     0xd3
 #define CMD_CONT_DMA8          0xd4
 #define CMD_PAUSE_DMA16                0xd5
 #define CMD_CONT_DMA16         0xd6
@@ -61,6 +63,10 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #define CMD_MODE_SIGNED                0x10
 #define CMD_MODE_STEREO                0x20
 
+/* mixer registers */
+#define MIX_IRQ_SEL                    0x80
+#define MIX_DMA_SEL                    0x81
+
 #define VER_MAJOR(x)   ((x) >> 8)
 #define VER_MINOR(x)   ((x) & 0xff)
 
@@ -68,13 +74,17 @@ static void intr_handler();
 static void start_dma_transfer(uint32_t addr, int size);
 static void write_dsp(unsigned char val);
 static unsigned char read_dsp(void);
+static void write_mix(unsigned char val, int reg);
+static unsigned char read_mix(int reg);
 static int get_dsp_version(void);
+static int sb16_detect_irq(void);
+static int sb16_detect_dma(void);
 static const char *sbname(int ver);
 
 extern unsigned char low_mem_buffer[];
 
 static int base_port;
-static int irq, dma_chan;
+static int irq, dma_chan, dma16_chan;
 static int sb16;
 static void *buffer;
 static int xfer_mode;
@@ -88,16 +98,36 @@ int sb_detect(void)
                if(sb_reset_dsp() == 0) {
                        ver = get_dsp_version();
                        sb16 = VER_MAJOR(ver) >= 4;
-                       printf("sb_detect: found %s (DSP v%d.%02d) at port %xh\n", sbname(ver),
-                                       VER_MAJOR(ver), VER_MINOR(ver), base_port);
+
+                       if(sb16) {
+                               if(sb16_detect_irq() == -1) {
+                                       printf("sb_detect: failed to configure IRQ\n");
+                                       return 0;
+                               }
+                               if(sb16_detect_dma() == -1) {
+                                       printf("sb_detect: failed to configure DMA\n");
+                                       return 0;
+                               }
+
+                               printf("sb_detect: found %s (DSP v%d.%02d) at port %xh, irq %d, dma %d/%d\n",
+                                               sbname(ver), VER_MAJOR(ver), VER_MINOR(ver),
+                                               base_port, irq, dma_chan, dma16_chan);
+
+                       } else {
+                               /* XXX for old sound blasters, hard-code to IRQ 5 DMA 1 for now */
+                               irq = 5;
+                               dma_chan = 1;
+                               dma16_chan = -1;
+
+                               printf("sb_detect: found %s (DSP v%d.%02d) at port %xh\n", sbname(ver),
+                                               VER_MAJOR(ver), VER_MINOR(ver), base_port);
+                               printf("sb_detect: old sound blaster dsp. assuming: irq 5, dma 1\n");
+                       }
+
                        return 1;
                }
        }
 
-       /* TODO: detect these somehow? */
-       irq = 5;
-       dma_chan = 1;
-
        return 0;
 }
 
@@ -164,6 +194,8 @@ void sb_start(int rate, int nchan)
 
        interrupt(IRQ_TO_INTR(irq), intr_handler);
 
+       write_dsp(CMD_ENABLE_OUTPUT);
+       sb_set_output_rate(rate);
        start_dma_transfer(addr, size);
 }
 
@@ -180,6 +212,7 @@ void sb_continue(void)
 void sb_stop(void)
 {
        write_dsp(CMD_END_DMA8);
+       write_dsp(CMD_DISABLE_OUTPUT);
 }
 
 void sb_volume(int vol)
@@ -208,7 +241,7 @@ static void start_dma_transfer(uint32_t addr, int size)
        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(CMD_START_DMA8);
        write_dsp(xfer_mode);
        size--;
        write_dsp(size & 0xff);
@@ -227,6 +260,18 @@ static unsigned char read_dsp(void)
        return inb(REG_RDATA);
 }
 
+static void write_mix(unsigned char val, int reg)
+{
+       outb(reg, REG_MIXPORT);
+       outb(val, REG_MIXDATA);
+}
+
+static unsigned char read_mix(int reg)
+{
+       outb(reg, REG_MIXPORT);
+       return inb(REG_MIXDATA);
+}
+
 static int get_dsp_version(void)
 {
        int major, minor;
@@ -238,6 +283,78 @@ static int get_dsp_version(void)
        return (major << 8) | minor;
 }
 
+static int sb16_detect_irq(void)
+{
+       int i, irqsel;
+       static int irqtab[] = {2, 5, 7, 10};
+
+       irq = 0;
+       irqsel = read_mix(MIX_IRQ_SEL);
+       for(i=0; i<4; i++) {
+               if(irqsel & (1 << i)) {
+                       irq = irqtab[i];
+                       break;
+               }
+       }
+       if(!irq) {
+               /* try to force IRQ 5 */
+               write_mix(2, MIX_IRQ_SEL);      /* bit1 selects irq 5 */
+
+               /* re-read to verify */
+               irqsel = read_mix(MIX_IRQ_SEL);
+               if(irqsel != 2) {
+                       return -1;
+               }
+               irq = 5;
+       }
+
+       return irq;
+}
+
+static int sb16_detect_dma(void)
+{
+       int i, dmasel, tmp;
+       static int dmatab[] = {0, 1, -1, 3, -1, 5, 6, 7};
+
+       dma_chan = -1;
+       dma16_chan = -1;
+       dmasel = read_mix(MIX_DMA_SEL);
+       for(i=0; i<4; i++) {
+               if(dmasel & (1 << i)) {
+                       dma_chan = dmatab[i];
+                       break;
+               }
+       }
+       for(i=5; i<8; i++) {
+               if(dmasel & (1 << i)) {
+                       dma16_chan = dmatab[i];
+                       break;
+               }
+       }
+       if(dma_chan == -1) {
+               /* try to force DMA 1 */
+               dmasel |= 2;
+       }
+       if(dma16_chan == -1) {
+               /* try to force 16bit DMA 5 */
+               dmasel |= 0x20;
+       }
+
+       if(dma_chan == -1 || dma16_chan == -1) {
+               write_mix(dmasel, MIX_DMA_SEL);
+
+               /* re-read to verify */
+               tmp = read_mix(MIX_DMA_SEL);
+               if(tmp != dmasel) {
+                       return -1;
+               }
+               dma_chan = 1;
+               dma16_chan = 5;
+       }
+
+       return dma_chan;
+}
+
 #define V(maj, min)    (((maj) << 8) | (min))
 
 static const char *sbname(int ver)