minix/drivers/audio/sb16/sb16.c

449 lines
10 KiB
C
Raw Normal View History

/* 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;
}
PUBLIC int drv_init_hw(void) {
int i;
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 */
if(DmaMode == DEV_WRITE_S) {
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;
}
Split block/character protocols and libdriver This patch separates the character and block driver communication protocols. The old character protocol remains the same, but a new block protocol is introduced. The libdriver library is replaced by two new libraries: libchardriver and libblockdriver. Their exposed API, and drivers that use them, have been updated accordingly. Together, libbdev and libblockdriver now completely abstract away the message format used by the block protocol. As the memory driver is both a character and a block device driver, it now implements its own message loop. The most important semantic change made to the block protocol is that it is no longer possible to return both partial results and an error for a single transfer. This simplifies the interaction between the caller and the driver, as the I/O vector no longer needs to be copied back. Also, drivers are now no longer supposed to decide based on the layout of the I/O vector when a transfer should be cut short. Put simply, transfers are now supposed to either succeed completely, or result in an error. After this patch, the state of the various pieces is as follows: - block protocol: stable - libbdev API: stable for synchronous communication - libblockdriver API: needs slight revision (the drvlib/partition API in particular; the threading API will also change shortly) - character protocol: needs cleanup - libchardriver API: needs cleanup accordingly - driver restarts: largely unsupported until endpoint changes are reintroduced As a side effect, this patch eliminates several bugs, hacks, and gcc -Wall and -W warnings all over the place. It probably introduces a few new ones, too. Update warning: this patch changes the protocol between MFS and disk drivers, so in order to use old/new images, the MFS from the ramdisk must be used to mount all file systems.
2011-11-22 13:27:53 +01:00
PUBLIC int drv_set_dma(u32_t dma, u32_t UNUSED(length), int UNUSED(chan)) {
Dprint(("drv_set_dma():\n"));
DmaPhys = dma;
return OK;
}
Split block/character protocols and libdriver This patch separates the character and block driver communication protocols. The old character protocol remains the same, but a new block protocol is introduced. The libdriver library is replaced by two new libraries: libchardriver and libblockdriver. Their exposed API, and drivers that use them, have been updated accordingly. Together, libbdev and libblockdriver now completely abstract away the message format used by the block protocol. As the memory driver is both a character and a block device driver, it now implements its own message loop. The most important semantic change made to the block protocol is that it is no longer possible to return both partial results and an error for a single transfer. This simplifies the interaction between the caller and the driver, as the I/O vector no longer needs to be copied back. Also, drivers are now no longer supposed to decide based on the layout of the I/O vector when a transfer should be cut short. Put simply, transfers are now supposed to either succeed completely, or result in an error. After this patch, the state of the various pieces is as follows: - block protocol: stable - libbdev API: stable for synchronous communication - libblockdriver API: needs slight revision (the drvlib/partition API in particular; the threading API will also change shortly) - character protocol: needs cleanup - libchardriver API: needs cleanup accordingly - driver restarts: largely unsupported until endpoint changes are reintroduced As a side effect, this patch eliminates several bugs, hacks, and gcc -Wall and -W warnings all over the place. It probably introduces a few new ones, too. Update warning: this patch changes the protocol between MFS and disk drivers, so in order to use old/new images, the MFS from the ramdisk must be used to mount all file systems.
2011-11-22 13:27:53 +01:00
PUBLIC int drv_reenable_int(int UNUSED(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;
}
Split block/character protocols and libdriver This patch separates the character and block driver communication protocols. The old character protocol remains the same, but a new block protocol is introduced. The libdriver library is replaced by two new libraries: libchardriver and libblockdriver. Their exposed API, and drivers that use them, have been updated accordingly. Together, libbdev and libblockdriver now completely abstract away the message format used by the block protocol. As the memory driver is both a character and a block device driver, it now implements its own message loop. The most important semantic change made to the block protocol is that it is no longer possible to return both partial results and an error for a single transfer. This simplifies the interaction between the caller and the driver, as the I/O vector no longer needs to be copied back. Also, drivers are now no longer supposed to decide based on the layout of the I/O vector when a transfer should be cut short. Put simply, transfers are now supposed to either succeed completely, or result in an error. After this patch, the state of the various pieces is as follows: - block protocol: stable - libbdev API: stable for synchronous communication - libblockdriver API: needs slight revision (the drvlib/partition API in particular; the threading API will also change shortly) - character protocol: needs cleanup - libchardriver API: needs cleanup accordingly - driver restarts: largely unsupported until endpoint changes are reintroduced As a side effect, this patch eliminates several bugs, hacks, and gcc -Wall and -W warnings all over the place. It probably introduces a few new ones, too. Update warning: this patch changes the protocol between MFS and disk drivers, so in order to use old/new images, the MFS from the ramdisk must be used to mount all file systems.
2011-11-22 13:27:53 +01:00
PUBLIC int drv_resume(int UNUSED(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;
}
Split block/character protocols and libdriver This patch separates the character and block driver communication protocols. The old character protocol remains the same, but a new block protocol is introduced. The libdriver library is replaced by two new libraries: libchardriver and libblockdriver. Their exposed API, and drivers that use them, have been updated accordingly. Together, libbdev and libblockdriver now completely abstract away the message format used by the block protocol. As the memory driver is both a character and a block device driver, it now implements its own message loop. The most important semantic change made to the block protocol is that it is no longer possible to return both partial results and an error for a single transfer. This simplifies the interaction between the caller and the driver, as the I/O vector no longer needs to be copied back. Also, drivers are now no longer supposed to decide based on the layout of the I/O vector when a transfer should be cut short. Put simply, transfers are now supposed to either succeed completely, or result in an error. After this patch, the state of the various pieces is as follows: - block protocol: stable - libbdev API: stable for synchronous communication - libblockdriver API: needs slight revision (the drvlib/partition API in particular; the threading API will also change shortly) - character protocol: needs cleanup - libchardriver API: needs cleanup accordingly - driver restarts: largely unsupported until endpoint changes are reintroduced As a side effect, this patch eliminates several bugs, hacks, and gcc -Wall and -W warnings all over the place. It probably introduces a few new ones, too. Update warning: this patch changes the protocol between MFS and disk drivers, so in order to use old/new images, the MFS from the ramdisk must be used to mount all file systems.
2011-11-22 13:27:53 +01:00
PUBLIC int drv_get_frag_size(u32_t *frag_size, int UNUSED(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 */
pv_set(pvb[2], DMA8_MODE, (DmaMode == DEV_WRITE_S ? DMA8_AUTO_PLAY : DMA8_AUTO_REC));
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 */
pv_set(pvb[2], DMA16_MODE, (DmaMode == DEV_WRITE_S ? DMA16_AUTO_PLAY : DMA16_AUTO_REC));
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) {
int i;
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;
unsigned long value;
if ((s=sys_inb(port, &value)) != OK)
panic("sys_inb() failed: %d", s);
return (int) value;
}
PUBLIC void sb16_outb(int port, int value) {
int s;
if ((s=sys_outb(port, value)) != OK)
panic("sys_outb() failed: %d", s);
}