freebsd-dev/sys/dev/sound/isa/sb16.c
Luigi Rizzo fe854067c7 Update the pcm driver to the most recent version. This should
add support for Vibra16X, OPTi925, and bring in several assorted
fixes to the code and documentation.
Also present here are apm hooks so that laptops can properly
reconfigure the hardware after suspend (tested on the Libretto50).
Reviewed by: jordan
1998-10-02 17:26:37 +00:00

1319 lines
34 KiB
C

/*
* sound/sb_dsp.c
*
* driver for the SoundBlaster and clones.
*
* Copyright 1997,1998 Luigi Rizzo.
*
* Derived from files in the Voxware 3.5 distribution,
* Copyright by Hannu Savolainen 1994, under the same copyright
* conditions.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
/*
* use this as a template file for board-specific drivers.
* The next two lines (and the final #endif) are in all drivers:
*/
#include <i386/isa/snd/sound.h>
#if NPCM > 0
/*
* Begin with the board-specific include files...
*/
#define __SB_MIXER_C__ /* XXX warning... */
#include <i386/isa/snd/sbcard.h>
/*
* then prototypes of functions which go in the snddev_info
* (usually static, unless they are shared by other modules)...
*/
static int sb_probe(struct isa_device *dev);
static int sb_attach(struct isa_device *dev);
static d_open_t sb_dsp_open;
static d_close_t sb_dsp_close;
static d_ioctl_t sb_dsp_ioctl;
static irq_proc_t sb_intr;
static snd_callback_t sb_callback;
/*
* and prototypes for other private functions defined in this module.
*/
static void sb_dsp_init(snddev_info *d, struct isa_device *dev);
static void sb_mix_init(snddev_info *d);
static int sb_mixer_set(snddev_info *d, int dev, int value);
static int dsp_speed(snddev_info *d);
static void sb_mixer_reset(snddev_info *d);
u_int sb_get_byte(int io_base);
int ess_write(int io_base, u_char reg, int val);
int ess_read(int io_base, u_char reg);
/*
* Then put here the descriptors for the various boards supported
* by this module, properly initialized.
*/
snddev_info sb_op_desc = {
"basic soundblaster",
SNDCARD_SB,
sb_probe,
sb_attach,
sb_dsp_open,
sb_dsp_close /* sb_close */,
NULL /* use generic sndread */,
NULL /* use generic sndwrite */,
sb_dsp_ioctl,
sndselect,
sb_intr,
sb_callback,
DSP_BUFFSIZE, /* bufsize */
AFMT_STEREO | AFMT_U8, /* audio format */
} ;
/*
* Then the file continues with the body of all functions
* directly referenced in the descriptor.
*/
/*
* the probe routine for the SoundBlaster only consists in
* resetting the dsp and testing if it is there.
* Version detection etc. will be done at attach time.
*
* Remember, ISA probe routines are supposed to return the
* size of io space used.
*/
static int
sb_probe(struct isa_device *dev)
{
bzero(&pcm_info[dev->id_unit], sizeof(pcm_info[dev->id_unit]) );
if (dev->id_iobase == -1) {
dev->id_iobase = 0x220;
BVDDB(printf("sb_probe: no address supplied, try defaults (0x220,0x240)\n");)
if (snd_conflict(dev->id_iobase))
dev->id_iobase = 0x240;
}
if (snd_conflict(dev->id_iobase))
return 0 ;
if (sb_reset_dsp(dev->id_iobase))
return 16 ; /* the SB uses 16 registers... */
else
return 0;
}
static int
sb_attach(struct isa_device *dev)
{
snddev_info *d = &pcm_info[dev->id_unit] ;
dev->id_alive = 16 ; /* number of io ports */
/* should be already set but just in case... */
sb_dsp_init(d, dev);
return 0 ;
}
/*
* here are the main routines from the switches.
*/
/*
* Unlike MSS, the sb only supports a single open (does not mean
* that only a single process is using it, since it can fork
* afterwards, or pass the descriptor to another process).
*
*/
static int
sb_dsp_open(dev_t dev, int flags, int mode, struct proc * p)
{
snddev_info *d;
int unit ;
dev = minor(dev);
unit = dev >> 4 ;
d = &pcm_info[unit] ;
DEB(printf("<%s>%d : open\n", d->name, unit));
if (d->flags & SND_F_BUSY) {
printf("<%s>%d open: device busy\n", d->name, unit);
return EBUSY ;
}
d->wsel.si_pid = 0;
d->wsel.si_flags = 0;
d->rsel.si_pid = 0;
d->rsel.si_flags = 0;
d->dbuf_out.total = d->dbuf_out.prev_total = 0 ;
d->dbuf_in.total = d->dbuf_in.prev_total = 0 ;
d->flags = 0 ;
d->bd_flags &= ~BD_F_HISPEED ;
switch ( dev & 0xf ) {
case SND_DEV_DSP16 :
if ((d->audio_fmt & AFMT_S16_LE) == 0) {
printf("sorry, 16-bit not supported on SB %d.%02d\n",
(d->bd_id >>8) & 0xff, d->bd_id & 0xff);
return ENXIO;
}
d->play_fmt = d->rec_fmt = AFMT_S16_LE ;
break;
case SND_DEV_AUDIO :
d->play_fmt = d->rec_fmt = AFMT_MU_LAW ;
break ;
case SND_DEV_DSP :
d->play_fmt = d->rec_fmt = AFMT_U8 ;
break ;
}
/*
* since the SB is not simmetric, I use the open mode to select
* which channel should be privileged, and disable I/O in the
* other direction.
* In case the board is opened RW, we don't have enough
* information on what to do. Temporarily, privilege the
* playback channel, which is used more often, and set the other
* one to U8.
*/
if ( (flags & FREAD) == 0) /* opened write only */
d->rec_fmt = 0 ;
else if ( (flags & FWRITE) == 0) /* opened read only */
d->play_fmt = 0 ;
else /* opened read/write */
d->rec_fmt = (d->play_fmt == AFMT_S16_LE) ? AFMT_U8 : AFMT_S16_LE ;
d->flags |= SND_F_BUSY ;
d->play_speed = d->rec_speed = DSP_DEFAULT_SPEED ;
if (flags & O_NONBLOCK)
d->flags |= SND_F_NBIO ;
sb_reset_dsp(d->io_base);
if (d->bd_flags & BD_F_ESS)
sb_cmd(d->io_base, 0xc6 ); /* enable extended ESS mode */
ask_init(d);
return 0;
}
static int
sb_dsp_close(dev_t dev, int flags, int mode, struct proc * p)
{
int unit;
snddev_info *d;
u_long s;
dev = minor(dev);
unit = dev >> 4 ;
d = &pcm_info[unit] ;
s = spltty();
d->flags |= SND_F_CLOSING ;
splx(s);
snd_flush(d);
sb_cmd(d->io_base, DSP_CMD_SPKOFF ); /* XXX useless ? */
d->flags = 0 ;
return 0 ;
}
static int
sb_dsp_ioctl(dev_t dev, u_long cmd, caddr_t arg, int mode, struct proc * p)
{
int unit;
snddev_info *d;
dev = minor(dev);
unit = dev >> 4 ;
d = &pcm_info[unit] ;
/*
* handle mixer calls first. Reads are in the default handler,
* so do not bother about them.
*/
if ( (cmd & MIXER_WRITE(0)) == MIXER_WRITE(0) )
return sb_mixer_set(d, cmd & 0xff, *(int *)arg) ;
/*
* for the remaining functions, use the default handler.
* ENOSYS means that the default handler should take care
* of implementing the ioctl.
*/
return ENOSYS ;
}
static void
sb_intr(int unit)
{
snddev_info *d = &pcm_info[unit];
int reason = 3, c=1, io_base = d->io_base;
DEB(printf("got sb_intr for unit %d, flags 0x%08lx\n", unit, d->flags));
/*
* SB < 4.0 is half duplex and has only 1 bit for int source,
* so we fake it. SB 4.x (SB16) has the int source in a separate
* register.
* The Vibra16X has separate flags for 8 and 16 bit transfers, but
* I have no idea how to tell capture from playback interrupts...
*/
#define PLAIN_SB16(x) ( ( (x) & (BD_F_SB16|BD_F_SB16X) ) == BD_F_SB16)
again:
if (d->bd_flags & BD_F_SB16) {
c = sb_getmixer(io_base, IRQ_STAT);
/* this tells us if the source is 8-bit or 16-bit dma. We
* have to check the io channel to map it to read or write...
*/
reason = 0 ;
if ( c & 1 ) { /* 8-bit dma */
if (d->play_fmt == AFMT_U8 || d->play_fmt == AFMT_MU_LAW )
reason |= 1;
if (d->rec_fmt == AFMT_U8 || d->rec_fmt == AFMT_MU_LAW )
reason |= 2;
}
if ( c & 2 ) { /* 16-bit dma */
if (d->play_fmt == AFMT_S16_LE)
reason |= 1;
if (d->rec_fmt == AFMT_S16_LE)
reason |= 2;
}
}
/* XXX previous location of ack... */
DEB(printf("sb_intr, flags 0x%08lx reason %d c 0x%x\n",
d->flags, reason, c));
if ( reason & 1 ) { /* possibly a write interrupt */
if ( d->dbuf_out.dl )
dsp_wrintr(d);
else {
if (PLAIN_SB16(d->bd_flags))
printf("WARNING: wrintr but write DMA inactive!\n");
}
}
if ( reason & 2 ) {
if ( d->dbuf_in.dl )
dsp_rdintr(d);
else {
if (PLAIN_SB16(d->bd_flags))
printf("WARNING: rdintr but read DMA inactive!\n");
}
}
if ( c & 2 )
inb(DSP_DATA_AVL16); /* 16-bit int ack */
if (c & 1)
inb(DSP_DATA_AVAIL); /* 8-bit int ack */
/*
* the sb16 might have multiple sources etc.
*/
if (d->bd_flags & BD_F_SB16 && (c & 3) )
goto again;
}
/*
* device-specific function called back from the dma module.
* The reason of the callback is the second argument.
* NOTE: during operations, some ioctl can be called to change
* settings (e.g. speed, channels, format), and the default
* ioctl handler will just record the change and set the
* flag SND_F_INIT. The callback routine is in charge of applying
* the changes at the next convenient time (typically, at the
* start of operations). For full duplex devices, in some cases the
* init requires both channels to be idle.
*/
static int
sb_callback(snddev_info *d, int reason)
{
int rd = reason & SND_CB_RD ;
snd_dbuf *b = (rd) ? & (d->dbuf_in) : & (d->dbuf_out) ;
int l = b->dl ;
switch (reason & SND_CB_REASON_MASK) {
case SND_CB_INIT : /* called with int enabled and no pending io */
/*
* set the speed
*/
dsp_speed(d);
/*
* set the desired DMA blocksize (influences select behaviour)
*/
snd_set_blocksize(d);
/*
* since native mulaw is not present, emulate it.
*/
if ( (d->play_fmt & AFMT_MU_LAW) || (d->rec_fmt & AFMT_MU_LAW) )
d->flags |= SND_F_XLAT8 ;
else
d->flags &= ~SND_F_XLAT8 ;
/*
* there are too many flavours of SB for my taste... here i try to do
* the proper initialization for each one.
*/
if (PLAIN_SB16(d->bd_flags)) {
u_char c, c1 ;
/* the original SB16 (non-PnP, or PnP, or Vibra16C)
* can do full duplex using one 16-bit channel
* and one 8-bit channel. It needs to be programmed to
* use split format though.
* I DON'T do this for the Vibra16X because I have no idea
* of what needs to be done there...
*
* I use the following algorithm:
* 1. check which direction(s) are active;
* 2. check if we should swap dma channels
* 3. check if we can do the swap.
*/
int swap = 1 ; /* default... */
if (d->play_fmt == 0) {
/* do whatever the read channel wants */
if ( d->rec_fmt == AFMT_S16_LE && d->dbuf_in.chan > 4 )
swap = 0;
if ( d->rec_fmt != AFMT_S16_LE && d->dbuf_in.chan < 4 )
swap = 0;
} else {
/* privilege the write channel */
if ( d->play_fmt == AFMT_S16_LE && d->dbuf_out.chan > 4 )
swap = 0;
if ( d->play_fmt != AFMT_S16_LE && d->dbuf_out.chan < 4 )
swap = 0;
if ( d->rec_fmt ) {
/* check for possible config errors.
* This cannot happen at open time since even in
* case of opening rw we privilege the play
* channel.
*/
if (d->rec_fmt == d->play_fmt) {
DDB(printf("sorry, read DMA channel unavailable\n"));
}
}
}
DEB(printf("sb16: play_fmt %d, rec_fmt %x, swap %d\n",
d->play_fmt, d->rec_fmt, swap);)
if (swap) {
int c = d->dbuf_in.chan ;
d->dbuf_in.chan = d->dbuf_out.chan;
d->dbuf_out.chan = c ;
}
} else if (d->bd_flags & BD_F_ESS) {
u_char c ;
if (d->play_fmt == 0) {
/* initialize for record */
static u_char cmd[] = {
0x51,0xd0,0x71,0xf4,0x51,0x98,0x71,0xbc
};
ess_write(d->io_base, 0xb8, 0x0e);
c = ( ess_read(d->io_base, 0xa8) & 0xfc ) | 1 ;
if (d->flags & SND_F_STEREO)
c++ ;
ess_write(d->io_base, 0xa8, c);
ess_write(d->io_base, 0xb9, 2); /* 4bytes/transfer */
/*
* set format in b6, b7
*/
} else {
/* initialize for play */
static u_char cmd[] = {
0x80,0x51,0xd0,0x00,0x71,0xf4,
0x80,0x51,0x98,0x00,0x71,0xbc
};
}
}
reset_dbuf(& (d->dbuf_in), SND_CHAN_RD );
reset_dbuf(& (d->dbuf_out), SND_CHAN_WR );
break ;
case SND_CB_START : /* called with int disabled */
if (d->bd_flags & BD_F_SB16) {
u_char c, c1 ;
if (d->bd_flags & BD_F_SB16X) {
/* just a guess: on the Vibra16X, the first
* op started takes the first dma channel,
* the second one takes the next...
* The default is to be ready for play.
*/
int swap = 0 ;
DEB(printf("start %s -- now dma %d:%d\n",
rd ? "rd" : "wr",
d->dbuf_out.chan, d->dbuf_in.chan););
/* swap only if both channels are idle
* play: dl=0, since there is no pause;
* rec: rl=0
*/
if ( rd && d->dbuf_out.dl == 0 && d->dbuf_in.rl == 0 ) {
/* must swap channels, but also save dl */
int c = d->dbuf_in.chan ;
int dl = d->dbuf_in.dl ;
d->dbuf_in.chan = d->dbuf_out.chan;
d->dbuf_out.chan = c ;
reset_dbuf(& (d->dbuf_in), SND_CHAN_RD );
reset_dbuf(& (d->dbuf_out), SND_CHAN_WR );
d->dbuf_in.dl = dl ;
printf("swapped -- now dma %d:%d\n",
d->dbuf_out.chan, d->dbuf_in.chan);
}
}
/*
* XXX note: c1 and l should be set basing on d->rec_fmt,
* but there is no choice once a 16 or 8-bit channel
* is assigned. This means that if the application
* tries to use a bad format, the sound will not be nice.
*/
if ( b->chan > 4
|| (rd && d->rec_fmt == AFMT_S16_LE)
|| (!rd && d->play_fmt == AFMT_S16_LE)
) {
c = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_DMA16 ;
c1 = DSP_F16_SIGNED ;
l /= 2 ;
} else {
c = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_DMA8 ;
c1 = 0 ;
}
c |= (rd) ? DSP_F16_ADC : DSP_F16_DAC ;
if (d->flags & SND_F_STEREO)
c1 |= DSP_F16_STEREO ;
sb_cmd(d->io_base, c );
sb_cmd3(d->io_base, c1 , l - 1) ;
} else if (d->bd_flags & BD_F_ESS) {
/* XXX this code is still incomplete */
sb_cmd2(d->io_base, 0xb8, rd ? 4 : 0xe ) ; /* auto dma */
sb_cmd2(d->io_base, 0xa8, d->flags & SND_F_STEREO ? 1 : 2) ;
sb_cmd2(d->io_base, 0xb9, 2) ; /* demand dma */
} else { /* SBPro -- stereo not supported */
u_char c ;
if (!rd)
sb_cmd(d->io_base, DSP_CMD_SPKON);
/* code for the SB2 and SB3, only MONO */
if (d->bd_flags & BD_F_HISPEED)
c = (rd) ? 0x98 : 0x90 ;
else
c = (rd) ? 0x2c : 0x1c ;
if (d->flags & SND_F_STEREO)
sb_setmixer(d->io_base, 0xe, 2 );
else
sb_setmixer(d->io_base, 0xe, 0 );
/*
* some ESS extensions -- they can do 16 bits
*/
if ( (rd && d->rec_fmt == AFMT_S16_LE) ||
(!rd && d->play_fmt == AFMT_S16_LE) ) {
c |= 1;
l /= 2 ;
}
sb_cmd3(d->io_base, 0x48 , l - 1) ;
sb_cmd(d->io_base, c ) ;
}
break;
case SND_CB_ABORT : /* XXX */
case SND_CB_STOP :
{
int cmd = DSP_CMD_DMAPAUSE_8 ; /* default: halt 8 bit chan */
if ( b->chan > 4
|| (rd && d->rec_fmt == AFMT_S16_LE)
|| (!rd && d->play_fmt == AFMT_S16_LE)
)
cmd = DSP_CMD_DMAPAUSE_16 ;
if (d->bd_flags & BD_F_HISPEED) {
sb_reset_dsp(d->io_base);
if (d->bd_flags & BD_F_ESS)
sb_cmd(d->io_base, 0xc6 ); /* enable extended ESS mode */
d->flags |= SND_F_INIT ;
} else {
sb_cmd(d->io_base, cmd); /* pause dma. */
/*
* The above seems to have the undocumented side effect of
* blocking the other side as well. If the other
* channel was active (SB16) I have to re-enable it :(
*/
if ( (rd && d->dbuf_out.dl) ||
(!rd && d->dbuf_in.dl) )
sb_cmd(d->io_base, cmd == DSP_CMD_DMAPAUSE_8 ?
0xd6 : 0xd4); /* continue other dma */
}
if (d->bd_flags & BD_F_SB16X) {
/* restore possible swapped channels.
* The default is to be ready for play.
* XXX right now, it kills all input on overflow
*/
if ( rd && d->dbuf_out.dl == 0 ) {
/* must swap channels ? */
int c = d->dbuf_in.chan ;
d->dbuf_in.chan = d->dbuf_out.chan;
d->dbuf_out.chan = c ;
reset_dbuf(& (d->dbuf_in), SND_CHAN_RD );
reset_dbuf(& (d->dbuf_out), SND_CHAN_WR );
printf("restored -- now dma %d:%d\n",
d->dbuf_out.chan, d->dbuf_in.chan);
}
}
}
DEB( sb_cmd(d->io_base, DSP_CMD_SPKOFF) ); /* speaker off */
break ;
}
return 0 ;
}
/*
* The second part of the file contains all functions specific to
* the board and (usually) not exported to other modules.
*/
int
sb_reset_dsp(int io_base)
{
int loopc;
outb(io_base + SBDSP_RST, 3);
DELAY(100);
outb(io_base + SBDSP_RST, 0);
for (loopc = 0; loopc<100 && !(inb(DSP_DATA_AVAIL) & 0x80); loopc++)
DELAY(30);
if (inb(DSP_READ) != 0xAA) {
DEB(printf("sb_reset_dsp 0x%x failed\n", io_base));
return 0; /* Sorry */
}
return 1;
}
/*
* only used in sb_attach from here.
*/
static void
sb_dsp_init(snddev_info *d, struct isa_device *dev)
{
int i, x;
char *fmt = NULL ;
int io_base = dev->id_iobase ;
d->bd_id = 0 ;
sb_reset_dsp(io_base);
sb_cmd(io_base, DSP_CMD_GETVER); /* Get version */
for (i = 10000; i; i--) { /* perhaps wait longer on a fast machine ? */
if (inb(DSP_DATA_AVAIL) & 0x80) { /* wait for Data Ready */
if ( (d->bd_id & 0xff00) == 0)
d->bd_id = inb(DSP_READ) << 8; /* major */
else {
d->bd_id |= inb(DSP_READ); /* minor */
break;
}
} else
DELAY(20);
}
/*
* now do various initializations depending on board id.
*/
fmt = "SoundBlaster %d.%d" ; /* default */
switch ( d->bd_id >> 8 ) {
case 0 :
printf("\n\nFailed to get SB version (%x) - possible I/O conflict\n\n",
inb(DSP_DATA_AVAIL));
d->bd_id = 0x100;
case 1 : /* old sound blaster has nothing... */
break ;
case 2 :
d->dbuf_in.chan = d->dbuf_out.chan ; /* half duplex */
d->bd_flags |= BD_F_DUP_MIDI ;
if (d->bd_id == 0x200)
break ; /* no mixer on the 2.0 */
d->bd_flags &= ~BD_F_MIX_MASK ;
d->bd_flags |= BD_F_MIX_CT1335 ;
break ;
case 4 :
fmt = "SoundBlaster 16 %d.%d";
d->audio_fmt |= AFMT_FULLDUPLEX | AFMT_WEIRD | AFMT_S8 | AFMT_S16_LE;
d->bd_flags |= BD_F_SB16;
d->bd_flags &= ~BD_F_MIX_MASK ;
d->bd_flags |= BD_F_MIX_CT1745 ;
/* soft irq/dma configuration */
x = -1 ;
if (d->irq == 5) x = 2;
else if (d->irq == 7) x = 4;
else if (d->irq == 9) x = 1;
else if (d->irq == 10) x = 8;
if (x == -1)
printf("<%s>%d: bad irq %d (only 5,7,9,10 allowed)\n",
d->name, dev->id_unit, d->irq);
else
sb_setmixer(io_base, IRQ_NR, x);
if (d->dbuf_out.chan == d->dbuf_in.chan) {
printf("WARNING: sb: misconfigured secondary DMA channel\n");
}
sb_setmixer(io_base, DMA_NR, (1 << d->dbuf_out.chan) | (1 << d->dbuf_in.chan));
break ;
case 3 :
d->dbuf_in.chan = d->dbuf_out.chan ; /* half duplex */
fmt = "SoundBlaster Pro %d.%d";
d->bd_flags |= BD_F_DUP_MIDI ;
d->bd_flags &= ~BD_F_MIX_MASK ;
d->bd_flags |= BD_F_MIX_CT1345 ;
if (d->bd_id == 0x301) {
int ess_major = 0, ess_minor = 0;
/*
* Try to detect ESS chips.
*/
sb_cmd(io_base, DSP_CMD_GETID); /* Return ident. bytes. */
for (i = 1000; i; i--) {
if (inb(DSP_DATA_AVAIL) & 0x80) { /* wait for Data Ready */
if (ess_major == 0)
ess_major = inb(DSP_READ);
else {
ess_minor = inb(DSP_READ);
break;
}
} else
DELAY(20);
}
if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) {
/* the ESS488 can be treated as an SBPRO */
printf("ESS488 (rev %d)\n", ess_minor & 0x0f);
break ;
} else if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) {
int rev = ess_minor & 0xf ;
if ( rev >= 8 )
printf("ESS1868 (rev %d)\n", rev);
else
printf("ESS688 (rev %d)\n", rev);
/* d->audio_fmt |= AFMT_S16_LE; */ /* not yet... */
break ; /* XXX */
} else {
printf("Unknown card 0x%x 0x%x -- hope it is SBPRO\n",
ess_major, ess_minor);
break ;
}
}
}
sprintf(d->name, fmt, (d->bd_id >> 8) &0xff, d->bd_id & 0xff);
sb_mix_init(d);
}
static void
sb_mix_init(snddev_info *d)
{
switch (d->bd_flags & BD_F_MIX_MASK) {
case BD_F_MIX_CT1345 : /* SB 3.0 has 1345 mixer */
d->mix_devs = SBPRO_MIXER_DEVICES ;
d->mix_rec_devs = SBPRO_RECORDING_DEVICES ;
d->mix_recsrc = SOUND_MASK_MIC ;
sb_setmixer(d->io_base, 0, 1 ); /* reset mixer */
sb_setmixer(d->io_base, MIC_VOL , 0x6 ); /* mic volume max */
sb_setmixer(d->io_base, RECORD_SRC , 0x0 ); /* mic source */
sb_setmixer(d->io_base, FM_VOL , 0x0 ); /* no midi */
break ;
case BD_F_MIX_CT1745 : /* SB16 mixer ... */
d->mix_devs = SB16_MIXER_DEVICES ;
d->mix_rec_devs = SB16_RECORDING_DEVICES ;
d->mix_recsrc = SOUND_MASK_MIC ;
}
sb_mixer_reset(d);
}
/*
* Common code for the midi and pcm functions
*
* sb_cmd write a single byte to the CMD port.
* sb_cmd2 write a CMD + 1 byte arg
* sb_cmd3 write a CMD + 2 byte arg
* sb_get_byte returns a single byte from the DSP data port
*
* ess_write is actually sb_cmd2
* ess_read access ext. regs via sb_cmd(0xc0, reg) followed by sb_get_byte
*/
int
sb_cmd(int io_base, u_char val)
{
int i;
for (i = 0; i < 1000 ; i++) {
if ((inb(io_base + SBDSP_STATUS) & 0x80) == 0) {
outb(io_base + SBDSP_CMD, val);
return 1;
}
if (i > 10)
DELAY (i > 100 ? 1000 : 10 );
}
printf("SoundBlaster: DSP Command(0x%02x) timeout. IRQ conflict ?\n", val);
return 0;
}
int
sb_cmd3(int io_base, u_char cmd, int val)
{
if (sb_cmd(io_base, cmd)) {
sb_cmd(io_base, val & 0xff );
sb_cmd(io_base, (val>>8) & 0xff );
return 1 ;
} else
return 0;
}
int
sb_cmd2(int io_base, u_char cmd, int val)
{
if (sb_cmd(io_base, cmd)) {
sb_cmd(io_base, val & 0xff );
return 1 ;
} else
return 0;
}
/*
* in the SB, there is a set of indirect "mixer" registers with
* address at offset 4, data at offset 5
*/
void
sb_setmixer(int io_base, u_int port, u_int value)
{
u_long flags;
flags = spltty();
outb(io_base + 4, (u_char) (port & 0xff)); /* Select register */
DELAY(10);
outb(io_base + 5, (u_char) (value & 0xff));
DELAY(10);
splx(flags);
}
int
sb_getmixer(int io_base, u_int port)
{
int val;
u_long flags;
flags = spltty();
outb(io_base + SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */
DELAY(10);
val = inb(io_base + SB_MIX_DATA);
DELAY(10);
splx(flags);
return val;
}
u_int
sb_get_byte(int io_base)
{
int i;
for (i = 1000; i; i--)
if (inb(DSP_DATA_AVAIL) & 0x80)
return inb(DSP_READ);
else
DELAY(20);
return 0xffff;
}
int
ess_write(int io_base, u_char reg, int val)
{
return sb_cmd2(io_base, reg, val);
}
int
ess_read(int io_base, u_char reg)
{
if (!sb_cmd(io_base, 0xc0) || !sb_cmd(io_base, reg) )
return 0xffff ;
return sb_get_byte(io_base);
}
/*
* various utility functions for the DSP
*/
/*
* dsp_speed updates the speed setting from the descriptor. make sure
* it is called at spltty().
* Besides, it takes care of stereo setting.
*/
static int
dsp_speed(snddev_info *d)
{
u_char tconst;
u_long flags;
int max_speed = 44100, speed = d->play_speed ;
/*
* special code for the SB16
*/
if (d->bd_flags & BD_F_SB16) {
RANGE (speed, 5000, 45000);
d->play_speed = d->rec_speed = speed ;
sb_cmd(d->io_base, 0x41);
sb_cmd(d->io_base, d->play_speed >> 8 );
sb_cmd(d->io_base, d->play_speed & 0xff );
sb_cmd(d->io_base, 0x42);
sb_cmd(d->io_base, d->rec_speed >> 8 );
sb_cmd(d->io_base, d->rec_speed & 0xff );
return speed ;
}
/*
* special code for the ESS ...
*/
if (d->bd_flags & BD_F_ESS) {
int t;
RANGE (speed, 5000, 49000);
if (speed > 22000) {
t = (795500 + speed / 2) / speed;
speed = (795500 + t / 2) / t ;
t = (256 - t ) | 0x80 ;
} else {
t = (397700 + speed / 2) / speed;
speed = (397700 + t / 2) / t ;
t = 128 - t ;
}
ess_write(d->io_base, 0xa1, t); /* set time constant */
d->play_speed = d->rec_speed = speed ;
speed = (speed * 9 ) / 20 ;
t = 256-7160000/(speed*82);
ess_write(d->io_base,0xa2,t);
return speed ;
}
/*
* This is code for the SB3.x and lower.
* Only some models can do stereo, and only if not
* simultaneously using midi.
* At the moment we do not support either...
*/
#if 0
d->flags &= ~SND_F_STEREO;
#endif
/*
* here enforce speed limitations.
*/
if (d->bd_id <= 0x200)
max_speed = 22050; /* max 22050 on SB 1.X */
/*
* SB models earlier than SB Pro have low limit for the
* input rate. Note that this is only for input, but since
* we do not support separate values for rec & play....
*/
if (d->bd_id <= 0x200)
max_speed = 13000;
else if (d->bd_id < 0x300)
max_speed = 15000;
RANGE(speed, 4000, max_speed);
if (d->flags & SND_F_STEREO) /* really unused right now... */
speed *= 2;
/*
* Now the speed should be valid. Compute the value to be
* programmed into the board.
*/
if (speed > 22050) { /* High speed mode on 2.01/3.xx */
int tmp;
tconst = (u_char) ((65536 - ((256000000 + speed / 2) / speed)) >> 8) ;
d->bd_flags |= BD_F_HISPEED ;
flags = spltty();
sb_cmd2(d->io_base, 0x40, tconst); /* set time constant */
splx(flags);
tmp = 65536 - (tconst << 8);
speed = (256000000 + tmp / 2) / tmp;
} else {
int tmp;
d->bd_flags &= ~BD_F_HISPEED ;
tconst = (256 - ((1000000 + speed / 2) / speed)) & 0xff;
flags = spltty();
sb_cmd2(d->io_base, 0x40, tconst); /* set time constant */
splx(flags);
tmp = 256 - tconst;
speed = (1000000 + tmp / 2) / tmp;
}
if (d->flags & SND_F_STEREO) /* really unused right now... */
speed /= 2;
d->play_speed = d->rec_speed = speed;
return speed;
}
/*
* mixer support, originally in sb_mixer.c
*/
static void
sb_set_recsrc(snddev_info *d, int mask)
{
u_char recdev ;
mask &= d->mix_rec_devs;
switch (d->bd_flags & BD_F_MIX_MASK) {
case BD_F_MIX_CT1345 :
if (mask == SOUND_MASK_LINE)
recdev = 6 ;
else if (mask == SOUND_MASK_CD)
recdev = 2 ;
else { /* default: mic */
mask = SOUND_MASK_MIC ;
recdev = 0 ;
}
sb_setmixer(d->io_base, RECORD_SRC,
recdev | (sb_getmixer(d->io_base, RECORD_SRC) & ~7 ));
break ;
case BD_F_MIX_CT1745 : /* sb16 */
if (mask == 0)
mask = SOUND_MASK_MIC ; /* XXX For compatibility. Bug ? */
recdev = 0 ;
if (mask & SOUND_MASK_MIC)
recdev |= 1 ;
if (mask & SOUND_MASK_CD)
recdev |= 6 ; /* l+r cd */
if (mask & SOUND_MASK_LINE)
recdev |= 0x18 ; /* l+r line */
if (mask & SOUND_MASK_SYNTH)
recdev |= 0x60 ; /* l+r midi */
sb_setmixer(d->io_base, SB16_IMASK_L, recdev);
sb_setmixer(d->io_base, SB16_IMASK_R, recdev);
/*
* since the same volume controls apply to the input and
* output sections, the best approach to have a consistent
* behaviour among cards would be to disable the output path
* on devices which are used to record.
* However, since users like to have feedback, we only disable
* the mike -- permanently.
*/
sb_setmixer(d->io_base, SB16_OMASK, 0x1f & ~1);
break ;
}
d->mix_recsrc = mask;
}
static void
sb_mixer_reset(snddev_info *d)
{
int i;
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
sb_mixer_set(d, i, levels[i]);
if (d->bd_flags & BD_F_SB16) {
sb_setmixer(d->io_base, 0x3c, 0x1f); /* make all output active */
sb_setmixer(d->io_base, 0x3d, 0); /* make all inputs-l off */
sb_setmixer(d->io_base, 0x3e, 0); /* make all inputs-r off */
}
sb_set_recsrc(d, SOUND_MASK_MIC);
}
static int
sb_mixer_set(snddev_info *d, int dev, int value)
{
int left = value & 0x000000ff;
int right = (value & 0x0000ff00) >> 8;
int regoffs;
u_char val;
mixer_tab *iomap;
#ifdef JAZZ16
if (d->bd_flags & BD_F_JAZZ16 && d->bd_flags & BD_F_JAZZ16_2)
return smw_mixer_set(dev, value);
#endif
if (dev == SOUND_MIXER_RECSRC) {
sb_set_recsrc(d, value);
return 0 ;
}
if (left > 100)
left = 100;
if (right > 100)
right = 100;
if (dev > 31)
return EINVAL ;
if (!(d->mix_devs & (1 << dev))) /* Not supported */
return EINVAL;
switch ( d->bd_flags & BD_F_MIX_MASK ) {
default:
/* mixer unknown, fail... */
return EINVAL ;/* XXX change this */
case BD_F_MIX_CT1345 :
iomap = &sbpro_mix ;
break;
case BD_F_MIX_CT1745 :
iomap = &sb16_mix ;
break;
/* XXX how about the SG NX Pro, iomap = sgnxpro_mix */
}
regoffs = (*iomap)[dev][LEFT_CHN].regno;
if (regoffs == 0)
return EINVAL;
val = sb_getmixer(d->io_base, regoffs);
change_bits(iomap, &val, dev, LEFT_CHN, left);
d->mix_levels[dev] = left | (left << 8);
if ((*iomap)[dev][RIGHT_CHN].regno != regoffs) { /* Change register */
sb_setmixer(d->io_base, regoffs, val); /* Save the old one */
regoffs = (*iomap)[dev][RIGHT_CHN].regno;
if (regoffs == 0)
return 0 ; /* Just left channel present */
val = sb_getmixer(d->io_base, regoffs); /* Read the new one */
}
change_bits(iomap, &val, dev, RIGHT_CHN, right);
sb_setmixer(d->io_base, regoffs, val);
d->mix_levels[dev] = left | (right << 8);
return 0 ; /* ok */
}
/*
* now support for some PnP boards.
*/
#if NPNP > 0
static char *ess1868_probe(u_long csn, u_long vend_id);
static void ess1868_attach(u_long csn, u_long vend_id, char *name,
struct isa_device *dev);
static struct pnp_device ess1868 = {
"ESS1868",
ess1868_probe,
ess1868_attach,
&nsnd, /* use this for all sound cards */
&tty_imask /* imask */
};
DATA_SET (pnpdevice_set, ess1868);
static char *
ess1868_probe(u_long csn, u_long vend_id)
{
/*
* pnp X 1 os enable drq0 3 irq0 12 port0 0x240
*/
if (vend_id == 0x68187316) {
struct pnp_cinfo d ;
read_pnp_parms ( &d , 1 ) ;
if (d.enable == 0) {
printf("This is an ESS1868, but LDN 1 is disabled\n");
return NULL;
}
return "ESS1868" ;
}
return NULL ;
}
static void
ess1868_attach(u_long csn, u_long vend_id, char *name,
struct isa_device *dev)
{
struct pnp_cinfo d ;
snddev_info tmp_d ; /* patched copy of the basic snddev_info */
int the_irq = 0 ;
tmp_d = sb_op_desc;
snddev_last_probed = &tmp_d;
#if 0
read_pnp_parms ( &d , 3 ); /* disable LDN 3 */
d.port[0] = 0 ;
d.enable = 0 ;
write_pnp_parms ( &d , 3 );
read_pnp_parms ( &d , 2 ); /* disable LDN 2 */
d.port[0] = 0 ;
d.enable = 0 ;
write_pnp_parms ( &d , 2 );
read_pnp_parms ( &d , 0 ); /* read config base */
tmp_d.conf_base = d.port[0];
write_pnp_parms ( &d , 0 );
#endif
read_pnp_parms ( &d , 1 ) ;
dev->id_iobase = d.port[0];
d.port[1] = 0 ;
d.port[2] = 0 ;
write_pnp_parms ( &d , 1 );
enable_pnp_card();
dev->id_drq = d.drq[0] ; /* primary dma */
dev->id_irq = (1 << d.irq[0] ) ;
dev->id_intr = pcmintr ;
dev->id_flags = 0 /* DV_F_DUAL_DMA | (d.drq[1] ) */;
snddev_last_probed->probe(dev); /* not really necessary but doesn't harm */
pcmattach(dev);
}
/*
* A driver for some SB16pnp and compatibles...
*
* Avance Asound 100 -- 0x01009305
* Avance Logic ALS100+ -- 0x10019305
* xxx -- 0x2b008c0e
*
*/
static char *sb16pnp_probe(u_long csn, u_long vend_id);
static void sb16pnp_attach(u_long csn, u_long vend_id, char *name,
struct isa_device *dev);
static struct pnp_device sb16pnp = {
"SB16pnp",
sb16pnp_probe,
sb16pnp_attach,
&nsnd, /* use this for all sound cards */
&tty_imask /* imask */
};
DATA_SET (pnpdevice_set, sb16pnp);
static char *
sb16pnp_probe(u_long csn, u_long vend_id)
{
char *s = NULL ;
/*
* The SB16/AWExx cards seem to differ in the fourth byte of
* the vendor id, so I have just masked it for the time being...
* Reported values are:
* SB16 Value PnP: 0x2b008c0e
* SB AWExx PnP: 0x39008c0e 0x9d008c0e 0xc3008c0e
* Vibra16X: 0xf0008c0e
*/
if (vend_id == 0xf0008c0e)
s = "Vibra16X" ;
else if ( (vend_id & 0xffffff) == (0x9d008c0e & 0xffffff) )
s = "SB16 PnP";
else if (vend_id == 0x01009305)
s = "Avance Asound 100" ;
else if (vend_id == 0x10019305)
s = "Avance Logic 100+" ; /* Vibra16X-class */
if (s) {
struct pnp_cinfo d;
read_pnp_parms(&d, 0);
if (d.enable == 0) {
printf("This is a %s, but LDN 0 is disabled\n", s);
return NULL ;
}
return s ;
}
return NULL ;
}
static void
sb16pnp_attach(u_long csn, u_long vend_id, char *name,
struct isa_device *dev)
{
struct pnp_cinfo d ;
snddev_info tmp_d ; /* patched copy of the basic snddev_info */
tmp_d = sb_op_desc;
snddev_last_probed = &tmp_d;
read_pnp_parms ( &d , 0 ) ;
d.port[1] = 0 ; /* only the first address is used */
dev->id_iobase = d.port[0];
tmp_d.synth_base = d.port[2];
write_pnp_parms ( &d , 0 );
enable_pnp_card();
dev->id_drq = d.drq[0] ; /* primary dma */
dev->id_irq = (1 << d.irq[0] ) ;
dev->id_intr = pcmintr ;
dev->id_flags = DV_F_DUAL_DMA | (d.drq[1] ) ;
pcm_info[dev->id_unit] = tmp_d; /* pcm_info[] will be reinitialized after */
snddev_last_probed->probe(dev); /* not really necessary but doesn't harm */
if (vend_id == 0x10019305 || vend_id == 0xf0008c0e) {
/*
* XXX please add here the vend_id for other vibra16X cards...
* And remember, must change tmp_d, not
*/
tmp_d.bd_flags |= BD_F_SB16X ;
}
pcmattach(dev);
}
#endif /* NPNP */
#endif