minix/drivers/audio/sb16/sb16.c
David van Moolenbroek b4d909d415 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-23 14:06:37 +01:00

448 lines
10 KiB
C

/* 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;
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;
}
PUBLIC int drv_set_dma(u32_t dma, u32_t UNUSED(length), int UNUSED(chan)) {
Dprint(("drv_set_dma():\n"));
DmaPhys = dma;
return OK;
}
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;
}
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;
}
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);
}