freebsd-nq/sys/dev/sound/isa/mss.c
1999-05-06 22:26:28 +00:00

2336 lines
60 KiB
C

/*
* sound/ad1848.c
*
* Driver for Microsoft Sound System/Windows Sound System (mss)
* -compatible boards. This includes:
*
* AD1848, CS4248, CS423x, OPTi931, Yamaha OPL/SAx and many others.
*
* Copyright Luigi Rizzo, 1997,1998
* Copyright by Hannu Savolainen 1994, 1995
*
* 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.
*
* Full data sheets in PDF format for the MSS-compatible chips
* are available at
*
* http://www.crystal.com/ for the CS42XX series, or
* http://www.opti.com/ for the OPTi931
*/
#include <i386/isa/snd/sound.h>
#if NPCM > 0
/*
* board-specific include files
*/
#include <i386/isa/snd/mss.h>
/*
* prototypes for procedures exported in the device descriptor
*/
static int mss_probe(struct isa_device *dev);
static int mss_attach(struct isa_device *dev);
d_open_t mss_open; /* this is a generic full-duplex open routine */
static d_close_t mss_close;
static d_ioctl_t mss_ioctl;
static irq_proc_t mss_intr;
static irq_proc_t opti931_intr;
static snd_callback_t mss_callback;
/*
* prototypes for local functions
*/
static void mss_reinit(snddev_info *d);
static int AD_WAIT_INIT(snddev_info *d, int x);
static int mss_mixer_set(snddev_info *d, int dev, int value);
static int mss_set_recsrc(snddev_info *d, int mask);
static void ad1848_mixer_reset(snddev_info *d);
static void opti_write(int io_base, u_char reg, u_char data);
static u_char opti_read(int io_base, u_char reg);
static void ad_write(snddev_info *d, int reg, u_char data);
static void ad_write_cnt(snddev_info *d, int reg, u_short data);
static int ad_read(snddev_info *d, int reg);
#if NPNP > 0 /* the ad1816 is pnp only */
/* ad1816 prototypes */
/* IO primitives */
static int ad1816_wait_init(snddev_info * d, int x);
static u_short ad1816_read(snddev_info * d, u_int reg);
static void ad1816_write(snddev_info * d, u_int reg, u_short data);
/* intr and callback functions */
static irq_proc_t ad1816_intr;
static snd_callback_t ad1816_callback;
/* device specific ioctl calls */
static d_ioctl_t ad1816_ioctl;
/* parameter set functions */
static void ad1816_reinit(snddev_info * d);
static int ad1816_mixer_set(snddev_info * d, int dev, int value);
static int ad1816_set_recsrc(snddev_info * d, int mask);
static void ad1816_mixer_reset(snddev_info * d);
/* ad1816 prototypes end */
#endif
/*
* device descriptors for the boards supported by this module.
*/
snddev_info mss_op_desc = {
"mss",
SNDCARD_MSS,
mss_probe,
mss_attach,
mss_open,
mss_close,
NULL /* mss_read */,
NULL /* mss_write */,
mss_ioctl,
sndselect /* mss_select */,
mss_intr,
mss_callback ,
DSP_BUFFSIZE, /* bufsize */
AFMT_STEREO |
AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW, /* audio formats */
/*
* the enhanced boards also have AFMT_IMA_ADPCM | AFMT_S16_BE
* but we do not use these modes.
*/
} ;
/*
* mss_probe() is the probe routine. Note, it is not necessary to
* go through this for PnP devices, since they are already
* indentified precisely using their PnP id.
*
* The base address supplied in the device refers to the old MSS
* specs where the four 4 registers in io space contain configuration
* information. Some boards (as an example, early MSS boards)
* has such a block of registers, whereas others (generally CS42xx)
* do not. In order to distinguish between the two and do not have
* to supply two separate probe routines, the flags entry in isa_device
* has a bit to mark this.
*
*/
static int
mss_probe(struct isa_device *dev)
{
u_char tmp;
int irq = ffs(dev->id_irq) - 1;
bzero(&pcm_info[dev->id_unit], sizeof(pcm_info[dev->id_unit]) );
if (dev->id_iobase == -1) {
dev->id_iobase = 0x530;
BVDDB(printf("mss_probe: no address supplied, try default 0x%x\n",
dev->id_iobase));
}
if (snd_conflict(dev->id_iobase))
return 0 ;
if ( !(dev->id_flags & DV_F_TRUE_MSS) ) /* Has no IRQ/DMA registers */
goto mss_probe_end;
/*
* Check if the IO port returns valid signature. The original MS
* Sound system returns 0x04 while some cards
* (AudioTriX Pro for example) return 0x00 or 0x0f.
*/
tmp = inb(dev->id_iobase + 3);
if (tmp == 0xff) { /* Bus float */
BVDDB(printf("I/O address inactive (%x), try pseudo_mss\n", tmp));
dev->id_flags &= ~DV_F_TRUE_MSS ;
goto mss_probe_end;
}
tmp &= 0x3f ;
if (tmp != 0x04 && tmp != 0x0f && tmp != 0x00) {
BVDDB(printf("No MSS signature detected on port 0x%x (0x%x)\n",
dev->id_iobase, inb(dev->id_iobase + 3)));
return 0;
}
if (irq > 11) {
printf("MSS: Bad IRQ %d\n", irq);
return 0;
}
if (dev->id_drq != 0 && dev->id_drq != 1 && dev->id_drq != 3) {
printf("MSS: Bad DMA %d\n", dev->id_drq);
return 0;
}
if (inb(dev->id_iobase + 3) & 0x80) {
/* 8-bit board: only drq1/3 and irq7/9 */
if (dev->id_drq == 0) {
printf("MSS: Can't use DMA0 with a 8 bit card/slot\n");
return 0;
}
if (irq != 7 && irq != 9) {
printf("MSS: Can't use IRQ%d with a 8 bit card/slot\n", irq);
return 0;
}
}
mss_probe_end:
return mss_detect(dev) ? 8 : 0 ; /* mss uses 8 regs */
}
#if NPNP > 0
static int
ad1816_attach(struct isa_device *dev)
{
snddev_info *d = &(pcm_info[dev->id_unit]);
dev->id_alive = 16; /* number of io ports */
if (FULL_DUPLEX(d))
d->audio_fmt |= AFMT_FULLDUPLEX;
ad1816_write(d, 1, 0x2);/* disable interrupts */
ad1816_write(d, 32, 0x90F0); /* SoundSystem Mode, split format */
ad1816_write(d, 5, 0x8080); /* FM volume mute */
ad1816_write(d, 6, 0x8080); /* I2S1 volume mute */
ad1816_write(d, 7, 0x8080); /* I2S0 volume mute */
ad1816_write(d, 17, 0x8888); /* VID Volume mute */
ad1816_write(d, 20, 0x5050); /* Source select Mic & auto gain ctrl
* off */
/* adc gain is set to 0 */
ad1816_reinit(d);
ad1816_mixer_reset(d);
return 0 ;
}
#endif /* NPNP */
/*
* the address passed as io_base for mss_attach is also the old
* MSS base address (e.g. 0x530). The codec is four locations ahead.
* Note that the attach routine for PnP devices might support
* device-specific initializations.
*/
static int
mss_attach(struct isa_device *dev)
{
snddev_info *d = &(pcm_info[dev->id_unit]);
printf("mss_attach <%s>%d at 0x%x irq %d dma %d:%d flags 0x%x\n",
d->name, dev->id_unit,
d->io_base, d->irq, d->dbuf_out.chan, d->dbuf_in.chan, dev->id_flags);
#if NPNP > 0
if (d->bd_id == MD_AD1816)
return ad1816_attach(dev);
#endif
dev->id_alive = 8 ; /* number of io ports */
/* should be already set but just in case... */
if ( dev->id_flags & DV_F_TRUE_MSS ) {
/* has IRQ/DMA registers, set IRQ and DMA addr */
static char interrupt_bits[12] = {
-1, -1, -1, -1, -1, 0x28, -1, 0x08, -1, 0x10, 0x18, 0x20
};
static char dma_bits[4] = { 1, 2, 0, 3 };
char bits ;
if (d->irq == -1 || (bits = interrupt_bits[d->irq]) == -1) {
dev->id_irq = 0 ; /* makk invalid irq */
return 0 ;
}
outb(dev->id_iobase, bits | 0x40); /* config port */
if ((inb(dev->id_iobase + 3) & 0x40) == 0) /* version port */
printf("[IRQ Conflict?]");
/* Write IRQ+DMA setup */
if ( ! FULL_DUPLEX(d) ) /* single chan dma */
outb(dev->id_iobase, bits | dma_bits[d->dbuf_out.chan]);
else {
if (d->dbuf_out.chan == 0 && d->dbuf_in.chan == 1)
bits |= 5 ;
else if (d->dbuf_out.chan == 1 && d->dbuf_in.chan == 0)
bits |= 6 ;
else if (d->dbuf_out.chan == 3 && d->dbuf_in.chan == 0)
bits |= 7 ;
else {
printf("invalid dual dma config %d:%d\n",
d->dbuf_out.chan, d->dbuf_in.chan);
dev->id_irq = 0 ;
dev->id_alive = 0 ; /* this makes attach fail. */
return 0 ;
}
outb(dev->id_iobase, bits );
}
}
if (1) { /* machine-specific code for the Toshiba Libretto */
u_char r6, r9;
outb( 0x370, 6 /* dma config */ );
outb( 0x371, 0xa9 /* config: DMA-B for rec, DMA-A for play */);
r6 = inb( 0x371 /* read */ );
outb( 0x370, 0xa /* version */ );
r9 = inb( 0x371 /* read */ );
DEB(printf("Yamaha: ver 0x%x DMA config 0x%x\n", r6, r9);)
/*
* yamaha - set volume to max
*/
outb( 0x370, 7 /* volume left */ );
outb( 0x371, 0 /* max level */ );
outb( 0x370, 8 /* volume right */ );
outb( 0x371, 0 /* max level */ );
}
if ( FULL_DUPLEX(d) )
d->audio_fmt |= AFMT_FULLDUPLEX ;
if (d->bd_id == MD_YM0020) {
DDB(printf("setting up yamaha registers\n"));
outb(0x370, 6 /* dma config */ ) ;
if (FULL_DUPLEX(d))
outb(0x371, 0xa9 ); /* use both dma chans */
else
outb(0x371, 0x8b ); /* use low dma chan */
}
mss_reinit(d);
ad1848_mixer_reset(d);
return 0;
}
int
mss_open(dev_t dev, int flags, int mode, struct proc * p)
{
int unit;
snddev_info *d;
u_long s;
dev = minor(dev);
unit = dev >> 4 ;
dev &= 0xf ;
d = &pcm_info[unit] ;
s = spltty();
/*
* This was meant to support up to 2 open descriptors for the
* some device, and check proper device usage on open.
* Unfortunately, the kernel will trap all close() calls but
* the last one, with the consequence that we cannot really
* keep track of which channels are busy.
* So, the correct tests cannot be done :( and we must rely
* on the locks on concurrent operations of the same type and
* on some approximate tests...
*/
if (dev == SND_DEV_AUDIO)
d->flags |= SND_F_BUSY_AUDIO ;
else if (dev == SND_DEV_DSP)
d->flags |= SND_F_BUSY_DSP ;
else if (dev == SND_DEV_DSP16)
d->flags |= SND_F_BUSY_DSP16 ;
if ( d->flags & SND_F_BUSY )
splx(s); /* device was already set, no need to reinit */
else {
/*
* device was idle. Do the necessary initialization,
* but no need keep interrupts blocked.
* will not get them
*/
splx(s);
d->play_speed = d->rec_speed = DSP_DEFAULT_SPEED ;
d->flags |= SND_F_BUSY ;
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 ;
if (flags & O_NONBLOCK)
d->flags |= SND_F_NBIO ;
switch (dev) {
default :
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 ;
case SND_DEV_DSP16 :
d->play_fmt = d->rec_fmt = AFMT_S16_LE ;
break;
}
ask_init(d); /* and reset buffers... */
}
return 0 ;
}
static int
mss_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 ;
dev &= 0xf;
d = &pcm_info[unit] ;
/*
* We will only get a single close call when the last reference
* to the device is gone. But we must handle ourselves references
* through different devices.
*/
s = spltty();
if (dev == SND_DEV_AUDIO)
d->flags &= ~SND_F_BUSY_AUDIO ;
else if (dev == SND_DEV_DSP)
d->flags &= ~SND_F_BUSY_DSP ;
else if (dev == SND_DEV_DSP16)
d->flags &= ~SND_F_BUSY_DSP16 ;
if ( d->flags & SND_F_BUSY_ANY ) /* still some device open */
splx(s);
else { /* last one */
d->flags |= SND_F_CLOSING ;
splx(s); /* is this ok here ? */
snd_flush(d);
/* Clear interrupt status */
if ( d->bd_id == MD_AD1816 )
outb(ad1816_int(d), 0);
else
outb(io_Status(d), 0);
d->flags = 0 ;
}
return 0 ;
}
static int
mss_ioctl(dev_t dev, u_long cmd, caddr_t arg, int mode, struct proc * p)
{
snddev_info *d;
int unit;
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) ) {
cmd &= 0xff ;
if (cmd == SOUND_MIXER_RECSRC)
return mss_set_recsrc(d, *(int *)arg) ;
else
return mss_mixer_set(d, cmd, *(int *)arg) ;
}
return ENOSYS ; /* fallback to the default ioctl handler */
}
/*
* the callback routine to handle all dma ops etc.
* With the exception of INIT, all other callbacks are invoked
* with interrupts disabled.
*/
static int
mss_callback(snddev_info *d, int reason)
{
u_char m;
int retry, wr, cnt;
DEB(printf("-- mss_callback reason 0x%03x\n", reason));
wr = reason & SND_CB_WR ;
reason &= SND_CB_REASON_MASK ;
switch (reason) {
case SND_CB_INIT : /* called with int enabled and no pending I/O */
/*
* perform all necessary initializations for i/o
*/
d->rec_fmt = d->play_fmt ; /* no split format on the MSS */
snd_set_blocksize(d);
mss_reinit(d);
reset_dbuf(& (d->dbuf_in), SND_CHAN_RD );
reset_dbuf(& (d->dbuf_out), SND_CHAN_WR );
return 1 ;
break ;
case SND_CB_START :
cnt = wr ? d->dbuf_out.dl : d->dbuf_in.dl ;
if (d->play_fmt == AFMT_S16_LE)
cnt /= 2;
if (d->flags & SND_F_STEREO)
cnt /= 2;
cnt-- ;
DEB(printf("-- (re)start cnt %d\n", cnt));
m = ad_read(d,9) ;
DEB( if (m & 4) printf("OUCH! reg 9 0x%02x\n", m); );
m |= wr ? I9_PEN : I9_CEN ; /* enable DMA */
/*
* on the OPTi931 the enable bit seems hard to set...
*/
for (retry = 10; retry; retry--) {
ad_write(d, 9, m );
if (ad_read(d,9) ==m) break;
}
if (retry == 0)
printf("start dma, failed to set bit 0x%02x 0x%02x\n",
m, ad_read(d, 9) ) ;
if (wr || ! FULL_DUPLEX(d) )
ad_write_cnt(d, 14, cnt);
else
ad_write_cnt(d, 30, cnt);
break ;
case SND_CB_STOP :
case SND_CB_ABORT : /* XXX check this... */
m = ad_read(d,9) ;
m &= wr ? ~I9_PEN : ~I9_CEN ; /* Stop DMA */
/*
* on the OPTi931 the enable bit seems hard to set...
*/
for (retry = 10; retry ; retry-- ) {
ad_write(d, 9, m );
if (ad_read(d,9) ==m) break;
}
if (retry == 0)
printf("start dma, failed to clear bit 0x%02x 0x%02x\n",
m, ad_read(d, 9) ) ;
#if 1
/*
* try to disable DMA by clearing count registers. Not sure it
* is needed, and it might cause false interrupts when the
* DMA is re-enabled later.
*/
if (wr || ! FULL_DUPLEX(d) )
ad_write_cnt(d, 14, 0);
else
ad_write_cnt(d, 30, 0);
break;
#endif
}
return 0 ;
}
/*
* main irq handler for the CS423x. The OPTi931 code is
* a separate one.
* The correct way to operate for a device with multiple internal
* interrupt sources is to loop on the status register and ack
* interrupts until all interrupts are served and none are reported. At
* this point the IRQ line to the ISA IRQ controller should go low
* and be raised at the next interrupt.
*
* Since the ISA IRQ controller is sent EOI _before_ passing control
* to the isr, it might happen that we serve an interrupt early, in
* which case the status register at the next interrupt should just
* say that there are no more interrupts...
*/
static void
mss_intr(int unit)
{
snddev_info *d = &pcm_info[unit];
u_char c, served = 0;
int i;
DEB(printf("mss_intr\n"));
ad_read(d, 11); /* fake read of status bits */
/*
* loop until there are interrupts, but no more than 10 times.
*/
for (i=10 ; i && inb(io_Status(d)) & 1 ; i-- ) {
/* get exact reason for full-duplex boards */
c = FULL_DUPLEX(d) ? ad_read(d, 24) : 0x30 ;
c &= ~served ;
if ( d->dbuf_out.dl && (c & 0x10) ) {
served |= 0x10 ;
dsp_wrintr(d);
}
if ( d->dbuf_in.dl && (c & 0x20) ) {
served |= 0x20 ;
dsp_rdintr(d);
}
/*
* now ack the interrupt
*/
if ( FULL_DUPLEX(d) )
ad_write(d, 24, ~c); /* ack selectively */
else
outb(io_Status(d), 0); /* Clear interrupt status */
}
if (served == 0) {
printf("How strange... mss_intr with no reason!\n");
/*
* this should not happen... I have no idea what to do now.
* maybe should do a sanity check and restart dmas ?
*/
outb(io_Status(d), 0); /* Clear interrupt status */
}
}
/*
* the opti931 seems to miss interrupts when working in full
* duplex, so we try some heuristics to catch them.
*/
static void
opti931_intr(int unit)
{
snddev_info *d = &pcm_info[unit];
u_char masked=0, i11, mc11, c=0;
u_char reason; /* b0 = playback, b1 = capture, b2 = timer */
int loops = 10;
#if 0
reason = inb(io_Status(d));
if ( ! (reason & 1) ) {/* no int, maybe a shared line ? */
printf("opti931_intr: flag 0, mcir11 0x%02x\n", ad_read(d,11));
return;
}
#endif
i11 = ad_read(d, 11); /* XXX what's for ? */
again:
c=mc11 = FULL_DUPLEX(d) ? opti_read(d->conf_base, 11) : 0xc ;
mc11 &= 0x0c ;
if (c & 0x10) {
DEB(printf("Warning: CD interrupt\n");)
mc11 |= 0x10 ;
}
if (c & 0x20) {
DEB(printf("Warning: MPU interrupt\n");)
mc11 |= 0x20 ;
}
if (mc11 & masked)
printf("irq reset failed, mc11 0x%02x, masked 0x%02x\n", mc11, masked);
masked |= mc11 ;
/*
* the nice OPTi931 sets the IRQ line before setting the bits in
* mc11. So, on some occasions I have to retry (max 10 times).
*/
if ( mc11 == 0 ) { /* perhaps can return ... */
reason = inb(io_Status(d));
if (reason & 1) {
DEB(printf("one more try...\n");)
if (--loops)
goto again;
else
DDB(printf("opti_intr: irq but mc11 not set!...\n");)
}
if (loops==10)
printf("ouch, intr but nothing in mcir11 0x%02x\n", mc11);
return;
}
if ( d->dbuf_in.dl && (mc11 & 8) ) {
dsp_rdintr(d);
}
if ( d->dbuf_out.dl && (mc11 & 4) ) {
dsp_wrintr(d);
}
opti_write(d->conf_base, 11, ~mc11); /* ack */
if (--loops)
goto again;
DEB(printf("xxx too many loops\n");)
}
/*
* Second part of the file: functions local to this module.
* in this section a few routines to access MSS registers
*
*/
static void
opti_write(int io_base, u_char reg, u_char value)
{
outb(io_base, reg);
outb(io_base+1, value);
}
static u_char
opti_read(int io_base, u_char reg)
{
outb(io_base, reg);
return inb(io_base+1);
}
static void
gus_write(int io_base, u_char reg, u_char value)
{
outb(io_base + 3, reg);
outb(io_base + 5, value);
}
#if 0
static void
gus_writew(int io_base, u_char reg, u_short value)
{
outb(io_base + 3, reg);
outb(io_base + 4, value);
}
#endif
static u_char
gus_read(int io_base, u_char reg)
{
outb(io_base+3, reg);
return inb(io_base+5);
}
#if 0
static u_short
gus_readw(int io_base, u_char reg)
{
outb(io_base+3, reg);
return inw(io_base+4);
}
#endif
/*
* AD_WAIT_INIT waits if we are initializing the board and
* we cannot modify its settings
*/
static int
AD_WAIT_INIT(snddev_info *d, int x)
{
int arg=x, n = 0; /* to shut up the compiler... */
for (; x-- ; )
if ( (n=inb(io_Index_Addr(d))) & IA_BUSY)
DELAY(10);
else
return n ;
printf("AD_WAIT_INIT FAILED %d 0x%02x\n", arg, n);
return n ;
}
static int
ad_read(snddev_info *d, int reg)
{
u_long flags;
int x;
flags = spltty();
AD_WAIT_INIT(d, 201);
x = inb(io_Index_Addr(d)) & ~IA_AMASK ;
outb(io_Index_Addr(d), (u_char) (reg & IA_AMASK) | x ) ;
x = inb(io_Indexed_Data(d));
splx(flags);
return x;
}
static void
ad_write(snddev_info *d, int reg, u_char data)
{
u_long flags;
int x ;
flags = spltty();
AD_WAIT_INIT(d, 1002);
x = inb(io_Index_Addr(d)) & ~IA_AMASK ;
outb(io_Index_Addr(d), (u_char) (reg & IA_AMASK) | x ) ;
outb(io_Indexed_Data(d), data);
splx(flags);
}
static void
ad_write_cnt(snddev_info *d, int reg, u_short cnt)
{
ad_write(d, reg+1, cnt & 0xff );
ad_write(d, reg, cnt >> 8 ); /* upper base must be last */
}
static void
wait_for_calibration(snddev_info *d)
{
int n, t;
/*
* Wait until the auto calibration process has finished.
*
* 1) Wait until the chip becomes ready (reads don't return 0x80).
* 2) Wait until the ACI bit of I11 gets on
* 3) Wait until the ACI bit of I11 gets off
*/
n = AD_WAIT_INIT(d, 1000);
if (n & IA_BUSY)
printf("mss: Auto calibration timed out(1).\n");
for (t = 100 ; t>0 && (ad_read(d, 11) & 0x20) == 0 ; t--)
DELAY(100);
for (t = 100 ; t>0 && ad_read(d, 11) & 0x20 ; t--)
DELAY(100);
}
#if 0 /* unused right now... */
static void
ad_mute(snddev_info *d)
{
ad_write(d, 6, ad_read(d,6) | I6_MUTE);
ad_write(d, 7, ad_read(d,7) | I6_MUTE);
}
static void
ad_unmute(snddev_info *d)
{
ad_write(d, 6, ad_read(d,6) & ~I6_MUTE);
ad_write(d, 7, ad_read(d,7) & ~I6_MUTE);
}
#endif
static void
ad_enter_MCE(snddev_info *d)
{
int prev;
d->bd_flags |= BD_F_MCE_BIT;
AD_WAIT_INIT(d, 203);
prev = inb(io_Index_Addr(d));
prev &= ~IA_TRD ;
outb(io_Index_Addr(d), prev | IA_MCE ) ;
}
static void
ad_leave_MCE(snddev_info *d)
{
u_long flags;
u_char prev;
if ( (d->bd_flags & BD_F_MCE_BIT) == 0 ) {
printf("--- hey, leave_MCE: MCE bit was not set!\n");
return;
}
AD_WAIT_INIT(d, 1000);
flags = spltty();
d->bd_flags &= ~BD_F_MCE_BIT;
prev = inb(io_Index_Addr(d));
prev &= ~IA_TRD ;
outb(io_Index_Addr(d), prev & ~IA_MCE ); /* Clear the MCE bit */
wait_for_calibration(d);
splx(flags);
}
/*
* only one source can be set...
*/
static int
mss_set_recsrc(snddev_info *d, int mask)
{
u_char recdev;
mask &= d->mix_rec_devs;
switch (mask) {
case SOUND_MASK_LINE:
case SOUND_MASK_LINE3:
recdev = 0;
break;
case SOUND_MASK_CD:
case SOUND_MASK_LINE1:
recdev = 0x40;
break;
case SOUND_MASK_IMIX:
recdev = 0xc0;
break;
case SOUND_MASK_MIC:
default:
mask = SOUND_MASK_MIC;
recdev = 0x80;
}
ad_write(d, 0, (ad_read(d, 0) & 0x3f) | recdev);
ad_write(d, 1, (ad_read(d, 1) & 0x3f) | recdev);
d->mix_recsrc = mask;
return 0;
}
/*
* there are differences in the mixer depending on the actual sound
* card.
*/
static int
mss_mixer_set(snddev_info *d, int dev, int value)
{
int left = value & 0x000000ff;
int right = (value & 0x0000ff00) >> 8;
int regoffs;
mixer_tab *mix_d = &mix_devices;
u_char old, val;
if (dev > 31)
return EINVAL;
if (!(d->mix_devs & (1 << dev)))
return EINVAL;
if (d->bd_id == MD_OPTI931)
mix_d = &(opti931_devices);
if ((*mix_d)[dev][LEFT_CHN].nbits == 0) {
DEB(printf("nbits = 0 for dev %d\n", dev) );
return EINVAL;
}
if (left > 100)
left = 100;
if (right > 100)
right = 100;
if ( (*mix_d)[dev][RIGHT_CHN].nbits == 0) /* Mono control */
right = left;
d->mix_levels[dev] = left | (right << 8);
#if 0
/* Scale volumes */
left = mix_cvt[left];
right = mix_cvt[right];
#endif
/*
* Set the left channel
*/
regoffs = (*mix_d)[dev][LEFT_CHN].regno;
old = val = ad_read(d, regoffs);
/*
* if volume is 0, mute chan. Otherwise, unmute.
*/
if (regoffs != 0) /* main input is different */
val = (left == 0 ) ? old | 0x80 : old & 0x7f ;
change_bits(mix_d, &val, dev, LEFT_CHN, left);
ad_write(d, regoffs, val);
DEB(printf("LEFT: dev %d reg %d old 0x%02x new 0x%02x\n",
dev, regoffs, old, val));
if ((*mix_d)[dev][RIGHT_CHN].nbits != 0) { /* have stereo */
/*
* Set the right channel
*/
regoffs = (*mix_d)[dev][RIGHT_CHN].regno;
old = val = ad_read(d, regoffs);
if (regoffs != 1)
val = (right == 0 ) ? old | 0x80 : old & 0x7f ;
change_bits(mix_d, &val, dev, RIGHT_CHN, right);
ad_write(d, regoffs, val);
DEB(printf("RIGHT: dev %d reg %d old 0x%02x new 0x%02x\n",
dev, regoffs, old, val));
}
return 0; /* success */
}
static void
ad1848_mixer_reset(snddev_info *d)
{
int i;
if (d->bd_id == MD_OPTI931)
d->mix_devs = OPTI931_MIXER_DEVICES;
else if (d->bd_id != MD_AD1848)
d->mix_devs = MODE2_MIXER_DEVICES;
else
d->mix_devs = MODE1_MIXER_DEVICES;
d->mix_rec_devs = MSS_REC_DEVICES;
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
if (d->mix_devs & (1 << i))
mss_mixer_set(d, i, default_mixer_levels[i]);
mss_set_recsrc(d, SOUND_MASK_MIC);
/*
* some device-specific things, mostly mute the mic to
* the output mixer so as to avoid hisses. In many cases this
* is the default after reset, this code is here mostly as a
* reminder that this might be necessary on other boards.
*/
switch(d->bd_id) {
case MD_OPTI931:
ad_write(d, 20, 0x88);
ad_write(d, 21, 0x88);
break;
case MD_YM0020:
/* set master volume to max */
DDB(printf("set yamaha master volume to max\n"); )
outb(0x370, 7) ;
outb(0x371, 0) ;
outb(0x370, 8) ;
outb(0x371, 0) ;
break;
case MD_GUSPNP:
/* this is only necessary in mode 3 ... */
ad_write(d, 22, 0x88);
ad_write(d, 23, 0x88);
}
}
/*
* mss_speed processes the value in play_speed finding the
* matching one. As a side effect, it returns the value to
* be written in the speed bits of the codec. It does _NOT_
* set the speed of the device (but it should!)
*/
static int
mss_speed(snddev_info *d)
{
/*
* In the CS4231, the low 4 bits of I8 are used to hold the
* sample rate. Only a fixed number of values is allowed. This
* table lists them. The speed-setting routines scans the table
* looking for the closest match. This is the only supported method.
*
* In the CS4236, there is an alternate metod (which we do not
* support yet) which provides almost arbitrary frequency setting.
* In the AD1845, it looks like the sample rate can be
* almost arbitrary, and written directly to a register.
* In the OPTi931, there is a SB command which provides for
* almost arbitrary frequency setting.
*
*/
static int speeds[] = {
8000, 5512, 16000, 11025, 27429, 18900, 32000, 22050,
-1, 37800, -1, 44100, 48000, 33075, 9600, 6615
};
int arg, i, sel = 0; /* assume entry 0 does not contain -1 */
arg = d->play_speed ;
for (i=1; i < 16 ; i++)
if (speeds[i] >0 && abs(arg-speeds[i]) < abs(arg-speeds[sel]) )
sel = i ;
d->play_speed = d->rec_speed = speeds[sel] ;
return sel ;
}
/*
* mss_format checks that the format is supported (or defaults to AFMT_U8)
* and returns the bit setting for the 1848 register corresponding to
* the desired format.
*
* fixed lr970724
*/
static int
mss_format(snddev_info *d)
{
int i, arg = d->play_fmt ;
/*
* The data format uses 3 bits (just 2 on the 1848). For each
* bit setting, the following array returns the corresponding format.
* The code scans the array looking for a suitable format. In
* case it is not found, default to AFMT_U8 (not such a good
* choice, but let's do it for compatibility...).
*/
static int fmts[] = {
AFMT_U8, AFMT_MU_LAW, AFMT_S16_LE, AFMT_A_LAW,
-1, AFMT_IMA_ADPCM, AFMT_U16_BE, -1
};
if ( (arg & d->audio_fmt) == 0 ) /* unsupported fmt, default to AFMT_U8 */
arg = AFMT_U8 ;
/* ulaw/alaw seems broken on the opti931... */
if (d->bd_id == MD_OPTI931 || d->bd_id == MD_GUSPNP) {
if (arg == AFMT_MU_LAW) {
arg = AFMT_U8 ;
d->flags |= SND_F_XLAT8 ;
} else
d->flags &= ~SND_F_XLAT8 ;
}
/*
* check that arg is one of the supported formats in d->format;
* otherwise fallback to AFMT_U8
*/
for (i=0 ; i<8 ; i++)
if (arg == fmts[i]) break;
if (i==8) { /* not found, default to AFMT_U8 */
arg = AFMT_U8 ;
i = 0 ;
}
d->play_fmt = d->rec_fmt = arg;
return i ;
}
/*
* mss_detect can be used in the probe and the attach routine.
*
* We store probe information in pcm_info[unit]. This descriptor
* is reinitialized just before the attach, so all relevant
* information is lost, and mss_detect must be run again in
* the attach routine if necessary.
*/
int
mss_detect(struct isa_device *dev)
{
int i;
u_char tmp, tmp1, tmp2 ;
snddev_info *d = &(pcm_info[dev->id_unit]);
char *name;
d->io_base = dev->id_iobase;
d->bd_flags |= BD_F_MCE_BIT ;
if (d->bd_id != 0) {
printf("preselected bd_id 0x%04x -- %s\n",
d->bd_id, d->name ? d->name : "???");
return 1;
}
name = "AD1848" ;
d->bd_id = MD_AD1848; /* AD1848 or CS4248 */
/*
* Check that the I/O address is in use.
*
* bit 7 of the base I/O port is known to be 0 after the chip has
* performed its power on initialization. Just assume this has
* happened before the OS is starting.
*
* If the I/O address is unused, it typically returns 0xff.
*/
for (i=0; i<10; i++)
if (inb(io_Index_Addr(d)) & IA_BUSY)
DELAY(10000); /* maybe busy, wait & retry later */
else
break ;
if ((inb(io_Index_Addr(d)) & IA_BUSY) != 0x00) { /* Not a AD1848 */
BVDDB(printf("mss_detect error, busy still set (0x%02x)\n",
inb(io_Index_Addr(d))));
return 0;
}
/*
* Test if it's possible to change contents of the indirect
* registers. Registers 0 and 1 are ADC volume registers. The bit
* 0x10 is read only so try to avoid using it.
*/
ad_write(d, 0, 0xaa);
ad_write(d, 1, 0x45);/* 0x55 with bit 0x10 clear */
tmp1 = ad_read(d, 0) ;
tmp2 = ad_read(d, 1) ;
if ( tmp1 != 0xaa || tmp2 != 0x45) {
BVDDB(printf("mss_detect error - IREG (0x%02x/0x%02x) want 0xaa/0x45\n",
tmp1, tmp2));
return 0;
}
ad_write(d, 0, 0x45);
ad_write(d, 1, 0xaa);
tmp1 = ad_read(d, 0) ;
tmp2 = ad_read(d, 1) ;
if (tmp1 != 0x45 || tmp2 != 0xaa) {
BVDDB(printf("mss_detect error - IREG2 (%x/%x)\n", tmp1, tmp2));
return 0;
}
/*
* The indirect register I12 has some read only bits. Lets try to
* change them.
*/
tmp = ad_read(d, 12);
ad_write(d, 12, (~tmp) & 0x0f);
tmp1 = ad_read(d, 12);
if ((tmp & 0x0f) != (tmp1 & 0x0f)) {
BVDDB(printf("mss_detect error - I12 (0x%02x was 0x%02x)\n",
tmp1, tmp));
return 0;
}
/*
* NOTE! Last 4 bits of the reg I12 tell the chip revision.
* 0x01=RevB
* 0x0A=RevC. also CS4231/CS4231A and OPTi931
*/
BVDDB(printf("mss_detect - chip revision 0x%02x\n", tmp & 0x0f);)
/*
* The original AD1848/CS4248 has just 16 indirect registers. This
* means that I0 and I16 should return the same value (etc.). Ensure
* that the Mode2 enable bit of I12 is 0. Otherwise this test fails
* with new parts.
*/
ad_write(d, 12, 0); /* Mode2=disabled */
for (i = 0; i < 16; i++)
if ((tmp1 = ad_read(d, i)) != (tmp2 = ad_read(d, i + 16))) {
BVDDB(printf("mss_detect warning - I%d: 0x%02x/0x%02x\n",
i, tmp1, tmp2));
/*
* note - this seems to fail on the 4232 on I11. So we just break
* rather than fail.
*/
break ; /* return 0; */
}
/*
* Try to switch the chip to mode2 (CS4231) by setting the MODE2 bit
* (0x40). The bit 0x80 is always 1 in CS4248 and CS4231.
*
* On the OPTi931, however, I12 is readonly and only contains the
* chip revision ID (as in the CS4231A). The upper bits return 0.
*/
ad_write(d, 12, 0x40); /* Set mode2, clear 0x80 */
tmp1 = ad_read(d, 12);
if (tmp1 & 0x80) {
name = "CS4248" ; /* Our best knowledge just now */
}
if ((tmp1 & 0xf0) == 0x00) {
BVDDB(printf("this should be an OPTi931\n");)
} else if ((tmp1 & 0xc0) == 0xC0) {
/*
* The 4231 has bit7=1 always, and bit6 we just set to 1.
* We want to check that this is really a CS4231
* Verify that setting I0 doesn't change I16.
*/
ad_write(d, 16, 0); /* Set I16 to known value */
ad_write(d, 0, 0x45);
if ((tmp1 = ad_read(d, 16)) != 0x45) { /* No change -> CS4231? */
ad_write(d, 0, 0xaa);
if ((tmp1 = ad_read(d, 16)) == 0xaa) { /* Rotten bits? */
BVDDB(printf("mss_detect error - step H(%x)\n", tmp1));
return 0;
}
/*
* Verify that some bits of I25 are read only.
*/
tmp1 = ad_read(d, 25); /* Original bits */
ad_write(d, 25, ~tmp1); /* Invert all bits */
if ((ad_read(d, 25) & 0xe7) == (tmp1 & 0xe7)) {
int id;
/*
* It's at least CS4231
*/
name = "CS4231" ;
d->bd_id = MD_CS4231;
/*
* It could be an AD1845 or CS4231A as well.
* CS4231 and AD1845 report the same revision info in I25
* while the CS4231A reports different.
*/
id = ad_read(d, 25) & 0xe7;
/*
* b7-b5 = version number;
* 100 : all CS4231
* 101 : CS4231A
*
* b2-b0 = chip id;
*/
switch (id) {
case 0xa0:
name = "CS4231A" ;
d->bd_id = MD_CS4231A;
break;
case 0xa2:
name = "CS4232" ;
d->bd_id = MD_CS4232;
break;
case 0xb2:
/* strange: the 4231 data sheet says b4-b3 are XX
* so this should be the same as 0xa2
*/
name = "CS4232A" ;
d->bd_id = MD_CS4232A;
break;
case 0x80:
/*
* It must be a CS4231 or AD1845. The register I23
* of CS4231 is undefined and it appears to be read
* only. AD1845 uses I23 for setting sample rate.
* Assume the chip is AD1845 if I23 is changeable.
*/
tmp = ad_read(d, 23);
ad_write(d, 23, ~tmp);
if (ad_read(d, 23) != tmp) { /* AD1845 ? */
name = "AD1845" ;
d->bd_id = MD_AD1845;
}
ad_write(d, 23, tmp); /* Restore */
DDB(printf("... try to identify the yamaha\n") ;)
tmp = inb(0x370) ;
outb(0x370, 6 /* dma config */ ) ;
if (inb(0x370) != 6 ) /* not a yamaha... restore. */
outb(0x370, tmp ) ;
else
d->bd_id = MD_YM0020 ;
break;
case 0x83: /* CS4236 */
case 0x03: /* CS4236 on Intel PR440FX motherboard XXX */
name = "CS4236";
d->bd_id = MD_CS4236;
break ;
default: /* Assume CS4231 */
BVDDB(printf("unknown id 0x%02x, assuming CS4231\n", id);)
d->bd_id = MD_CS4231;
}
}
ad_write(d, 25, tmp1); /* Restore bits */
}
}
BVDDB(printf("mss_detect() - Detected %s\n", name));
snprintf(d->name, sizeof(d->name), "%s", name);
dev->id_flags &= ~DV_F_DEV_MASK ;
dev->id_flags |= (d->bd_id << DV_F_DEV_SHIFT) & DV_F_DEV_MASK ;
return 1;
}
/*
* mss_reinit resets registers of the codec
*/
static void
mss_reinit(snddev_info *d)
{
u_char r;
r = mss_speed(d) ;
r |= (mss_format(d) << 5) ;
if (d->flags & SND_F_STEREO)
r |= 0x10 ;
/* XXX check if MCE is necessary... */
ad_enter_MCE(d);
/*
* perhaps this is not the place to set mode2, should be done
* only once at attach time...
*/
if ( FULL_DUPLEX(d) && d->bd_id != MD_OPTI931)
/*
* set mode2 bit for dual dma op. This bit is not implemented
* on the OPTi931
*/
ad_write(d, 12, ad_read(d, 12) | 0x40 /* mode 2 on the CS42xx */ );
/*
* XXX this should really go into mss-speed...
*/
if (d->bd_id == MD_AD1845) { /* Use alternate speed select regs */
r &= 0xf0; /* Mask off the rate select bits */
ad_write(d, 22, (d->play_speed >> 8) & 0xff); /* Speed MSB */
ad_write(d, 23, d->play_speed & 0xff); /* Speed LSB */
/*
* XXX must also do something in I27 for the ad1845
*/
}
ad_write(d, 8, r) ;
if ( FULL_DUPLEX(d) ) {
#if 0
if (d->bd_id == MD_GUSPNP && d->play_fmt == AFMT_MU_LAW) {
printf("warning, cannot do ulaw rec + play on the GUS\n");
r = 0 ; /* move to U8 */
}
#endif
ad_write(d, 28, r & 0xf0 ) ; /* capture mode */
ad_write(d, 9, 0 /* no capture, no playback, dual dma */) ;
} else
ad_write(d, 9, 4 /* no capture, no playback, single dma */) ;
ad_leave_MCE(d);
/*
* not sure if this is really needed...
*/
ad_write_cnt(d, 14, 0 ); /* playback count */
if ( FULL_DUPLEX(d) )
ad_write_cnt(d, 30, 0 ); /* rec. count on dual dma */
ad_write(d, 10, 2 /* int enable */) ;
outb(io_Status(d), 0); /* Clear interrupt status */
/* the following seem required on the CS4232 */
ad_write(d, 6, ad_read(d,6) & ~I6_MUTE);
ad_write(d, 7, ad_read(d,7) & ~I6_MUTE);
snd_set_blocksize(d); /* update blocksize if user did not force it */
}
/*
* here we have support for PnP cards
*
*/
#if NPNP > 0
static char * cs423x_probe(u_long csn, u_long vend_id);
static void
cs423x_attach(u_long csn, u_long vend_id, char *name,
struct isa_device *dev);
static struct pnp_device cs423x = {
"CS423x/Yamaha/AD1816",
cs423x_probe,
cs423x_attach,
&nsnd, /* use this for all sound cards */
&tty_imask /* imask */
};
DATA_SET (pnpdevice_set, cs423x);
static char *
cs423x_probe(u_long csn, u_long vend_id)
{
char *s = NULL ;
u_long id = vend_id & 0xff00ffff;
if ( id == 0x3700630e )
s = "CS4237" ;
else if ( id == 0x2500630e )
s = "CS4235" ;
else if ( id == 0x3600630e )
s = "CS4236" ;
else if ( id == 0x3500630e )
s = "CS4236B" ;
else if ( id == 0x3200630e)
s = "CS4232" ;
else if ( id == 0x2000a865)
s = "Yamaha SA2";
else if ( id == 0x3000a865)
s = "Yamaha SA3";
else if ( id == 0x0000a865)
s = "Yamaha YMF719 OPL-SA3";
else if (vend_id == 0x8140d315)
s = "SoundscapeVIVO";
else if (vend_id == 0x1114b250)
s = "Terratec Soundsystem BASE 1";
else if (vend_id == 0x50719304)
s = "Generic AD1815";
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 ;
}
extern snddev_info sb_op_desc;
static void
cs423x_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 ldn = 0 ;
if (read_pnp_parms ( &d , ldn ) == 0 ) {
printf("failed to read pnp parms\n");
return ;
}
snddev_last_probed = &tmp_d;
/* AD1816 */
if (vend_id == 0x1114b250 || vend_id == 0x50719304) {
dev->id_alive = 16; /* number of io ports ? */
tmp_d = mss_op_desc; /* copy it */
tmp_d.ioctl = ad1816_ioctl;
tmp_d.isr = ad1816_intr;
tmp_d.callback = ad1816_callback;
tmp_d.audio_fmt = AFMT_STEREO | AFMT_U8 |
AFMT_A_LAW | AFMT_MU_LAW |
AFMT_S16_LE | AFMT_S16_BE;
dev->id_iobase = d.port[2];
tmp_d.alt_base = d.port[0]; /* soundblaster comp. but we don't
* use that */
tmp_d.bd_id = MD_AD1816;
strcpy(tmp_d.name, name);
} else if (d.flags & DV_PNP_SBCODEC) { /* use sb-compatible codec */
dev->id_alive = 16 ; /* number of io ports ? */
tmp_d = sb_op_desc ;
if (vend_id==0x2000a865 || vend_id==0x3000a865 ||
vend_id==0x0008a865 || vend_id==0x8140d315) {
/* Yamaha SA2/SA3 or ENSONIQ SoundscapeVIVO ENS4081 */
dev->id_iobase = d.port[0] ;
tmp_d.alt_base = d.port[1] ;
d.irq[1] = 0 ; /* only needed for the VIVO */
} else {
dev->id_iobase = d.port[2] ;
tmp_d.alt_base = d.port[0] - 4;
}
d.drq[1] = 4 ; /* disable, it is not used ... */
} else { /* mss-compatible codec */
dev->id_alive = 8 ; /* number of io ports ? */
tmp_d = mss_op_desc ;
dev->id_iobase = d.port[0] -4 ; /* XXX old mss have 4 bytes before... */
tmp_d.alt_base = d.port[2];
switch (vend_id & 0xff00ffff) {
case 0x2000a865: /* Yamaha SA2 */
case 0x3000a865: /* Yamaha SA3 */
case 0x0000a865: /* Yamaha TMF719 SA3 */
dev->id_iobase = d.port[1];
tmp_d.alt_base = d.port[0];
tmp_d.conf_base = d.port[4];
tmp_d.bd_id = MD_YM0020 ;
break;
case 0x8100d315: /* ENSONIQ SoundscapeVIVO */
dev->id_iobase = d.port[1];
tmp_d.alt_base = d.port[0];
tmp_d.bd_id = MD_VIVO ;
d.irq[1] = 0 ;
break;
case 0x3700630e: /* CS4237 */
tmp_d.bd_id = MD_CS4237 ;
break;
case 0x2500630e: /* AOpen AW37, CS4235 */
tmp_d.bd_id = MD_CS4237 ;
break ;
case 0x3500630e: /* CS4236B */
case 0x3600630e: /* CS4236 */
tmp_d.bd_id = MD_CS4236 ;
break;
default:
tmp_d.bd_id = MD_CS4232; /* to short-circuit the
* detect routine */
break;
}
snprintf(tmp_d.name, sizeof(tmp_d.name), "%s", name);
tmp_d.audio_fmt |= AFMT_FULLDUPLEX ;
}
write_pnp_parms( &d, ldn );
enable_pnp_card();
if ( (vend_id & 0x0000ffff) == 0x0000a865 ) {
/* special volume setting for the Yamaha... */
outb(tmp_d.conf_base, 7 /* volume, left */);
outb(tmp_d.conf_base+1, 0 );
outb(tmp_d.conf_base, 8 /* volume, right */);
outb(tmp_d.conf_base+1, 0 );
}
dev->id_drq = d.drq[0] ; /* primary dma */
dev->id_irq = (1 << d.irq[0] ) ;
dev->id_ointr = pcmintr ;
dev->id_flags = DV_F_DUAL_DMA | (d.drq[1] ) ;
tmp_d.synth_base = d.port[1]; /* XXX check this for yamaha */
pcmattach(dev);
}
static char *opti931_probe(u_long csn, u_long vend_id);
static void opti931_attach(u_long csn, u_long vend_id, char *name,
struct isa_device *dev);
static struct pnp_device opti931 = {
"OPTi931",
opti931_probe,
opti931_attach,
&nsnd, /* use this for all sound cards */
&tty_imask /* imask */
};
DATA_SET (pnpdevice_set, opti931);
static char *
opti931_probe(u_long csn, u_long vend_id)
{
if (vend_id == 0x3109143e) {
struct pnp_cinfo d;
read_pnp_parms(&d, 1);
if (d.enable == 0) {
printf("This is an OPTi931, but LDN 1 is disabled\n");
return NULL ;
}
return "OPTi931" ;
}
return NULL ;
}
static void
opti931_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 p;
read_pnp_parms ( &d , 3 ); /* free resources taken by LDN 3 */
d.irq[0]=0; /* free irq... */
d.port[0]=0; /* free address... */
d.enable = 0 ;
write_pnp_parms ( &d , 3 );
read_pnp_parms ( &d , 2 ); /* disable LDN 2 */
d.enable = 0 ;
write_pnp_parms ( &d , 2 );
read_pnp_parms ( &d , 1 ) ;
write_pnp_parms( &d, 1 );
enable_pnp_card();
snddev_last_probed = &tmp_d;
tmp_d = d.flags & DV_PNP_SBCODEC ? sb_op_desc : mss_op_desc ;
snprintf(tmp_d.name, sizeof(tmp_d.name), "%s", name);
/*
* My MED3931 v.1.0 allocates 3 bytes for the config space,
* whereas v.2.0 allocates 4 bytes. What I know for sure is that the
* upper two ports must be used, and they should end on a boundary
* of 4 bytes. So I need the following trick...
*/
p = tmp_d.conf_base = (d.port[3] & ~3) + 2; /* config port */
/*
* now set default values for both modes.
*/
dev->id_iobase = d.port[0] - 4 ; /* old mss have 4 bytes before... */
tmp_d.io_base = dev->id_iobase; /* needed for ad_write to work... */
tmp_d.alt_base = d.port[2];
tmp_d.synth_base = d.port[1];
opti_write(p, 4, 0xd6 /* fifo empty, OPL3, audio enable, SB3.2 */ );
ad_write (&tmp_d, 10, 2); /* enable interrupts */
if (d.flags & DV_PNP_SBCODEC) { /* sb-compatible codec */
/*
* the 931 is not a real SB, it has important pieces of
* hardware controlled by both the MSS and the SB port...
*/
printf("--- opti931 in sb mode ---\n");
opti_write(p, 6, 1); /* MCIR6 mss disable, sb enable */
/*
* swap the main and alternate iobase address since we want
* to work in sb mode.
*/
dev->id_iobase = d.port[2] ;
tmp_d.alt_base = d.port[0] - 4;
dev->id_flags = DV_F_DUAL_DMA | d.drq[1] ;
} else { /* mss-compatible codec */
tmp_d.bd_id = MD_OPTI931 ; /* to short-circuit the detect routine */
opti_write(p, 6 , 2); /* MCIR6: mss enable, sb disable */
opti_write(p, 5, 0x28); /* MCIR5: codec in exp. mode,fifo */
dev->id_flags = DV_F_DUAL_DMA | d.drq[1] ;
tmp_d.audio_fmt |= AFMT_FULLDUPLEX ; /* not really well... */
tmp_d.isr = opti931_intr;
}
dev->id_drq = d.drq[0] ; /* primary dma */
dev->id_irq = (1 << d.irq[0] ) ;
dev->id_ointr = pcmintr ;
pcmattach(dev);
}
static char *opti925_probe(u_long csn, u_long vend_id);
static void opti925_attach(u_long csn, u_long vend_id, char *name,
struct isa_device *dev);
static struct pnp_device opti925 = {
"opti925",
opti925_probe,
opti925_attach,
&nsnd, /* use this for all sound cards */
&tty_imask /* imask */
};
DATA_SET (pnpdevice_set, opti925);
static char *
opti925_probe(u_long csn, u_long vend_id)
{
if (vend_id == 0x2509143e) {
struct pnp_cinfo d ;
read_pnp_parms ( &d , 1 ) ;
if (d.enable == 0) {
printf("This is an OPTi925, but LDN 1 is disabled\n");
return NULL;
}
return "OPTi925" ;
}
return NULL ;
}
static void
opti925_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 = mss_op_desc;
snddev_last_probed = &tmp_d;
read_pnp_parms ( &d , 3 ); /* disable LDN 3 */
the_irq = d.irq[0];
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 , 1 ) ;
d.irq[0] = the_irq ;
dev->id_iobase = d.port[1];
tmp_d.alt_base = d.port[0];
write_pnp_parms ( &d , 1 );
enable_pnp_card();
tmp_d.conf_base = d.port[3];
dev->id_drq = d.drq[0] ; /* primary dma */
dev->id_irq = (1 << d.irq[0] ) ;
dev->id_ointr = pcmintr ;
dev->id_flags = DV_F_DUAL_DMA | d.drq[1] ;
tmp_d.audio_fmt |= AFMT_FULLDUPLEX ;
snddev_last_probed->probe(dev); /* not really necessary but doesn't harm */
pcmattach(dev);
}
#if 0
static void gus_mem_cfg(snddev_info *tmp);
#endif
static char *guspnp_probe(u_long csn, u_long vend_id);
static void guspnp_attach(u_long csn, u_long vend_id, char *name,
struct isa_device *dev);
static struct pnp_device guspnp = {
"GusPnP",
guspnp_probe,
guspnp_attach,
&nsnd, /* use this for all sound cards */
&tty_imask /* imask */
};
DATA_SET (pnpdevice_set, guspnp);
static char *
guspnp_probe(u_long csn, u_long vend_id)
{
if (vend_id == 0x0100561e) {
struct pnp_cinfo d;
read_pnp_parms(&d, 0);
if (d.enable == 0) {
printf("This is a GusPnP, but LDN 0 is disabled\n");
return NULL ;
}
return "GusPnP" ;
}
return NULL ;
}
static void
guspnp_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 */
u_char tmp;
read_pnp_parms ( &d , 0 ) ;
/* d.irq[1] = d.irq[0] ; */
pnp_write ( 0xf2, 0xff ); /* enable power on the guspnp */
write_pnp_parms ( &d , 0 );
enable_pnp_card();
tmp_d = mss_op_desc ;
snddev_last_probed = &tmp_d;
dev->id_iobase = d.port[2] - 4 ; /* room for 4 mss registers */
dev->id_drq = d.drq[1] ; /* XXX PLAY dma */
dev->id_irq = (1 << d.irq[0] ) ;
dev->id_ointr = pcmintr ;
dev->id_flags = DV_F_DUAL_DMA | d.drq[0] ; /* REC dma */
tmp_d.io_base = d.port[2] - 4;
tmp_d.alt_base = d.port[0]; /* 0x220 */
tmp_d.conf_base = d.port[1]; /* gus control block... */
tmp_d.bd_id = MD_GUSPNP ;
/* reset */
gus_write(tmp_d.conf_base, 0x4c /* _URSTI */, 0 );/* Pull reset */
DELAY(1000 * 30);
/* release reset and enable DAC */
gus_write(tmp_d.conf_base, 0x4c /* _URSTI */, 3 );
DELAY(1000 * 30);
/* end of reset */
outb( tmp_d.alt_base, 0xC ); /* enable int and dma */
/*
* unmute left & right line. Need to go in mode3, unmute,
* and back to mode 2
*/
tmp = ad_read(&tmp_d, 0x0c);
ad_write(&tmp_d, 0x0c, 0x6c ); /* special value to enter mode 3 */
ad_write(&tmp_d, 0x19, 0 ); /* unmute left */
ad_write(&tmp_d, 0x1b, 0 ); /* unmute right */
ad_write(&tmp_d, 0x0c, tmp ); /* restore old mode */
/* send codec interrupts on irq1 and only use that one */
gus_write(tmp_d.conf_base, 0x5a , 0x4f );
/* enable access to hidden regs */
tmp = gus_read(tmp_d.conf_base, 0x5b /* IVERI */ );
gus_write(tmp_d.conf_base, 0x5b , tmp | 1 );
BVDDB(printf("GUS: silicon rev %c\n", 'A' + ( ( tmp & 0xf ) >> 4) );)
snprintf(tmp_d.name, sizeof(tmp_d.name), "%s", name);
pcmattach(dev);
}
#if 0
int
gus_mem_write(snddev_info *d, int addr, u_char data)
{
gus_writew(d->conf_base, 0x43 , addr & 0xffff );
gus_write(d->conf_base, 0x44 , (addr>>16) & 0xff );
outb(d->conf_base + 7, data);
}
u_char
gus_mem_read(snddev_info *d, int addr)
{
gus_writew(d->conf_base, 0x43 , addr & 0xffff );
gus_write(d->conf_base, 0x44 , (addr>>16) & 0xff );
return inb(d->conf_base + 7);
}
void
gus_mem_cfg(snddev_info *d)
{
int base;
u_char old;
u_char a, b;
printf("configuring gus memory...\n");
gus_writew(d->conf_base, 0x52 /* LMCFI */, 1 /* 512K*/);
old = gus_read(d->conf_base, 0x19);
gus_write(d->conf_base, 0x19, old | 1); /* enable enhaced mode */
for (base = 0; base < 1024; base++) {
a=gus_mem_read(d, base*1024);
a = ~a ;
gus_mem_write(d, base*1024, a);
b=gus_mem_read(d, base*1024);
if ( b != a )
break ;
}
printf("Have found %d KB ( 0x%x != 0x%x)\n", base, a, b);
}
#endif /* gus mem cfg... */
static int
ad1816_ioctl(dev_t dev, u_long cmd, caddr_t arg, int mode, struct proc * p)
{
snddev_info *d;
int unit;
dev = minor(dev);
unit = dev >> 4;
d = &pcm_info[unit];
if ((cmd & MIXER_WRITE(0)) == MIXER_WRITE(0)) {
cmd &= 0xff;
if (cmd == SOUND_MIXER_RECSRC)
return ad1816_set_recsrc(d, *(int *) arg);
else
return ad1816_mixer_set(d, cmd, *(int *) arg);
}
switch (cmd) { /* driver specific ioctls other than mixer
* calls */
/* ad1816 has special features */
case AIOGCAP: /* get capabilities */
{
snd_capabilities *p = (snd_capabilities *) arg;
p->rate_min = 4000;
p->rate_max = 55200;
p->bufsize = d->bufsize;
p->formats = d->audio_fmt;
p->mixers = 1;
p->inputs = d->mix_devs;
p->left = p->right = 100;
return 0;
}
default:
{
return ENOSYS; /* fallback to default */
}
break;
}
}
static int
ad1816_callback(snddev_info * d, int reason)
{
int wr, cnt;
wr = reason & SND_CB_WR;
reason &= SND_CB_REASON_MASK;
switch (reason) {
case SND_CB_INIT:
ad1816_reinit(d);
reset_dbuf(&(d->dbuf_in), SND_CHAN_RD);
reset_dbuf(&(d->dbuf_out), SND_CHAN_WR);
return 1;
break;
case SND_CB_START:
cnt = wr ? d->dbuf_out.dl : d->dbuf_in.dl;
cnt /= 4;
cnt--;
/* start only if not already running */
if (wr && !(inb(ad1816_play(d)) & AD1816_ENABLE)) {
/* set dma counter */
ad1816_write(d, 8, cnt); /* playback count */
/* int enable */
ad1816_write(d, 1, ad1816_read(d, 1) | 0x8000);
/* enable playback */
outb(ad1816_play(d), (inb(ad1816_play(d)) | AD1816_ENABLE));
/* check if we succeeded */
if (!(inb(ad1816_play(d)) & AD1816_ENABLE)) {
printf("ad1816: failed to start write (playback) DMA !\n");
}
} else if (!wr && !(inb(ad1816_capt(d)) & AD1816_ENABLE)) {
/* same for capture */
ad1816_write(d, 10, cnt); /* capture count */
ad1816_write(d, 1, ad1816_read(d, 1) | 0x4000); /* int */
outb(ad1816_capt(d), (inb(ad1816_capt(d)) | AD1816_ENABLE)); /* CEN */
if (!(inb(ad1816_capt(d)) & AD1816_ENABLE)) { /* check */
printf("ad1816: failed to start read (capture) DMA !\n");
}
}
break;
case SND_CB_STOP:
case SND_CB_ABORT: /* XXX check this... */
/* we don't test here if it is running... */
if (wr) {
ad1816_write(d, 1, ad1816_read(d, 1) & ~0x8000);
/* disable int */
outb(ad1816_play(d), (inb(ad1816_play(d)) & ~AD1816_ENABLE));
/* disable playback */
if ((inb(ad1816_play(d)) & AD1816_ENABLE)) {
printf("ad1816: failed to stop write (playback) DMA !\n");
}
ad1816_write(d, 8, 0); /* reset base counter */
ad1816_write(d, 9, 0); /* reset cur counter */
} else {
/* same for capture */
ad1816_write(d, 1, ad1816_read(d, 1) & ~0x4000);
outb(ad1816_capt(d), (inb(ad1816_capt(d)) & ~AD1816_ENABLE));
if ((inb(ad1816_capt(d)) & AD1816_ENABLE)) {
printf("ad1816: failed to stop read (capture) DMA !\n");
}
ad1816_write(d, 10, 0);
ad1816_write(d, 11, 0);
}
break;
}
return 0;
}
static void
ad1816_intr(int unit)
{
snddev_info *d = &pcm_info[unit];
unsigned char c, served = 0;
/* get interupt status */
c = inb(ad1816_int(d));
/* check for stray interupts */
if (c & ~(AD1816_INTRCI | AD1816_INTRPI)) {
printf("ad1816: Stray interrupt 0x%x.\n", c);
c = c & (AD1816_INTRCI | AD1816_INTRPI);
outb(ad1816_int(d), c); /* ack it anyway */
}
/* check for capture interupt */
if (d->dbuf_in.dl && (c & AD1816_INTRCI)) {
outb(ad1816_int(d), c & ~AD1816_INTRCI); /* ack it */
if (inb(ad1816_int(d)) & AD1816_INTRCI)
printf("ad1816: Failed to clear cp int !!!\n");
dsp_rdintr(d);
served |= AD1816_INTRCI; /* cp served */
}
/* check for playback interupt */
if (d->dbuf_out.dl && (c & AD1816_INTRPI)) {
outb(ad1816_int(d), c & ~AD1816_INTRPI); /* ack it */
if ((inb(ad1816_int(d)) & AD1816_INTRPI) != 0)
printf("ad1816: Failed to clear pb int !!!\n");
dsp_wrintr(d);
served |= AD1816_INTRPI; /* pb served */
}
if (served == 0) {
/* this probably means this is not a (working) ad1816 chip, */
/* or an error in dma handling */
printf("ad1816: raised an interrupt without reason 0x%x.\n", c);
outb(ad1816_int(d), 0); /* Clear interrupt status anyway */
}
}
static int
ad1816_wait_init(snddev_info * d, int x)
{
int n = 0; /* to shut up the compiler... */
for (; x--;)
if (((n = (inb(ad1816_ale(d)) & AD1816_BUSY))) == 0)
DELAY(10);
else
return n;
printf("ad1816_wait_init failed 0x%02x.\n", inb(ad1816_ale(d)));
return n;
}
static unsigned short
ad1816_read(snddev_info * d, unsigned int reg)
{
int flags;
u_short x;
/* we don't want to be blocked here */
flags = spltty();
if (ad1816_wait_init(d, 100) == 0) {
printf("ad1816_read: chip timeout before read.\n");
return 0;
}
outb(ad1816_ale(d), (u_char) 0);
outb(ad1816_ale(d), (u_char) (reg & AD1816_ALEMASK));
if (ad1816_wait_init(d, 100) == 0) {
printf("ad1816_read: chip timeout during read.\n");
return 0;
}
x = (inb(ad1816_high(d)) << 8) | inb(ad1816_low(d));
splx(flags);
return x;
}
static void
ad1816_write(snddev_info * d, unsigned int reg, unsigned short data)
{
int flags;
flags = spltty();
if (ad1816_wait_init(d, 100) == 0) {
printf("ad1816_write: chip timeout before write.\n");
return;
}
outb(ad1816_ale(d), (u_char) (reg & AD1816_ALEMASK));
outb(ad1816_low(d), (u_char) (data & 0x000000ff));
outb(ad1816_high(d), (u_char) ((data & 0x0000ff00) >> 8));
splx(flags);
}
#if 0 /* unused right now..., and untested... */
static void
ad1816_mute(snddev_info * d)
{
ad1816_write(d, 14, ad1816_read(d, 14) | 0x8000 | 0x80);
}
static void
ad1816_unmute(snddev_info * d)
{
ad1816_write(d, 14, ad1816_read(d, 14) & ~(0x8000 | 0x80));
}
#endif
/* only one rec source is possible */
static int
ad1816_set_recsrc(snddev_info * d, int mask)
{
mask &= d->mix_rec_devs;
switch (mask) {
case SOUND_MASK_LINE:
case SOUND_MASK_LINE3:
ad1816_write(d, 20, (ad1816_read(d, 20) & ~0x7070) | 0x0000);
break;
case SOUND_MASK_CD:
case SOUND_MASK_LINE1:
ad1816_write(d, 20, (ad1816_read(d, 20) & ~0x7070) | 0x2020);
break;
case SOUND_MASK_MIC:
default:
ad1816_write(d, 20, (ad1816_read(d, 20) & ~0x7070) | 0x5050);
}
d->mix_recsrc = mask;
return 0; /* success */
}
#define AD1816_MUTE 31 /* value for mute */
static int
ad1816_mixer_set(snddev_info * d, int dev, int value)
{
u_char left = (value & 0x000000ff);
u_char right = (value & 0x0000ff00) >> 8;
u_short reg = 0;
if (dev > 31)
return EINVAL;
if (!(d->mix_devs & (1 << dev)))
return EINVAL;
if (left > 100)
left = 100;
if (right > 100)
right = 100;
d->mix_levels[dev] = left | (right << 8);
/* Scale volumes */
left = AD1816_MUTE - (AD1816_MUTE * left) / 100;
right = AD1816_MUTE - (AD1816_MUTE * right) / 100;
reg = (left << 8) | right;
/* do channel selective muting if volume is zero */
if (left == AD1816_MUTE)
reg |= 0x8000;
if (right == AD1816_MUTE)
reg |= 0x0080;
switch (dev) {
case SOUND_MIXER_VOLUME: /* Register 14 master volume */
ad1816_write(d, 14, reg);
break;
case SOUND_MIXER_CD: /* Register 15 cd */
case SOUND_MIXER_LINE1:
ad1816_write(d, 15, reg);
break;
case SOUND_MIXER_SYNTH: /* Register 16 synth */
ad1816_write(d, 16, reg);
break;
case SOUND_MIXER_PCM: /* Register 4 pcm */
ad1816_write(d, 4, reg);
break;
case SOUND_MIXER_LINE:
case SOUND_MIXER_LINE3: /* Register 18 line in */
ad1816_write(d, 18, reg);
break;
case SOUND_MIXER_MIC: /* Register 19 mic volume */
ad1816_write(d, 19, reg & ~0xff); /* mic is mono */
break;
case SOUND_MIXER_IGAIN:
/* and now to something completely different ... */
ad1816_write(d, 20, ((ad1816_read(d, 20) & ~0x0f0f)
| (((AD1816_MUTE - left) / 2) << 8) /* four bits of adc gain */
| ((AD1816_MUTE - right) / 2)));
break;
default:
printf("ad1816_mixer_set(): unknown device.\n");
break;
}
return 0; /* success */
}
static void
ad1816_mixer_reset(snddev_info * d)
{
int i;
d->mix_devs = AD1816_MIXER_DEVICES;
d->mix_rec_devs = AD1816_REC_DEVICES;
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
if (d->mix_devs & (1 << i))
ad1816_mixer_set(d, i, default_mixer_levels[i]);
ad1816_set_recsrc(d, SOUND_MASK_MIC);
}
/* Set the playback and capture rates. */
static int
ad1816_speed(snddev_info * d)
{
RANGE(d->play_speed,4000,55200);
RANGE(d->rec_speed,4000,55200);
ad1816_write(d, 2, d->play_speed);
ad1816_write(d, 3, d->rec_speed);
return d->play_speed;
}
/*
* ad1816_format checks that the format is supported (or defaults to AFMT_U8)
* and sets the chip to the desired format.
*/
static int
ad1816_format(snddev_info * d)
{
int oldplay =inb(ad1816_play(d)) & ~AD1816_FORMASK;
int oldrec = inb(ad1816_capt(d)) & ~AD1816_FORMASK;
int play = (d->play_fmt & d->audio_fmt) ? d->play_fmt : AFMT_U8;
int rec = (d->rec_fmt & d->audio_fmt) ? d->rec_fmt : AFMT_U8;
/*
* check that arg is one of the supported formats in d->format; otherwise
* fallback to AFMT_U8
*/
switch (play) {
case AFMT_A_LAW:
outb(ad1816_play(d), oldplay | AD1816_ALAW);
break;
case AFMT_MU_LAW:
outb(ad1816_play(d), oldplay | AD1816_MULAW);
break;
case AFMT_S16_LE:
outb(ad1816_play(d), oldplay | AD1816_S16LE);
break;
case AFMT_S16_BE:
outb(ad1816_play(d), oldplay | AD1816_S16BE);
break;
default:
/* unlikely to happen */
printf("ad1816: unknown play format. defaulting to U8.\n");
case AFMT_U8:
outb(ad1816_play(d), oldplay | AD1816_U8);
break;
}
switch (rec) {
case AFMT_A_LAW:
outb(ad1816_capt(d), oldrec | AD1816_ALAW);
break;
case AFMT_MU_LAW:
outb(ad1816_capt(d), oldrec | AD1816_MULAW);
break;
case AFMT_S16_LE:
outb(ad1816_capt(d), oldrec | AD1816_S16LE);
break;
case AFMT_S16_BE:
outb(ad1816_capt(d), oldrec | AD1816_S16BE);
break;
default:
printf("ad1816: unknown capture format. defaulting to U8.\n");
case AFMT_U8:
outb(ad1816_capt(d), oldrec | AD1816_U8);
break;
}
d->play_fmt = play;
d->rec_fmt = rec;
return (play);
}
/*
* ad1816_reinit resets codec registers
*/
static void
ad1816_reinit(snddev_info * d)
{
ad1816_write(d, 8, 0x0000); /* reset base and current counter */
ad1816_write(d, 9, 0x0000); /* for playback and capture */
ad1816_write(d, 10, 0x0000);
ad1816_write(d, 11, 0x0000);
if (d->flags & SND_F_STEREO) {
outb((ad1816_play(d)), AD1816_STEREO); /* set playback to stereo */
outb((ad1816_capt(d)), AD1816_STEREO); /* set capture to stereo */
} else {
outb((ad1816_play(d)), 0x00); /* set playback to mono */
outb((ad1816_capt(d)), 0x00); /* set capture to mono */
}
ad1816_format(d);
ad1816_speed(d);
snd_set_blocksize(d); /* update blocksize if user did not force it */
}
#endif /* NPNP > 0 */
#endif /* NPCM > 0 */