2007-11-23 12:30:50 +01:00
|
|
|
/* Driver for SB16 ISA card
|
|
|
|
* Implementing audio/audio_fw.h
|
|
|
|
*
|
|
|
|
* February 2006 Integrated standalone driver with audio framework (Peter Boonstoppel)
|
|
|
|
* August 24 2005 Ported audio driver to user space (only audio playback) (Peter Boonstoppel)
|
|
|
|
* May 20 1995 SB16 Driver: Michel R. Prevenier
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include "sb16.h"
|
|
|
|
#include "mixer.h"
|
|
|
|
|
|
|
|
|
|
|
|
FORWARD _PROTOTYPE( void dsp_dma_setup, (phys_bytes address, int count, int sub_dev) );
|
|
|
|
|
|
|
|
FORWARD _PROTOTYPE( int dsp_ioctl, (int request, void *val, int *len));
|
|
|
|
FORWARD _PROTOTYPE( int dsp_set_size, (unsigned int size) );
|
|
|
|
FORWARD _PROTOTYPE( int dsp_set_speed, (unsigned int speed) );
|
|
|
|
FORWARD _PROTOTYPE( int dsp_set_stereo, (unsigned int stereo) );
|
|
|
|
FORWARD _PROTOTYPE( int dsp_set_bits, (unsigned int bits) );
|
|
|
|
FORWARD _PROTOTYPE( int dsp_set_sign, (unsigned int sign) );
|
|
|
|
FORWARD _PROTOTYPE( int dsp_get_max_frag_size, (u32_t *val, int *len) );
|
|
|
|
|
|
|
|
|
|
|
|
PRIVATE unsigned int DspStereo = DEFAULT_STEREO;
|
|
|
|
PRIVATE unsigned int DspSpeed = DEFAULT_SPEED;
|
|
|
|
PRIVATE unsigned int DspBits = DEFAULT_BITS;
|
|
|
|
PRIVATE unsigned int DspSign = DEFAULT_SIGN;
|
|
|
|
PRIVATE unsigned int DspFragmentSize;
|
|
|
|
|
|
|
|
PRIVATE phys_bytes DmaPhys;
|
|
|
|
PRIVATE int running = FALSE;
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC sub_dev_t sub_dev[2];
|
|
|
|
PUBLIC special_file_t special_file[3];
|
|
|
|
PUBLIC drv_t drv;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC int drv_init(void) {
|
|
|
|
drv.DriverName = "SB16";
|
|
|
|
drv.NrOfSubDevices = 2;
|
|
|
|
drv.NrOfSpecialFiles = 3;
|
|
|
|
|
|
|
|
sub_dev[AUDIO].readable = 1;
|
|
|
|
sub_dev[AUDIO].writable = 1;
|
|
|
|
sub_dev[AUDIO].DmaSize = 64 * 1024;
|
|
|
|
sub_dev[AUDIO].NrOfDmaFragments = 2;
|
|
|
|
sub_dev[AUDIO].MinFragmentSize = 1024;
|
|
|
|
sub_dev[AUDIO].NrOfExtraBuffers = 4;
|
|
|
|
|
|
|
|
sub_dev[MIXER].writable = 0;
|
|
|
|
sub_dev[MIXER].readable = 0;
|
|
|
|
|
|
|
|
special_file[0].minor_dev_nr = 0;
|
|
|
|
special_file[0].write_chan = AUDIO;
|
|
|
|
special_file[0].read_chan = NO_CHANNEL;
|
|
|
|
special_file[0].io_ctl = AUDIO;
|
|
|
|
|
|
|
|
special_file[1].minor_dev_nr = 1;
|
|
|
|
special_file[1].write_chan = NO_CHANNEL;
|
|
|
|
special_file[1].read_chan = AUDIO;
|
|
|
|
special_file[1].io_ctl = AUDIO;
|
|
|
|
|
|
|
|
special_file[2].minor_dev_nr = 2;
|
|
|
|
special_file[2].write_chan = NO_CHANNEL;
|
|
|
|
special_file[2].read_chan = NO_CHANNEL;
|
|
|
|
special_file[2].io_ctl = MIXER;
|
2010-01-11 15:22:29 +01:00
|
|
|
|
|
|
|
return OK;
|
2007-11-23 12:30:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC int drv_init_hw(void) {
|
2010-01-15 22:45:30 +01:00
|
|
|
int i;
|
2007-11-23 12:30:50 +01:00
|
|
|
int DspVersion[2];
|
|
|
|
dprint("drv_init_hw():\n");
|
|
|
|
|
|
|
|
if(drv_reset () != OK) {
|
|
|
|
dprint("sb16: No SoundBlaster card detected\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
DspVersion[0] = DspVersion[1] = 0;
|
|
|
|
dsp_command(DSP_GET_VERSION); /* Get DSP version bytes */
|
|
|
|
|
|
|
|
for(i = 1000; i; i--) {
|
|
|
|
if(sb16_inb(DSP_DATA_AVL) & 0x80) {
|
|
|
|
if(DspVersion[0] == 0) {
|
|
|
|
DspVersion[0] = sb16_inb(DSP_READ);
|
|
|
|
} else {
|
|
|
|
DspVersion[1] = sb16_inb(DSP_READ);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(DspVersion[0] < 4) {
|
|
|
|
dprint("sb16: No SoundBlaster 16 compatible card detected\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
dprint("sb16: SoundBlaster DSP version %d.%d detected!\n", DspVersion[0], DspVersion[1]);
|
|
|
|
|
|
|
|
/* set SB to use our IRQ and DMA channels */
|
|
|
|
mixer_set(MIXER_SET_IRQ, (1 << (SB_IRQ / 2 - 1)));
|
|
|
|
mixer_set(MIXER_SET_DMA, (1 << SB_DMA_8 | 1 << SB_DMA_16));
|
|
|
|
|
|
|
|
DspFragmentSize = sub_dev[AUDIO].DmaSize / sub_dev[AUDIO].NrOfDmaFragments;
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC int drv_reset(void) {
|
|
|
|
int i;
|
|
|
|
dprint("drv_reset():\n");
|
|
|
|
|
|
|
|
sb16_outb(DSP_RESET, 1);
|
|
|
|
for(i = 0; i < 1000; i++); /* wait a while */
|
|
|
|
sb16_outb(DSP_RESET, 0);
|
|
|
|
|
|
|
|
for(i = 0; i < 1000 && !(sb16_inb(DSP_DATA_AVL) & 0x80); i++);
|
|
|
|
|
|
|
|
if(sb16_inb(DSP_READ) != 0xAA) return EIO; /* No SoundBlaster */
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC int drv_start(int channel, int DmaMode) {
|
|
|
|
dprint("drv_start():\n");
|
|
|
|
|
|
|
|
drv_reset();
|
|
|
|
|
|
|
|
dsp_dma_setup(DmaPhys, DspFragmentSize * sub_dev[channel].NrOfDmaFragments, DmaMode);
|
|
|
|
|
|
|
|
dsp_set_speed(DspSpeed);
|
|
|
|
|
|
|
|
/* Put the speaker on */
|
2009-03-31 16:23:33 +02:00
|
|
|
if(DmaMode == DEV_WRITE_S) {
|
2007-11-23 12:30:50 +01:00
|
|
|
dsp_command (DSP_CMD_SPKON); /* put speaker on */
|
|
|
|
|
|
|
|
/* Program DSP with dma mode */
|
|
|
|
dsp_command((DspBits == 8 ? DSP_CMD_8BITAUTO_OUT : DSP_CMD_16BITAUTO_OUT));
|
|
|
|
} else {
|
|
|
|
dsp_command (DSP_CMD_SPKOFF); /* put speaker off */
|
|
|
|
|
|
|
|
/* Program DSP with dma mode */
|
|
|
|
dsp_command((DspBits == 8 ? DSP_CMD_8BITAUTO_IN : DSP_CMD_16BITAUTO_IN));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Program DSP with transfer mode */
|
|
|
|
if (!DspSign) {
|
|
|
|
dsp_command((DspStereo == 1 ? DSP_MODE_STEREO_US : DSP_MODE_MONO_US));
|
|
|
|
} else {
|
|
|
|
dsp_command((DspStereo == 1 ? DSP_MODE_STEREO_S : DSP_MODE_MONO_S));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Give length of fragment to DSP */
|
|
|
|
if (DspBits == 8) { /* 8 bit transfer */
|
|
|
|
/* #bytes - 1 */
|
|
|
|
dsp_command((DspFragmentSize - 1) >> 0);
|
|
|
|
dsp_command((DspFragmentSize - 1) >> 8);
|
|
|
|
} else { /* 16 bit transfer */
|
|
|
|
/* #words - 1 */
|
|
|
|
dsp_command((DspFragmentSize - 1) >> 1);
|
|
|
|
dsp_command((DspFragmentSize - 1) >> 9);
|
|
|
|
}
|
|
|
|
|
|
|
|
running = TRUE;
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC int drv_stop(int sub_dev) {
|
|
|
|
if(running) {
|
|
|
|
dprint("drv_stop():\n");
|
|
|
|
dsp_command((DspBits == 8 ? DSP_CMD_DMA8HALT : DSP_CMD_DMA16HALT));
|
|
|
|
running = FALSE;
|
|
|
|
drv_reenable_int(sub_dev);
|
|
|
|
}
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC int drv_set_dma(u32_t dma, u32_t length, int chan) {
|
|
|
|
dprint("drv_set_dma():\n");
|
|
|
|
DmaPhys = dma;
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC int drv_reenable_int(int chan) {
|
|
|
|
dprint("drv_reenable_int()\n");
|
|
|
|
sb16_inb((DspBits == 8 ? DSP_DATA_AVL : DSP_DATA16_AVL));
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC int drv_int_sum(void) {
|
|
|
|
return mixer_get(MIXER_IRQ_STATUS) & 0x0F;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC int drv_int(int sub_dev) {
|
|
|
|
return sub_dev == AUDIO && mixer_get(MIXER_IRQ_STATUS) & 0x03;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC int drv_pause(int chan) {
|
|
|
|
drv_stop(chan);
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC int drv_resume(int chan) {
|
|
|
|
dsp_command((DspBits == 8 ? DSP_CMD_DMA8CONT : DSP_CMD_DMA16CONT));
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC int drv_io_ctl(int request, void *val, int *len, int sub_dev) {
|
|
|
|
dprint("dsp_ioctl: got ioctl %d, argument: %d sub_dev: %d\n", request, val, sub_dev);
|
|
|
|
|
|
|
|
if(sub_dev == AUDIO) {
|
|
|
|
return dsp_ioctl(request, val, len);
|
|
|
|
} else if(sub_dev == MIXER) {
|
|
|
|
return mixer_ioctl(request, val, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
return EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC int drv_get_irq(char *irq) {
|
|
|
|
dprint("drv_get_irq():\n");
|
|
|
|
*irq = SB_IRQ;
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC int drv_get_frag_size(u32_t *frag_size, int sub_dev) {
|
|
|
|
dprint("drv_get_frag_size():\n");
|
|
|
|
*frag_size = DspFragmentSize;
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PRIVATE int dsp_ioctl(int request, void *val, int *len) {
|
|
|
|
int status;
|
|
|
|
|
|
|
|
switch(request) {
|
|
|
|
case DSPIORATE: status = dsp_set_speed(*((u32_t*) val)); break;
|
|
|
|
case DSPIOSTEREO: status = dsp_set_stereo(*((u32_t*) val)); break;
|
|
|
|
case DSPIOBITS: status = dsp_set_bits(*((u32_t*) val)); break;
|
|
|
|
case DSPIOSIZE: status = dsp_set_size(*((u32_t*) val)); break;
|
|
|
|
case DSPIOSIGN: status = dsp_set_sign(*((u32_t*) val)); break;
|
|
|
|
case DSPIOMAX: status = dsp_get_max_frag_size(val, len); break;
|
|
|
|
case DSPIORESET: status = drv_reset(); break;
|
|
|
|
default: status = ENOTTY; break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PRIVATE void dsp_dma_setup(phys_bytes address, int count, int DmaMode) {
|
|
|
|
pvb_pair_t pvb[9];
|
|
|
|
|
|
|
|
dprint("Setting up %d bit DMA\n", DspBits);
|
|
|
|
|
|
|
|
if(DspBits == 8) { /* 8 bit sound */
|
|
|
|
count--;
|
|
|
|
|
|
|
|
pv_set(pvb[0], DMA8_MASK, SB_DMA_8 | 0x04); /* Disable DMA channel */
|
|
|
|
pv_set(pvb[1], DMA8_CLEAR, 0x00); /* Clear flip flop */
|
|
|
|
|
|
|
|
/* set DMA mode */
|
2009-03-31 16:23:33 +02:00
|
|
|
pv_set(pvb[2], DMA8_MODE, (DmaMode == DEV_WRITE_S ? DMA8_AUTO_PLAY : DMA8_AUTO_REC));
|
2007-11-23 12:30:50 +01:00
|
|
|
|
|
|
|
pv_set(pvb[3], DMA8_ADDR, address >> 0); /* Low_byte of address */
|
|
|
|
pv_set(pvb[4], DMA8_ADDR, address >> 8); /* High byte of address */
|
|
|
|
pv_set(pvb[5], DMA8_PAGE, address >> 16); /* 64K page number */
|
|
|
|
pv_set(pvb[6], DMA8_COUNT, count >> 0); /* Low byte of count */
|
|
|
|
pv_set(pvb[7], DMA8_COUNT, count >> 8); /* High byte of count */
|
|
|
|
pv_set(pvb[8], DMA8_MASK, SB_DMA_8); /* Enable DMA channel */
|
|
|
|
|
|
|
|
sys_voutb(pvb, 9);
|
|
|
|
} else { /* 16 bit sound */
|
|
|
|
count -= 2;
|
|
|
|
|
|
|
|
pv_set(pvb[0], DMA16_MASK, (SB_DMA_16 & 3) | 0x04); /* Disable DMA channel */
|
|
|
|
|
|
|
|
pv_set(pvb[1], DMA16_CLEAR, 0x00); /* Clear flip flop */
|
|
|
|
|
|
|
|
/* Set dma mode */
|
2009-03-31 16:23:33 +02:00
|
|
|
pv_set(pvb[2], DMA16_MODE, (DmaMode == DEV_WRITE_S ? DMA16_AUTO_PLAY : DMA16_AUTO_REC));
|
2007-11-23 12:30:50 +01:00
|
|
|
|
|
|
|
pv_set(pvb[3], DMA16_ADDR, (address >> 1) & 0xFF); /* Low_byte of address */
|
|
|
|
pv_set(pvb[4], DMA16_ADDR, (address >> 9) & 0xFF); /* High byte of address */
|
|
|
|
pv_set(pvb[5], DMA16_PAGE, (address >> 16) & 0xFE); /* 128K page number */
|
|
|
|
pv_set(pvb[6], DMA16_COUNT, count >> 1); /* Low byte of count */
|
|
|
|
pv_set(pvb[7], DMA16_COUNT, count >> 9); /* High byte of count */
|
|
|
|
pv_set(pvb[8], DMA16_MASK, SB_DMA_16 & 3); /* Enable DMA channel */
|
|
|
|
|
|
|
|
sys_voutb(pvb, 9);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PRIVATE int dsp_set_size(unsigned int size) {
|
|
|
|
dprint("dsp_set_size(): set fragment size to %u\n", size);
|
|
|
|
|
|
|
|
/* Sanity checks */
|
|
|
|
if(size < sub_dev[AUDIO].MinFragmentSize || size > sub_dev[AUDIO].DmaSize / sub_dev[AUDIO].NrOfDmaFragments || size % 2 != 0) {
|
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
DspFragmentSize = size;
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PRIVATE int dsp_set_speed(unsigned int speed) {
|
|
|
|
dprint("sb16: setting speed to %u, stereo = %d\n", speed, DspStereo);
|
|
|
|
|
|
|
|
if(speed < DSP_MIN_SPEED || speed > DSP_MAX_SPEED) {
|
|
|
|
return EPERM;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Soundblaster 16 can be programmed with real sample rates
|
|
|
|
* instead of time constants
|
|
|
|
*
|
|
|
|
* Since you cannot sample and play at the same time
|
|
|
|
* we set in- and output rate to the same value
|
|
|
|
*/
|
|
|
|
|
|
|
|
dsp_command(DSP_INPUT_RATE); /* set input rate */
|
|
|
|
dsp_command(speed >> 8); /* high byte of speed */
|
|
|
|
dsp_command(speed); /* low byte of speed */
|
|
|
|
dsp_command(DSP_OUTPUT_RATE); /* same for output rate */
|
|
|
|
dsp_command(speed >> 8);
|
|
|
|
dsp_command(speed);
|
|
|
|
|
|
|
|
DspSpeed = speed;
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PRIVATE int dsp_set_stereo(unsigned int stereo) {
|
|
|
|
if(stereo) {
|
|
|
|
DspStereo = 1;
|
|
|
|
} else {
|
|
|
|
DspStereo = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PRIVATE int dsp_set_bits(unsigned int bits) {
|
|
|
|
/* Sanity checks */
|
|
|
|
if(bits != 8 && bits != 16) {
|
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
DspBits = bits;
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PRIVATE int dsp_set_sign(unsigned int sign) {
|
|
|
|
dprint("sb16: set sign to %u\n", sign);
|
|
|
|
|
|
|
|
DspSign = (sign > 0 ? 1 : 0);
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PRIVATE int dsp_get_max_frag_size(u32_t *val, int *len) {
|
|
|
|
*len = sizeof(*val);
|
|
|
|
*val = sub_dev[AUDIO].DmaSize / sub_dev[AUDIO].NrOfDmaFragments;
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC int dsp_command(int value) {
|
2010-01-15 22:45:30 +01:00
|
|
|
int i;
|
2007-11-23 12:30:50 +01:00
|
|
|
|
|
|
|
for (i = 0; i < SB_TIMEOUT; i++) {
|
|
|
|
if((sb16_inb(DSP_STATUS) & 0x80) == 0) {
|
|
|
|
sb16_outb(DSP_COMMAND, value);
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dprint("sb16: SoundBlaster: DSP Command(%x) timeout\n", value);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC int sb16_inb(int port) {
|
|
|
|
int s, value = -1;
|
|
|
|
|
|
|
|
if ((s=sys_inb(port, &value)) != OK)
|
2010-03-05 16:05:11 +01:00
|
|
|
panic("sys_inb() failed: %d", s);
|
2007-11-23 12:30:50 +01:00
|
|
|
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PUBLIC void sb16_outb(int port, int value) {
|
|
|
|
int s;
|
|
|
|
|
|
|
|
if ((s=sys_outb(port, value)) != OK)
|
2010-03-05 16:05:11 +01:00
|
|
|
panic("sys_outb() failed: %d", s);
|
2009-03-31 16:23:33 +02:00
|
|
|
}
|