diff options
author | jmg <jmg@FreeBSD.org> | 1997-09-14 21:42:12 +0000 |
---|---|---|
committer | jmg <jmg@FreeBSD.org> | 1997-09-14 21:42:12 +0000 |
commit | 2c60690db528079dba2c7480eab56c06777b3a76 (patch) | |
tree | 992221a086383e8ad8443d0eda5af0672f934e11 | |
parent | cbd8eade7a84003667e3004811248ce627128046 (diff) | |
download | FreeBSD-src-2c60690db528079dba2c7480eab56c06777b3a76.zip FreeBSD-src-2c60690db528079dba2c7480eab56c06777b3a76.tar.gz |
Import of Luigi Rizzo's sound code. For more information about the driver
check out the README that is included.
Submitted by: Luigi Rizzo <luigi@labinfo.iet.unipi.it>
-rw-r--r-- | sys/dev/pcm/isa/mss.c | 1509 | ||||
-rw-r--r-- | sys/dev/pcm/isa/mss.h | 250 | ||||
-rw-r--r-- | sys/dev/pcm/isa/sb.c | 1080 | ||||
-rw-r--r-- | sys/dev/pcm/isa/sb.h | 387 | ||||
-rw-r--r-- | sys/dev/sound/isa/mss.c | 1509 | ||||
-rw-r--r-- | sys/dev/sound/isa/mss.h | 250 | ||||
-rw-r--r-- | sys/dev/sound/isa/sb.c | 1080 | ||||
-rw-r--r-- | sys/dev/sound/isa/sb.h | 387 | ||||
-rw-r--r-- | sys/dev/sound/isa/sb16.c | 1080 | ||||
-rw-r--r-- | sys/dev/sound/isa/sb8.c | 1080 |
10 files changed, 8612 insertions, 0 deletions
diff --git a/sys/dev/pcm/isa/mss.c b/sys/dev/pcm/isa/mss.c new file mode 100644 index 0000000..97b328d0 --- /dev/null +++ b/sys/dev/pcm/isa/mss.c @@ -0,0 +1,1509 @@ +/* + * sound/ad1848.c + * + * Modified by Luigi Rizzo (luigi@iet.unipi.it) + * + * Driver for Microsoft Sound System/Windows Sound System (mss) + * -compatible boards. This includes: + * + * AD1848, CS4248 + * + * CS4231, used in the GUS MAX and some other cards; + * AD1845, CS4231A (CS4231-like) + * CS4232 (CS4231+SB and MPU, PnP) + * CS4236 (upgrade of the CS4232, has a better mixer) + * OPTi931 (WSS compatible, full duplex, some differences from CS42xx) + * + * Copyright Luigi Rizzo, 1997 + * 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 + * + * The OPTi931 appears to be quite buggy. + */ + +#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); + +static d_open_t mss_open; +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); + +/* + * 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 + */ +} ; + +/* + * this 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; + + if (dev->id_iobase == -1) { + dev->id_iobase = 0x530; + 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 */ + DDB(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) { + DDB(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 */ +} + +/* + * 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->dma1, d->dma2, dev->id_flags); + + 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 (d->dma1 == d->dma2) /* single chan dma */ + outb(dev->id_iobase, bits | dma_bits[d->dma1]); + else { + if (d->dma1 == 0 && d->dma2 == 1) + bits |= 5 ; + else if (d->dma1 == 1 && d->dma2 == 0) + bits |= 6 ; + else if (d->dma1 == 3 && d->dma2 == 0) + bits |= 7 ; + else { + printf("invalid dual dma config %d:%d\n", + d->dma1, d->dma2); + dev->id_irq = 0 ; + return 0 ; + } + outb(dev->id_iobase, bits ); + } + } + mss_reinit(d); + ad1848_mixer_reset(d); + return 0; +} + +static 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) ) { + /* + * device was idle. Do the necessary initialization, + * but no need keep interrupts blocked since this device + * 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->esel.si_pid = 0; + d->esel.si_flags = 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; + } + reset_dbuf(& (d->dbuf_in) ); + reset_dbuf(& (d->dbuf_out) ); + ask_init(d); + } + splx(s); + 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) ) { /* last one ... */ + d->flags |= SND_F_CLOSING ; + splx(s); /* is this ok here ? */ + snd_flush(d); + outb(io_Status(d), 0); /* Clear interrupt status */ + d->flags = 0 ; + } + splx(s); + return 0 ; +} + +static int +mss_ioctl(dev_t dev, int 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. + */ + +static int +mss_callback(snddev_info *d, int reason) +{ + u_long s; + 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 WSS */ + snd_set_blocksize(d); + mss_reinit(d); + return 1 ; + break ; + + case SND_CB_START : + /* fallthrough */ + case SND_CB_RESTART : + s = spltty(); + cnt = wr ? d->dbuf_out.dl0 : d->dbuf_in.dl0 ; + 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) ; + DDB( 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 || (d->dma1 == d->dma2) ) + ad_write_cnt(d, 14, cnt); + else + ad_write_cnt(d, 30, cnt); + + splx(s); + break ; + case SND_CB_STOP : + case SND_CB_ABORT : /* XXX check this... */ + s = spltty(); + 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) ) ; + + /* disable DMA by clearing count registers. */ + if (wr || (d->dma1 == d->dma2) ) + ad_write_cnt(d, 14, 0); + else + ad_write_cnt(d, 30, 0); + splx(s); + break; + } + return 0 ; +} + +/* + * main irq handler for the CS423x. The OPTi931 code is + * a separate one. + */ + +static void +mss_intr(int unit) +{ + snddev_info *d = &pcm_info[unit]; + u_char i11, c; + u_char reason; /* b0 = playback, b1 = capture, b2 = timer */ + + i11 = ad_read(d, 11); + reason = inb(io_Status(d)); + + if ( ! (reason & 1) ) /* no int, maybe a shared line ? */ + return; + /* get exact reason */ + c = (d->dma1 == d->dma2) ? 0x10 : ad_read(d, 24); + if ( (d->flags & SND_F_WR_DMA) && (c & 0x10) ) + dsp_wrintr(d); + c = (d->dma1 == d->dma2) ? 0x20 : ad_read(d, 24); + if ( (d->flags & SND_F_RD_DMA) && (c & 0x20) ) + dsp_rdintr(d); + /* XXX check this on the 4236... */ + if (d->dma1 == d->dma2) + outb(io_Status(d), 0); /* Clear interrupt status */ + else + ad_write(d, 24, ~c); /* ack selectively */ +} + +/* + * the opti931 seems to miss interrupts when working in full + * duplex. god know why... + */ +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; + int w_miss=0, r_miss=0; + static int misses=0; /* XXX kludge */ + +#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 = (d->dma1 == d->dma2) ? 0xc : opti_read(d->conf_base, 11); + mc11 &= 0x0c ; + if (c & 0x10) { + printf("Warning CD interrupt\n"); + mc11 |= 0x10 ; + } + if (c & 0x20) { + 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 ; + if ( mc11 == 0 ) { /* perhaps can return ... */ + reason = inb(io_Status(d)); + if (reason & 1) { + printf("one more try...\n"); + goto again; + } + if (w_miss || r_miss ) { + misses++; + if ( (misses & 0xff) == 1 ) + printf("opti931: %d missed irq (now %s%s)\n", + misses, w_miss?"play ":"", + r_miss?"capture ":""); + } + if (loops==10) + printf("ouch, intr but nothing in mcir11 0x%02x\n", mc11); + if (w_miss) mc11 |= 4 ; + if (r_miss) mc11 |= 8 ; + if (mc11==0) + return; + } + + if ( (d->flags & SND_F_RD_DMA) && (mc11 & 8) ) { + dsp_rdintr(d); + r_miss = 0 ; + } + else if (isa_dmastatus1(d->dma2) == 0 && (d->flags & SND_F_RD_DMA) ) + r_miss = 1 ; + + if ( (d->flags & SND_F_WR_DMA) && (mc11 & 4) ) { + if (isa_dmastatus1(d->dma1) != 0) + printf("opti931_intr: wr dma %d\n", + isa_dmastatus1(d->dma1)); + dsp_wrintr(d); + w_miss = 0 ; + } + else if (isa_dmastatus1(d->dma1) == 0 && (d->flags & SND_F_WR_DMA) ) + w_miss = 1 ; + + opti_write(d->conf_base, 11, ~mc11); /* ack */ + if (--loops) goto again; + 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); +} +/* + * 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 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 0x%02x\n", n); + return n ; +} + +static int +ad_read(snddev_info *d, int reg) +{ + u_long flags; + int x; + + flags = spltty(); + AD_WAIT_INIT(d, 100); + 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, 100); + 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, 100); + prev = inb(io_Index_Addr(d)); + outb(io_Index_Addr(d), prev | IA_MCE | IA_TRD ) ; +} + +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)); + + outb(io_Index_Addr(d), (prev & ~IA_MCE) | IA_TRD); /* Clear the MCE bit */ + wait_for_calibration(d); + splx(flags); +} + +/* + * only one source can be set... + * + * fixed -- lr 970725 + */ +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; +} + +/* + * mixer conversion table: from 0..100 scale to codec values + * + * I don't understand what's this for... maybe achieve a log-scale + * volume control ? + */ + +static char mix_cvt[101] = { + 0, 0, 3, 7,10,13,16,19,21,23,26,28,30,32,34,35,37,39,40,42, + 43,45,46,47,49,50,51,52,53,55,56,57,58,59,60,61,62,63,64,65, + 65,66,67,68,69,70,70,71,72,73,73,74,75,75,76,77,77,78,79,79, + 80,81,81,82,82,83,84,84,85,85,86,86,87,87,88,88,89,89,90,90, + 91,91,92,92,93,93,94,94,95,95,96,96,96,97,97,98,98,98,99,99, + 100 +}; + +/* + * 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) { + DDB(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); + + /* Scale volumes */ + left = mix_cvt[left]; + right = mix_cvt[right]; + + /* + * Set the left channel + */ + + regoffs = (*mix_d)[dev][LEFT_CHN].regno; + old = val = ad_read(d, regoffs); + if (regoffs != 0) + val = old & 0x7f ; /* clear mute bit. */ + change_bits(mix_d, &val, dev, LEFT_CHN, left); + ad_write(d, regoffs, val); + DEB(printf("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; + val = ad_read(d, regoffs); + if (regoffs != 1) + val = old & 0x7f ; /* clear mute bit. */ + change_bits(mix_d, &val, dev, RIGHT_CHN, right); + ad_write(d, regoffs, 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 = MODE1_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); +} + +/* + * 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!) + * + * fixed lr970724 + */ + +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) { + 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 it's 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 */ + DDB(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) { + DDB(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) { + DDB(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)) { + DDB(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 + */ + + 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))) { + DDB(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) { + 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? */ + DDB(printf("mss_detect error - step H(%x)\n", tmp1)); + return 0; + } + /* + * Verify that some bits of I25 are read only. + */ + + DDB(printf("mss_detect() - step I\n")); + 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 */ + break; + + case 0x83: /* CS4236 */ + name = "CS4236"; + d->bd_id = MD_CS4236; + break ; + + default: /* Assume CS4231 */ + printf("unknown id 0x%02x, assuming CS4231\n", id); + d->bd_id = MD_CS4231; + + } + } + ad_write(d, 25, tmp1); /* Restore bits */ + + } + } + DDB(printf("mss_detect() - Detected %s\n", name)); + strcpy(d->name, 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 ( d->dma1 != d->dma2 && 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 (d->dma1 != d->dma2) { + 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); + ad_write_cnt(d, 14, 0 ); /* playback count */ + if (d->dma1 != d->dma2) + 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 * cs4236_probe(u_long csn, u_long vend_id); +static void cs4236_attach(u_long csn, u_long vend_id, char *name, + struct isa_device *dev); + +static struct pnp_device cs4236 = { + "cs423x", + cs4236_probe, + cs4236_attach, + &nsnd, /* use this for all sound cards */ + &tty_imask /* imask */ +}; +DATA_SET (pnpdevice_set, cs4236); + +static char * +cs4236_probe(u_long csn, u_long vend_id) +{ + char *s = NULL ; + if (vend_id == 0x3742630e) + s = "CS4237" ; + else if (vend_id == 0x3642630e) + s = "CS4236" ; + else if (vend_id == 0x3242630e) + s = "CS4232" ; + 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 +cs4236_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; +#if 0 + /* sb-compatible codec */ + dev->id_iobase = d.port[2] ; + tmp_d = sb_op_desc ; + tmp_d.alt_base = d.port[0] - 4; + d.drq[1] = 4 ; /* disable, it is not used ... */ + d.drq[0] = 0 ; /* remap ... */ +#else + /* mss-compatible codec */ + dev->id_iobase = d.port[0] -4 ; /* XXX old mss have 4 bytes before... */ + tmp_d = mss_op_desc ; + tmp_d.bd_id = MD_CS4232 ; /* to short-circuit the detect routine */ + tmp_d.alt_base = d.port[2]; + strcpy(tmp_d.name, name); + tmp_d.audio_fmt |= AFMT_FULLDUPLEX ; +#endif + write_pnp_parms( &d, ldn ); + 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; /* during the probe... */ + snddev_last_probed->probe(dev); + + 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; + int sb_mode = 0 ; /* XXX still not work in SB mode */ + + 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 = sb_mode ? sb_op_desc : mss_op_desc ; + + strcpy(tmp_d.name, 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]; + opti_write(p, 4, 0x56 /* fifo 1/2, OPL3, audio enable, SB3.2 */ ); + ad_write (&tmp_d, 10, 2); /* enable interrupts */ + + if (sb_mode) { /* sb-compatible codec */ + /* + * the 931 is not a real SB, it has important pieces of + * hardware controlled by both the WSS and the SB port... + */ + opti_write(p, 6, 1); /* MCIR6 wss 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: wss enable, sb disable */ +#if 0 /* not working yet... */ + opti_write(p, 5, 0x0 /* codec in single mode */ ); + dev->id_flags = 0 ; +#else + opti_write(p, 5, 0x28); /* MCIR5: codec in exp. mode,fifo */ + dev->id_flags = DV_F_DUAL_DMA | d.drq[1] ; +#endif + 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_intr = pcmintr ; + pcmattach(dev); +} + +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 */ + + read_pnp_parms ( &d , 0 ) ; + 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[0] ; /* primary dma */ + dev->id_irq = (1 << d.irq[0] ) ; + dev->id_intr = pcmintr ; + dev->id_flags = DV_F_DUAL_DMA | d.drq[1] ; + + tmp_d.alt_base = d.port[0]; + + pcmattach(dev); +} +#endif /* NPNP > 0 */ +#endif /* NPCM > 0 */ diff --git a/sys/dev/pcm/isa/mss.h b/sys/dev/pcm/isa/mss.h new file mode 100644 index 0000000..4b34e51 --- /dev/null +++ b/sys/dev/pcm/isa/mss.h @@ -0,0 +1,250 @@ +/* + * file: mss.h + * + * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it) + * + * This file contains information and macro definitions for + * AD1848-compatible devices, used in the MSS/WSS compatible boards. + * + */ + +/* + * + +The codec part of the board is seen as a set of 4 registers mapped +at the base address for the board (default 0x534). Note that some +(early) boards implemented 4 additional registers 4 location before +(usually 0x530) to store configuration information. This is a source +of confusion in that one never knows what address to specify. The +(current) convention is to use the old address (0x530) in the kernel +configuration file and consider MSS registers start four location +ahead. + + * + */ + +/* + * The four visible registers of the MSS : + * + */ + +#define io_Index_Addr(d) ((d)->io_base + 4) +#define IA_BUSY 0x80 /* readonly, set when busy */ +#define IA_MCE 0x40 /* the MCE bit. */ + /* + * the MCE bit must be set whenever the current mode of the + * codec is changed; this in particular is true for the + * Data Format (I8, I28) and Interface Config(I9) registers. + * Only exception are CEN and PEN which can be changed on the fly. + * The DAC output is muted when MCE is set. + */ +#define IA_TRD 0x20 /* Transfer request disable */ + /* + * When TRD is set, DMA transfers cease when the INT bit in + * the MSS status reg is set. Must be cleared for automode + * DMA, set otherwise. + */ +#define IA_AMASK 0x1f /* mask for indirect address */ + +#define io_Indexed_Data(d) ((d)->io_base+1+4) + /* + * data to be transferred to the indirect register addressed + * by index addr. During init and sw. powerdown, cannot be + * written to, and is always read as 0x80 (consistent with the + * busy flag). + */ + +#define io_Status(d) ((d)->io_base+2+4) + +#define IS_CUL 0x80 /* capture upper/lower */ +#define IS_CLR 0x40 /* capture left/right */ +#define IS_CRDY 0x20 /* capture ready for programmed i/o */ +#define IS_SER 0x10 /* sample error (overrun/underrun) */ +#define IS_PUL 0x08 /* playback upper/lower */ +#define IS_PLR 0x04 /* playback left/right */ +#define IS_PRDY 0x02 /* playback ready for programmed i/o */ +#define IS_INT 0x01 /* int status (1 = active) */ + /* + * IS_INT is clreared by any write to the status register. + */ + +#define io_Polled_IO(d) ((d)->io_base+3+4) + /* + * this register is used in case of polled i/o + */ + +/* + * The MSS has a set of 16 (or 32 depending on the model) indirect + * registers accessible through the data port by specifying the + * appropriate address in the address register. + * + * The 16 low registers are uniformly handled in AD1848/CS4248 compatible + * mode (often called MODE1). For the upper 16 registers there are + * some differences among different products, mainly Crystal uses them + * differently from OPTi. + * + */ + +/* + * volume registers + */ + +#define I6_MUTE 0x80 + +/* + * register I9 -- interface configuration. + */ + +#define I9_PEN 0x01 /* playback enable */ +#define I9_CEN 0x02 /* capture enable */ + +/* + * values used in bd_flags + */ +#define BD_F_MCE_BIT 0x0001 +#define BD_F_IRQ_OK 0x0002 +#define BD_F_TMR_RUN 0x0004 + + +/* + * sound/ad1848_mixer.h + * + * Definitions for the mixer of AD1848 and compatible codecs. + * + * Copyright by Hannu Savolainen 1994 + * + * 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. + */ +/* + * The AD1848 codec has generic input lines called Line, Aux1 and Aux2. + * Soundcard manufacturers have connected actual inputs (CD, synth, line, + * etc) to these inputs in different order. Therefore it's difficult + * to assign mixer channels to to these inputs correctly. The following + * contains two alternative mappings. The first one is for GUS MAX and + * the second is just a generic one (line1, line2 and line3). + * (Actually this is not a mapping but rather some kind of interleaving + * solution). + */ +#define GUSMAX_MIXER +#ifdef GUSMAX_MIXER +#define MODE1_REC_DEVICES \ + (SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD|SOUND_MASK_IMIX) + +#define MODE1_MIXER_DEVICES \ + (SOUND_MASK_SYNTH | SOUND_MASK_MIC | SOUND_MASK_CD | \ + SOUND_MASK_IGAIN | SOUND_MASK_PCM|SOUND_MASK_IMIX) + +#define MODE2_MIXER_DEVICES \ + (SOUND_MASK_SYNTH | SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_CD | SOUND_MASK_SPEAKER | SOUND_MASK_IGAIN | \ + SOUND_MASK_PCM | SOUND_MASK_IMIX) + +#else /* Generic mapping */ + +#define MODE1_REC_DEVICES \ + (SOUND_MASK_LINE3 | SOUND_MASK_MIC | SOUND_MASK_LINE1|SOUND_MASK_IMIX) + +#define MODE1_MIXER_DEVICES \ + (SOUND_MASK_LINE1 | SOUND_MASK_MIC | SOUND_MASK_LINE2 | \ + SOUND_MASK_IGAIN | SOUND_MASK_PCM | SOUND_MASK_IMIX) + +#define MODE2_MIXER_DEVICES \ + (SOUND_MASK_LINE1 | SOUND_MASK_MIC | SOUND_MASK_LINE2 | \ + SOUND_MASK_LINE3 | SOUND_MASK_SPEAKER | \ + SOUND_MASK_IGAIN | SOUND_MASK_PCM | SOUND_MASK_IMIX) +#endif + +#define OPTI931_MIXER_DEVICES \ + (SOUND_MASK_VOLUME | SOUND_MASK_SYNTH | SOUND_MASK_PCM | \ + SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_IGAIN ) + +/* + * Most of the mixer entries work in backwards. Setting the polarity field + * makes them to work correctly. + * + * The channel numbering used by individual soundcards is not fixed. + * Some cards have assigned different meanings for the AUX1, AUX2 + * and LINE inputs. Some have different features... + * The current version doesn't try to compensate this. + * + */ + +mixer_ent mix_devices[32][2] = { /* As used in GUS MAX */ +MIX_ENT(SOUND_MIXER_VOLUME, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_BASS, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_TREBLE, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_SYNTH, 4, 1, 0, 5, 5, 1, 0, 5), +MIX_ENT(SOUND_MIXER_PCM, 6, 1, 0, 6, 7, 1, 0, 6), +MIX_ENT(SOUND_MIXER_SPEAKER, 26, 1, 0, 4, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_LINE, 18, 1, 0, 5, 19, 1, 0, 5), +MIX_ENT(SOUND_MIXER_MIC, 0, 0, 5, 1, 1, 0, 5, 1), +MIX_ENT(SOUND_MIXER_CD, 2, 1, 0, 5, 3, 1, 0, 5), +MIX_ENT(SOUND_MIXER_IMIX, 13, 1, 2, 6, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_ALTPCM, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_RECLEV, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_IGAIN, 0, 0, 0, 4, 1, 0, 0, 4), +MIX_ENT(SOUND_MIXER_OGAIN, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_LINE1, 2, 1, 0, 5, 3, 1, 0, 5), +MIX_ENT(SOUND_MIXER_LINE2, 4, 1, 0, 5, 5, 1, 0, 5), +MIX_ENT(SOUND_MIXER_LINE3, 18, 1, 0, 5, 19, 1, 0, 5) +}; + +mixer_ent opti931_devices[32][2] = { /* for the opti931 */ +MIX_ENT(SOUND_MIXER_VOLUME, 22, 1, 1, 5, 23, 1, 1, 5), +MIX_ENT(SOUND_MIXER_BASS, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_TREBLE, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_SYNTH, 4, 1, 1, 4, 5, 1, 1, 4), +MIX_ENT(SOUND_MIXER_PCM, 6, 1, 0, 5, 7, 1, 0, 5), +MIX_ENT(SOUND_MIXER_SPEAKER, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_LINE, 18, 1, 1, 4, 19, 1, 1, 4), +MIX_ENT(SOUND_MIXER_MIC, 0, 0, 5, 1, 1, 0, 5, 1), +MIX_ENT(SOUND_MIXER_CD, 2, 1, 1, 4, 3, 1, 1, 4), +MIX_ENT(SOUND_MIXER_IMIX, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_ALTPCM, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_RECLEV, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_IGAIN, 0, 0, 0, 4, 1, 0, 0, 4), +MIX_ENT(SOUND_MIXER_OGAIN, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_LINE1, 2, 1, 0, 5, 3, 1, 0, 5), +MIX_ENT(SOUND_MIXER_LINE2, 4, 1, 0, 5, 5, 1, 0, 5), +MIX_ENT(SOUND_MIXER_LINE3, 18, 1, 0, 5, 19, 1, 0, 5) +}; + +static u_short default_mixer_levels[SOUND_MIXER_NRDEVICES] = { + 0x5a5a, /* Master Volume */ + 0x3232, /* Bass */ + 0x3232, /* Treble */ + 0x4b4b, /* FM */ + 0x4040, /* PCM */ + 0x4b4b, /* PC Speaker */ + 0x2020, /* Ext Line */ + 0x4040, /* Mic */ + 0x4b4b, /* CD */ + 0x0000, /* Recording monitor */ + 0x4b4b, /* SB PCM */ + 0x4b4b, /* Recording level */ + 0x2525, /* Input gain */ + 0x0000, /* Output gain */ + /* 0x4040, Line1 */ + 0x0000, /* Line1 */ + 0x0000, /* Line2 */ + 0x1515 /* Line3 (usually line in)*/ +}; + diff --git a/sys/dev/pcm/isa/sb.c b/sys/dev/pcm/isa/sb.c new file mode 100644 index 0000000..4ca1f1f --- /dev/null +++ b/sys/dev/pcm/isa/sb.c @@ -0,0 +1,1080 @@ +/* + * sound/sb_dsp.c + * + * driver for the SoundBlaster and clones. + * + * Copyright 1997 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 sbintr; +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); + +/* + * 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, + + sbintr, + 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. + * + * Remebber, isa probe routines are supposed to return the + * size of io space used. + */ + +static int +sb_probe(struct isa_device *dev) +{ + if (dev->id_iobase == -1) { + dev->id_iobase = 0x220; + 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] ; + + sb_dsp_init(d, dev); + return 0 ; +} + +/* + * here are the main routines from the switches. + */ + +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->esel.si_pid = 0; + d->esel.si_flags = 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 ; + } + + d->flags |= SND_F_BUSY ; + d->play_speed = d->rec_speed = DSP_DEFAULT_SPEED ; + + if (flags & O_NONBLOCK) + d->flags |= SND_F_NBIO ; + + reset_dbuf(& (d->dbuf_in) ); + reset_dbuf(& (d->dbuf_out) ); + sb_reset_dsp(d->io_base); + 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, int 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. + */ + + return ENOSYS ; +} + +static void +sbintr(int unit) +{ + snddev_info *d = &pcm_info[unit]; + int reason = 3, c=1, io_base = d->io_base; + + DEB(printf("got sbintr for unit %d, flags 0x%08x\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. + */ +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->dma1 < 4) + reason |= 1; + if (d->dma2 < 4) + reason |= 2; + } + if ( c & 2 ) { /* 16-bit dma */ + if (d->dma1 >= 4) + reason |= 1; + if (d->dma2 >= 4) + reason |= 2; + } + } + if ( c & 2 ) + inb(DSP_DATA_AVL16); /* 16-bit int ack */ + if (c & 1) + inb(DSP_DATA_AVAIL); /* 8-bit int ack */ + +DEB(printf("sbintr, flags 0x%08x reason %d\n", d->flags, reason)); + if ( (d->flags & SND_F_WR_DMA) && (reason & 1) ) + dsp_wrintr(d); + if ( (d->flags & SND_F_RD_DMA) && (reason & 2) ) + dsp_rdintr(d); + + /* + * 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 done 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) +{ + u_long s ; + int rd = reason & SND_CB_RD ; + int l = (rd) ? d->dbuf_in.dl0 : d->dbuf_out.dl0 ; + + switch (reason & SND_CB_REASON_MASK) { + case SND_CB_INIT : /* called with int enabled and no pending io */ + dsp_speed(d); + snd_set_blocksize(d); + if (d->play_fmt & AFMT_MU_LAW) + d->flags |= SND_F_XLAT8 ; + else + d->flags &= ~SND_F_XLAT8 ; + return 1; + break ; + + case SND_CB_START : /* called with int disabled */ + sb_cmd(d->io_base, rd ? DSP_CMD_SPKOFF : DSP_CMD_SPKON); + d->flags &= ~SND_F_INIT ; + if (d->bd_flags & BD_F_SB16) { + /* the SB16 can do full duplex using one 16-bit channel + * and one 8-bit channel. It needs to be programmed to + * use split format though. + */ + int b16 ; + int swap = 0 ; + + b16 = (rd) ? d->rec_fmt : d->play_fmt ; + b16 = (b16 == AFMT_S16_LE) ? 1 : 0; + /* + * check if I have to swap dma channels. Swap if + * - !rd, dma1 <4, b16 + * - !rd, dma1 >=4, !b16 + * - rd, dma2 <4, b16 + * - rd, dma2 >=4, !b16 + */ + if (!rd) { + if ( (d->dma1 <4 && b16) || (d->dma1 >=4 && !b16) ) swap = 1; + } else { + if ( (d->dma2 <4 && b16) || (d->dma2 >=4 && !b16) ) swap = 1; + } + /* + * before swapping should make sure that there is no + * pending DMA on the other channel... + */ + if (swap) { + int c = d->dma2 ; + d->dma2 = d->dma1; + d->dma1 = c ; + } + DEB(printf("sb_init: play %d rec %d dma1 %d dma2 %d\n", + d->play_fmt, d->rec_fmt, d->dma1, d->dma2)); + } + /* fallthrough */ + case SND_CB_RESTART: + if (d->bd_flags & BD_F_SB16) { + u_char c, c1 ; + /* + * SB16 support still not completely working!!! + * + * in principle, on the SB16, I could support simultaneous + * play & rec. + * However, there is no way to ask explicitly for 8 or + * 16 bit transfer. As a consequence, if we do 8-bit, + * we need to use the 8-bit channel, and if we do 16-bit, + * we need to use the other one. The only way I find to + * do this is to swap d->dma1 and d->dma2 ... + * + */ + + if (rd) { + c = ((d->dma2 > 3) ? DSP_DMA16 : DSP_DMA8) | + DSP_F16_FIFO_ON | DSP_F16_ADC ; + c1 = (d->play_fmt == AFMT_U8) ? 0 : DSP_F16_SIGNED ; + if (d->play_fmt == AFMT_MU_LAW) c1 = 0 ; + if (d->rec_fmt == AFMT_S16_LE) + l /= 2 ; + } else { + c = ((d->dma1 > 3) ? DSP_DMA16 : DSP_DMA8) | + DSP_F16_FIFO_ON | DSP_F16_DAC ; + c1 = (d->rec_fmt == AFMT_U8) ? 0 : DSP_F16_SIGNED ; + if (d->play_fmt == AFMT_MU_LAW) c1 = 0 ; + if (d->play_fmt == AFMT_S16_LE) + l /= 2 ; + } + + 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 { + u_char c ; + if (d->bd_flags & BD_F_HISPEED) + c = (rd) ? DSP_CMD_HSADC : DSP_CMD_HSDAC ; + else + c = (rd) ? DSP_CMD_ADC8 : DSP_CMD_DAC8 ; + sb_cmd3(d->io_base, c , l - 1) ; + } + break; + + case SND_CB_STOP : + /* 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(DSP_RESET, 1); + DELAY(100); + outb(DSP_RESET, 0); + for (loopc = 0; loopc<100 && !(inb(DSP_DATA_AVAIL) & 0x80); loopc++) + DELAY(30); + + if (inb(DSP_READ) != 0xAA) { + DEB(printf("sb_reset_dsp failed\n")); + 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->dma2 = d->dma1 ; /* 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); + + sb_setmixer(io_base, DMA_NR, (1 << d->dma1) | (1 << d->dma2)); + break ; + + case 3 : + d->dma2 = d->dma1 ; /* 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; + } + } + } + + if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) + printf("Hmm... Could this be an ESS488 based card (rev %d)\n", + ess_minor & 0x0f); + else if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) + printf("Hmm... Could this be an ESS688 based card (rev %d)\n", + ess_minor & 0x0f); + } + + if (d->bd_flags & BD_F_JAZZ16) { + if (d->bd_flags & BD_F_JAZZ16_2) + fmt = "SoundMan Wave %d.%d"; + else + fmt = "MV Jazz16 %d.%d"; + d->audio_fmt |= AFMT_S16_LE; /* 16 bits */ + } + } + + 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 + */ + +int +sb_cmd(int io_base, u_char val) +{ + int i; + + for (i = 0; i < 1000 ; i++) { + if ((inb(DSP_STATUS) & 0x80) == 0) { + outb(DSP_COMMAND, 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; +} + +void +sb_setmixer(int io_base, u_int port, u_int value) +{ + u_long flags; + + flags = spltty(); + outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + DELAY(10); + outb(MIXER_DATA, (u_char) (value & 0xff)); + DELAY(10); + splx(flags); +} + +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 +sb_getmixer(int io_base, u_int port) +{ + int val; + u_long flags; + + flags = spltty(); + outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + DELAY(10); + val = inb(MIXER_DATA); + DELAY(10); + splx(flags); + + return val; +} + + +/* + * 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 ; + + 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 ; + } + /* + * only some models can do stereo, and only if not + * simultaneously using midi. + */ + if ( (d->bd_id & 0xff00) < 0x300 || d->bd_flags & BD_F_MIDIBUSY) + d->flags &= ~SND_F_STEREO; + + /* + * 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); + + /* + * Logitech SoundMan Games and Jazz16 cards can support 44.1kHz + * stereo + */ +#if !defined (SM_GAMES) + /* + * Max. stereo speed is 22050 + */ + if (d->flags & SND_F_STEREO && speed > 22050 && !(d->bd_flags & BD_F_JAZZ16)) + speed = 22050; +#endif + + if ((speed > 22050) && d->bd_flags & BD_F_MIDIBUSY) + speed = 22050; + + if (d->flags & SND_F_STEREO) + 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, DSP_CMD_TCONST, tconst); + 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, DSP_CMD_TCONST, tconst); + splx(flags); + + tmp = 256 - tconst; + speed = (1000000 + tmp / 2) / tmp; + } + + if (d->flags & SND_F_STEREO) + 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 outmask; + 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 *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 = sb_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[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_intr = pcmintr ; + dev->id_flags = 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 + * 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 ; + if (vend_id == 0x01009305) + s = "Avance Asound 100" ; + if (vend_id == 0x2b008c0e) + s = "SB16 Value PnP" ; + /* + * The SB16/AWE64 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 AWE64 PnP: 0x39008c0e 0x9d008c0e 0xc3008c0e + */ + if ( (vend_id & 0xffffff) == (0x9d008c0e & 0xffffff) ) + s = "SB AWE64 PnP"; + 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]; + 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; + snddev_last_probed->probe(dev); /* not really necessary but doesn't harm */ + + pcmattach(dev); +} +#endif /* NPNP */ + +#endif diff --git a/sys/dev/pcm/isa/sb.h b/sys/dev/pcm/isa/sb.h new file mode 100644 index 0000000..8548997 --- /dev/null +++ b/sys/dev/pcm/isa/sb.h @@ -0,0 +1,387 @@ +/* + * file: sbcard.h + */ + +typedef struct _sbdev_info { + +} sbdev_info ; + +extern int sbc_major, sbc_minor ; +/* + * sound blaster registers + */ + +#define DSP_RESET (io_base + 0x6) +#define DSP_READ (io_base + 0xA) +#define DSP_WRITE (io_base + 0xC) +#define DSP_COMMAND (io_base + 0xC) +#define DSP_STATUS (io_base + 0xC) +#define DSP_DATA_AVAIL (io_base + 0xE) +#define DSP_DATA_AVL16 (io_base + 0xF) +#define MIXER_ADDR (io_base + 0x4) +#define MIXER_DATA (io_base + 0x5) +#define OPL3_LEFT (io_base + 0x0) +#define OPL3_RIGHT (io_base + 0x2) +#define OPL3_BOTH (io_base + 0x8) + +/* + * DSP Commands. There are many, and in many cases they are used explicitly + */ + +#define DSP_DAC8 0x10 /* direct DAC output */ +#define DSP_CMD_DAC8 0x14 /* single cycle 8-bit dma out */ +#define DSP_CMD_DAC2 0x16 /* 2-bit adpcm dma out (cont) */ +#define DSP_CMD_DAC2S 0x17 /* 2-bit adpcm dma out (start) */ + +#define DSP_CMD_DAC8_A 0x1c /* auto 8-bit dma out */ +#define DSP_CMD_DAC2S_A 0x1f /* auto 2-bit adpcm dma out (start) */ + +#define DSP_ADC8 0x20 /* direct ADC input */ + +#define DSP_CMD_ADC8 0x24 /* single cycle 8-bit dma in */ +#define DSP_CMD_ADC8_A 0x2c /* auto 8-bit dma out */ + +#define DSP_CMD_O16 0xb0 +#define DSP_CMD_I16 0xb8 +#define DSP_CMD_O8 0xc0 +#define DSP_CMD_I8 0xc8 + +#define DSP_MODE_U8MONO 0x00 +#define DSP_MODE_U8STEREO 0x20 +#define DSP_MODE_S16MONO 0x10 +#define DSP_MODE_S16STEREO 0x30 + +#define DSP_CMD_SPKON 0xD1 +#define DSP_CMD_SPKOFF 0xD3 +#define DSP_CMD_SPKR(on) (0xD1 | (on ? 0:2)) +#define DSP_CMD_DMAON 0xD0 /* ??? the comment says Halt DMA */ +#define DSP_CMD_DMAOFF 0xD4 /* ??? comment says continue dma */ + +#define DSP_CMD_DMAHALT 0xD0 +#define DSP_CMD_TCONST 0x40 /* set time constant */ +#define DSP_CMD_HSSIZE 0x48 /* high speed dma count */ +#define DSP_CMD_HSDAC 0x91 /* high speed dac */ +#define DSP_CMD_HSADC 0x99 /* high speed adc */ + +#define DSP_CMD_GETVER 0xE1 +#define DSP_CMD_GETID 0xE7 /* return id bytes */ + + /* prepare for dma input */ +#define DSP_CMD_DMAMODE(stereo, bit16) (0xA0 | (stereo ? 8:0) | (bit16 ? 4:0)) + +#define DSP_CMD_OUT16 0x41 /* send parms for dma out on sb16 */ +#define DSP_CMD_IN16 0x42 /* send parms for dma in on sb16 */ +#if 0 /*** unknown ***/ + /* + * D9 and D5 are used on the sb16 on close... maybe a reset of + * some subsystem ? + */ +#define DSP_CMD_D9 0xD9 +#define DSP_CMD_D5 0xD5 +#define DSP_CMD_FA 0xFA /* get version from prosonic*/ +#define DSP_CMD_FB 0xFB /* set irq/dma for prosonic*/ +#endif + +/* + * in fact, for the SB16, dma commands are as follows: + * + * cmd, mode, len_low, len_high. + * + * cmd is a combination of DSP_DMA16 or DSP_DMA8 and + */ + +#define DSP_DMA16 0xb0 +#define DSP_DMA8 0xc0 +# define DSP_F16_DAC 0x00 +# define DSP_F16_ADC 0x08 +# define DSP_F16_AUTO 0x04 +# define DSP_F16_FIFO_ON 0x02 + +/* + * mode is a combination of the following: + */ +#define DSP_F16_STEREO 0x20 +#define DSP_F16_SIGNED 0x10 + +#define IMODE_NONE 0 +#define IMODE_OUTPUT PCM_ENABLE_OUTPUT +#define IMODE_INPUT PCM_ENABLE_INPUT +#define IMODE_INIT 3 +#define IMODE_MIDI 4 + +#define NORMAL_MIDI 0 +#define UART_MIDI 1 + +/* + * values used for bd_flags in SoundBlaster driver + */ +#define BD_F_HISPEED 0x0001 /* doing high speed ... */ + +#define BD_F_JAZZ16 0x0002 /* jazz16 detected */ +#define BD_F_JAZZ16_2 0x0004 /* jazz16 type 2 */ + +#define BD_F_DUP_MIDI 0x0008 /* duplex midi */ + +#define BD_F_MIX_MASK 0x0070 /* up to 8 mixers (I know of 3) */ +#define BD_F_MIX_CT1335 0x0010 /* CT1335 */ +#define BD_F_MIX_CT1345 0x0020 /* CT1345 */ +#define BD_F_MIX_CT1745 0x0030 /* CT1745 */ + +#define BD_F_SB16 0x0100 /* this is a SB16 */ +#define BD_F_NOREC 0x0200 /* recording not supported on this board */ +#define BD_F_MIDIBUSY 0x0400 /* midi busy */ + + +/* + * sound/sb_mixer.h + * + * Definitions for the SB Pro and SB16 mixers + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * Modified: Hunyue Yau Jan 6 1994 Added defines for the Sound Galaxy NX Pro + * mixer. + * + */ + +#define SBPRO_RECORDING_DEVICES \ + (SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD) + +/* Same as SB Pro, unless I find otherwise */ +#define SGNXPRO_RECORDING_DEVICES SBPRO_RECORDING_DEVICES + +#define SBPRO_MIXER_DEVICES \ + (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_CD | SOUND_MASK_VOLUME) + +/* + * SG NX Pro has treble and bass settings on the mixer. The 'speaker' channel + * is the COVOX/DisneySoundSource emulation volume control on the mixer. It + * does NOT control speaker volume. Should have own mask eventually? + */ +#define SGNXPRO_MIXER_DEVICES \ + (SBPRO_MIXER_DEVICES | SOUND_MASK_BASS | \ + SOUND_MASK_TREBLE | SOUND_MASK_SPEAKER ) + +#define SB16_RECORDING_DEVICES \ + (SOUND_MASK_SYNTH | SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD) + +#define SB16_MIXER_DEVICES \ + (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | \ + SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | \ + SOUND_MASK_IGAIN | SOUND_MASK_OGAIN | \ + SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE) + +/* + * Mixer registers + * + * NOTE! RECORD_SRC == IN_FILTER + */ + +/* + * Mixer registers of SB Pro + */ +#define VOC_VOL 0x04 +#define MIC_VOL 0x0A +#define MIC_MIX 0x0A +#define RECORD_SRC 0x0C +#define IN_FILTER 0x0C +#define OUT_FILTER 0x0E +#define MASTER_VOL 0x22 +#define FM_VOL 0x26 +#define CD_VOL 0x28 +#define LINE_VOL 0x2E +#define IRQ_NR 0x80 +#define DMA_NR 0x81 +#define IRQ_STAT 0x82 + +/* + * Additional registers on the SG NX Pro + */ +#define COVOX_VOL 0x42 +#define TREBLE_LVL 0x44 +#define BASS_LVL 0x46 + +#define FREQ_HI (1 << 3)/* Use High-frequency ANFI filters */ +#define FREQ_LOW 0 /* Use Low-frequency ANFI filters */ +#define FILT_ON 0 /* Yes, 0 to turn it on, 1 for off */ +#define FILT_OFF (1 << 5) + +#define MONO_DAC 0x00 +#define STEREO_DAC 0x02 + +/* + * Mixer registers of SB16 + */ +#define SB16_IMASK_L 0x3d +#define SB16_IMASK_R 0x3e +#define SB16_OMASK 0x3c + + +#ifndef __SB_MIXER_C__ +mixer_tab sbpro_mix; +mixer_tab sb16_mix; +#ifdef __SGNXPRO__ +mixer_tab sgnxpro_mix; +#endif +static u_char sb16_recmasks_L[SOUND_MIXER_NRDEVICES]; +static u_char sb16_recmasks_R[SOUND_MIXER_NRDEVICES]; +#else /* __SB_MIXER_C__ defined */ +mixer_tab sbpro_mix = { + PMIX_ENT(SOUND_MIXER_VOLUME, 0x22, 7, 4, 0x22, 3, 4), + PMIX_ENT(SOUND_MIXER_BASS, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_TREBLE, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_SYNTH, 0x26, 7, 4, 0x26, 3, 4), + PMIX_ENT(SOUND_MIXER_PCM, 0x04, 7, 4, 0x04, 3, 4), + PMIX_ENT(SOUND_MIXER_SPEAKER, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_LINE, 0x2e, 7, 4, 0x2e, 3, 4), + PMIX_ENT(SOUND_MIXER_MIC, 0x0a, 2, 3, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_CD, 0x28, 7, 4, 0x28, 3, 4), + PMIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_RECLEV, 0x00, 0, 0, 0x00, 0, 0) +}; + +#ifdef __SGNXPRO__ +mixer_tab sgnxpro_mix = { + PMIX_ENT(SOUND_MIXER_VOLUME, 0x22, 7, 4, 0x22, 3, 4), + PMIX_ENT(SOUND_MIXER_BASS, 0x46, 2, 3, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_TREBLE, 0x44, 2, 3, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_SYNTH, 0x26, 7, 4, 0x26, 3, 4), + PMIX_ENT(SOUND_MIXER_PCM, 0x04, 7, 4, 0x04, 3, 4), + PMIX_ENT(SOUND_MIXER_SPEAKER, 0x42, 2, 3, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_LINE, 0x2e, 7, 4, 0x2e, 3, 4), + PMIX_ENT(SOUND_MIXER_MIC, 0x0a, 2, 3, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_CD, 0x28, 7, 4, 0x28, 3, 4), + PMIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_RECLEV, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_IGAIN, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_OGAIN, 0x00, 0, 0, 0x00, 0, 0) +}; +#endif + +mixer_tab sb16_mix = { + PMIX_ENT(SOUND_MIXER_VOLUME, 0x30, 3, 5, 0x31, 3, 5), + PMIX_ENT(SOUND_MIXER_BASS, 0x46, 4, 4, 0x47, 4, 4), + PMIX_ENT(SOUND_MIXER_TREBLE, 0x44, 4, 4, 0x45, 4, 4), + PMIX_ENT(SOUND_MIXER_SYNTH, 0x34, 3, 5, 0x35, 3, 5), + PMIX_ENT(SOUND_MIXER_PCM, 0x32, 3, 5, 0x33, 3, 5), + PMIX_ENT(SOUND_MIXER_SPEAKER, 0x3b, 6, 2, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_LINE, 0x38, 3, 5, 0x39, 3, 5), + PMIX_ENT(SOUND_MIXER_MIC, 0x3a, 3, 5, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_CD, 0x36, 3, 5, 0x37, 3, 5), + PMIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_RECLEV, 0x3f, 6, 2, 0x40, 6, 2), /* Obsol,Use IGAIN*/ + PMIX_ENT(SOUND_MIXER_IGAIN, 0x3f, 6, 2, 0x40, 6, 2), + PMIX_ENT(SOUND_MIXER_OGAIN, 0x41, 6, 2, 0x42, 6, 2) +}; + +#ifdef SM_GAMES /* Master volume is lower and PCM & FM + * volumes higher than with SB Pro. This + * improves the sound quality */ + +static u_short levels[SOUND_MIXER_NRDEVICES] = +{ + 0x2020, /* Master Volume */ + 0x4b4b, /* Bass */ + 0x4b4b, /* Treble */ + 0x6464, /* FM */ + 0x6464, /* PCM */ + 0x4b4b, /* PC Speaker */ + 0x4b4b, /* Ext Line */ + 0x0000, /* Mic */ + 0x4b4b, /* CD */ + 0x4b4b, /* Recording monitor */ + 0x4b4b, /* SB PCM */ + 0x4b4b, /* Recording level */ + 0x4b4b, /* Input gain */ +0x4b4b}; /* Output gain */ + +#else /* If the user selected just plain SB Pro */ + +static u_short levels[SOUND_MIXER_NRDEVICES] = +{ + 0x5a5a, /* Master Volume */ + 0x4b4b, /* Bass */ + 0x4b4b, /* Treble */ + 0x4b4b, /* FM */ + 0x4b4b, /* PCM */ + 0x4b4b, /* PC Speaker */ + 0x4b4b, /* Ext Line */ + 0x1010, /* Mic */ + 0x4b4b, /* CD */ + 0x4b4b, /* Recording monitor */ + 0x4b4b, /* SB PCM */ + 0x4b4b, /* Recording level */ + 0x4b4b, /* Input gain */ +0x4b4b}; /* Output gain */ +#endif /* SM_GAMES */ + +static u_char sb16_recmasks_L[SOUND_MIXER_NRDEVICES] = +{ + 0x00, /* SOUND_MIXER_VOLUME */ + 0x00, /* SOUND_MIXER_BASS */ + 0x00, /* SOUND_MIXER_TREBLE */ + 0x40, /* SOUND_MIXER_SYNTH */ + 0x00, /* SOUND_MIXER_PCM */ + 0x00, /* SOUND_MIXER_SPEAKER */ + 0x10, /* SOUND_MIXER_LINE */ + 0x01, /* SOUND_MIXER_MIC */ + 0x04, /* SOUND_MIXER_CD */ + 0x00, /* SOUND_MIXER_IMIX */ + 0x00, /* SOUND_MIXER_ALTPCM */ + 0x00, /* SOUND_MIXER_RECLEV */ + 0x00, /* SOUND_MIXER_IGAIN */ + 0x00 /* SOUND_MIXER_OGAIN */ +}; + +static u_char sb16_recmasks_R[SOUND_MIXER_NRDEVICES] = +{ + 0x00, /* SOUND_MIXER_VOLUME */ + 0x00, /* SOUND_MIXER_BASS */ + 0x00, /* SOUND_MIXER_TREBLE */ + 0x20, /* SOUND_MIXER_SYNTH */ + 0x00, /* SOUND_MIXER_PCM */ + 0x00, /* SOUND_MIXER_SPEAKER */ + 0x08, /* SOUND_MIXER_LINE */ + 0x01, /* SOUND_MIXER_MIC */ + 0x02, /* SOUND_MIXER_CD */ + 0x00, /* SOUND_MIXER_IMIX */ + 0x00, /* SOUND_MIXER_ALTPCM */ + 0x00, /* SOUND_MIXER_RECLEV */ + 0x00, /* SOUND_MIXER_IGAIN */ + 0x00 /* SOUND_MIXER_OGAIN */ +}; + +/* + * Recording sources (SB Pro) + */ +#endif /* __SB_MIXER_C__ */ + +#define SRC_MIC 1 /* Select Microphone recording source */ +#define SRC_CD 3 /* Select CD recording source */ +#define SRC_LINE 7 /* Use Line-in for recording source */ + + diff --git a/sys/dev/sound/isa/mss.c b/sys/dev/sound/isa/mss.c new file mode 100644 index 0000000..97b328d0 --- /dev/null +++ b/sys/dev/sound/isa/mss.c @@ -0,0 +1,1509 @@ +/* + * sound/ad1848.c + * + * Modified by Luigi Rizzo (luigi@iet.unipi.it) + * + * Driver for Microsoft Sound System/Windows Sound System (mss) + * -compatible boards. This includes: + * + * AD1848, CS4248 + * + * CS4231, used in the GUS MAX and some other cards; + * AD1845, CS4231A (CS4231-like) + * CS4232 (CS4231+SB and MPU, PnP) + * CS4236 (upgrade of the CS4232, has a better mixer) + * OPTi931 (WSS compatible, full duplex, some differences from CS42xx) + * + * Copyright Luigi Rizzo, 1997 + * 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 + * + * The OPTi931 appears to be quite buggy. + */ + +#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); + +static d_open_t mss_open; +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); + +/* + * 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 + */ +} ; + +/* + * this 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; + + if (dev->id_iobase == -1) { + dev->id_iobase = 0x530; + 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 */ + DDB(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) { + DDB(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 */ +} + +/* + * 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->dma1, d->dma2, dev->id_flags); + + 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 (d->dma1 == d->dma2) /* single chan dma */ + outb(dev->id_iobase, bits | dma_bits[d->dma1]); + else { + if (d->dma1 == 0 && d->dma2 == 1) + bits |= 5 ; + else if (d->dma1 == 1 && d->dma2 == 0) + bits |= 6 ; + else if (d->dma1 == 3 && d->dma2 == 0) + bits |= 7 ; + else { + printf("invalid dual dma config %d:%d\n", + d->dma1, d->dma2); + dev->id_irq = 0 ; + return 0 ; + } + outb(dev->id_iobase, bits ); + } + } + mss_reinit(d); + ad1848_mixer_reset(d); + return 0; +} + +static 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) ) { + /* + * device was idle. Do the necessary initialization, + * but no need keep interrupts blocked since this device + * 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->esel.si_pid = 0; + d->esel.si_flags = 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; + } + reset_dbuf(& (d->dbuf_in) ); + reset_dbuf(& (d->dbuf_out) ); + ask_init(d); + } + splx(s); + 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) ) { /* last one ... */ + d->flags |= SND_F_CLOSING ; + splx(s); /* is this ok here ? */ + snd_flush(d); + outb(io_Status(d), 0); /* Clear interrupt status */ + d->flags = 0 ; + } + splx(s); + return 0 ; +} + +static int +mss_ioctl(dev_t dev, int 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. + */ + +static int +mss_callback(snddev_info *d, int reason) +{ + u_long s; + 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 WSS */ + snd_set_blocksize(d); + mss_reinit(d); + return 1 ; + break ; + + case SND_CB_START : + /* fallthrough */ + case SND_CB_RESTART : + s = spltty(); + cnt = wr ? d->dbuf_out.dl0 : d->dbuf_in.dl0 ; + 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) ; + DDB( 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 || (d->dma1 == d->dma2) ) + ad_write_cnt(d, 14, cnt); + else + ad_write_cnt(d, 30, cnt); + + splx(s); + break ; + case SND_CB_STOP : + case SND_CB_ABORT : /* XXX check this... */ + s = spltty(); + 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) ) ; + + /* disable DMA by clearing count registers. */ + if (wr || (d->dma1 == d->dma2) ) + ad_write_cnt(d, 14, 0); + else + ad_write_cnt(d, 30, 0); + splx(s); + break; + } + return 0 ; +} + +/* + * main irq handler for the CS423x. The OPTi931 code is + * a separate one. + */ + +static void +mss_intr(int unit) +{ + snddev_info *d = &pcm_info[unit]; + u_char i11, c; + u_char reason; /* b0 = playback, b1 = capture, b2 = timer */ + + i11 = ad_read(d, 11); + reason = inb(io_Status(d)); + + if ( ! (reason & 1) ) /* no int, maybe a shared line ? */ + return; + /* get exact reason */ + c = (d->dma1 == d->dma2) ? 0x10 : ad_read(d, 24); + if ( (d->flags & SND_F_WR_DMA) && (c & 0x10) ) + dsp_wrintr(d); + c = (d->dma1 == d->dma2) ? 0x20 : ad_read(d, 24); + if ( (d->flags & SND_F_RD_DMA) && (c & 0x20) ) + dsp_rdintr(d); + /* XXX check this on the 4236... */ + if (d->dma1 == d->dma2) + outb(io_Status(d), 0); /* Clear interrupt status */ + else + ad_write(d, 24, ~c); /* ack selectively */ +} + +/* + * the opti931 seems to miss interrupts when working in full + * duplex. god know why... + */ +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; + int w_miss=0, r_miss=0; + static int misses=0; /* XXX kludge */ + +#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 = (d->dma1 == d->dma2) ? 0xc : opti_read(d->conf_base, 11); + mc11 &= 0x0c ; + if (c & 0x10) { + printf("Warning CD interrupt\n"); + mc11 |= 0x10 ; + } + if (c & 0x20) { + 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 ; + if ( mc11 == 0 ) { /* perhaps can return ... */ + reason = inb(io_Status(d)); + if (reason & 1) { + printf("one more try...\n"); + goto again; + } + if (w_miss || r_miss ) { + misses++; + if ( (misses & 0xff) == 1 ) + printf("opti931: %d missed irq (now %s%s)\n", + misses, w_miss?"play ":"", + r_miss?"capture ":""); + } + if (loops==10) + printf("ouch, intr but nothing in mcir11 0x%02x\n", mc11); + if (w_miss) mc11 |= 4 ; + if (r_miss) mc11 |= 8 ; + if (mc11==0) + return; + } + + if ( (d->flags & SND_F_RD_DMA) && (mc11 & 8) ) { + dsp_rdintr(d); + r_miss = 0 ; + } + else if (isa_dmastatus1(d->dma2) == 0 && (d->flags & SND_F_RD_DMA) ) + r_miss = 1 ; + + if ( (d->flags & SND_F_WR_DMA) && (mc11 & 4) ) { + if (isa_dmastatus1(d->dma1) != 0) + printf("opti931_intr: wr dma %d\n", + isa_dmastatus1(d->dma1)); + dsp_wrintr(d); + w_miss = 0 ; + } + else if (isa_dmastatus1(d->dma1) == 0 && (d->flags & SND_F_WR_DMA) ) + w_miss = 1 ; + + opti_write(d->conf_base, 11, ~mc11); /* ack */ + if (--loops) goto again; + 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); +} +/* + * 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 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 0x%02x\n", n); + return n ; +} + +static int +ad_read(snddev_info *d, int reg) +{ + u_long flags; + int x; + + flags = spltty(); + AD_WAIT_INIT(d, 100); + 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, 100); + 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, 100); + prev = inb(io_Index_Addr(d)); + outb(io_Index_Addr(d), prev | IA_MCE | IA_TRD ) ; +} + +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)); + + outb(io_Index_Addr(d), (prev & ~IA_MCE) | IA_TRD); /* Clear the MCE bit */ + wait_for_calibration(d); + splx(flags); +} + +/* + * only one source can be set... + * + * fixed -- lr 970725 + */ +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; +} + +/* + * mixer conversion table: from 0..100 scale to codec values + * + * I don't understand what's this for... maybe achieve a log-scale + * volume control ? + */ + +static char mix_cvt[101] = { + 0, 0, 3, 7,10,13,16,19,21,23,26,28,30,32,34,35,37,39,40,42, + 43,45,46,47,49,50,51,52,53,55,56,57,58,59,60,61,62,63,64,65, + 65,66,67,68,69,70,70,71,72,73,73,74,75,75,76,77,77,78,79,79, + 80,81,81,82,82,83,84,84,85,85,86,86,87,87,88,88,89,89,90,90, + 91,91,92,92,93,93,94,94,95,95,96,96,96,97,97,98,98,98,99,99, + 100 +}; + +/* + * 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) { + DDB(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); + + /* Scale volumes */ + left = mix_cvt[left]; + right = mix_cvt[right]; + + /* + * Set the left channel + */ + + regoffs = (*mix_d)[dev][LEFT_CHN].regno; + old = val = ad_read(d, regoffs); + if (regoffs != 0) + val = old & 0x7f ; /* clear mute bit. */ + change_bits(mix_d, &val, dev, LEFT_CHN, left); + ad_write(d, regoffs, val); + DEB(printf("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; + val = ad_read(d, regoffs); + if (regoffs != 1) + val = old & 0x7f ; /* clear mute bit. */ + change_bits(mix_d, &val, dev, RIGHT_CHN, right); + ad_write(d, regoffs, 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 = MODE1_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); +} + +/* + * 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!) + * + * fixed lr970724 + */ + +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) { + 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 it's 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 */ + DDB(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) { + DDB(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) { + DDB(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)) { + DDB(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 + */ + + 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))) { + DDB(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) { + 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? */ + DDB(printf("mss_detect error - step H(%x)\n", tmp1)); + return 0; + } + /* + * Verify that some bits of I25 are read only. + */ + + DDB(printf("mss_detect() - step I\n")); + 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 */ + break; + + case 0x83: /* CS4236 */ + name = "CS4236"; + d->bd_id = MD_CS4236; + break ; + + default: /* Assume CS4231 */ + printf("unknown id 0x%02x, assuming CS4231\n", id); + d->bd_id = MD_CS4231; + + } + } + ad_write(d, 25, tmp1); /* Restore bits */ + + } + } + DDB(printf("mss_detect() - Detected %s\n", name)); + strcpy(d->name, 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 ( d->dma1 != d->dma2 && 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 (d->dma1 != d->dma2) { + 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); + ad_write_cnt(d, 14, 0 ); /* playback count */ + if (d->dma1 != d->dma2) + 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 * cs4236_probe(u_long csn, u_long vend_id); +static void cs4236_attach(u_long csn, u_long vend_id, char *name, + struct isa_device *dev); + +static struct pnp_device cs4236 = { + "cs423x", + cs4236_probe, + cs4236_attach, + &nsnd, /* use this for all sound cards */ + &tty_imask /* imask */ +}; +DATA_SET (pnpdevice_set, cs4236); + +static char * +cs4236_probe(u_long csn, u_long vend_id) +{ + char *s = NULL ; + if (vend_id == 0x3742630e) + s = "CS4237" ; + else if (vend_id == 0x3642630e) + s = "CS4236" ; + else if (vend_id == 0x3242630e) + s = "CS4232" ; + 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 +cs4236_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; +#if 0 + /* sb-compatible codec */ + dev->id_iobase = d.port[2] ; + tmp_d = sb_op_desc ; + tmp_d.alt_base = d.port[0] - 4; + d.drq[1] = 4 ; /* disable, it is not used ... */ + d.drq[0] = 0 ; /* remap ... */ +#else + /* mss-compatible codec */ + dev->id_iobase = d.port[0] -4 ; /* XXX old mss have 4 bytes before... */ + tmp_d = mss_op_desc ; + tmp_d.bd_id = MD_CS4232 ; /* to short-circuit the detect routine */ + tmp_d.alt_base = d.port[2]; + strcpy(tmp_d.name, name); + tmp_d.audio_fmt |= AFMT_FULLDUPLEX ; +#endif + write_pnp_parms( &d, ldn ); + 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; /* during the probe... */ + snddev_last_probed->probe(dev); + + 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; + int sb_mode = 0 ; /* XXX still not work in SB mode */ + + 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 = sb_mode ? sb_op_desc : mss_op_desc ; + + strcpy(tmp_d.name, 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]; + opti_write(p, 4, 0x56 /* fifo 1/2, OPL3, audio enable, SB3.2 */ ); + ad_write (&tmp_d, 10, 2); /* enable interrupts */ + + if (sb_mode) { /* sb-compatible codec */ + /* + * the 931 is not a real SB, it has important pieces of + * hardware controlled by both the WSS and the SB port... + */ + opti_write(p, 6, 1); /* MCIR6 wss 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: wss enable, sb disable */ +#if 0 /* not working yet... */ + opti_write(p, 5, 0x0 /* codec in single mode */ ); + dev->id_flags = 0 ; +#else + opti_write(p, 5, 0x28); /* MCIR5: codec in exp. mode,fifo */ + dev->id_flags = DV_F_DUAL_DMA | d.drq[1] ; +#endif + 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_intr = pcmintr ; + pcmattach(dev); +} + +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 */ + + read_pnp_parms ( &d , 0 ) ; + 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[0] ; /* primary dma */ + dev->id_irq = (1 << d.irq[0] ) ; + dev->id_intr = pcmintr ; + dev->id_flags = DV_F_DUAL_DMA | d.drq[1] ; + + tmp_d.alt_base = d.port[0]; + + pcmattach(dev); +} +#endif /* NPNP > 0 */ +#endif /* NPCM > 0 */ diff --git a/sys/dev/sound/isa/mss.h b/sys/dev/sound/isa/mss.h new file mode 100644 index 0000000..4b34e51 --- /dev/null +++ b/sys/dev/sound/isa/mss.h @@ -0,0 +1,250 @@ +/* + * file: mss.h + * + * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it) + * + * This file contains information and macro definitions for + * AD1848-compatible devices, used in the MSS/WSS compatible boards. + * + */ + +/* + * + +The codec part of the board is seen as a set of 4 registers mapped +at the base address for the board (default 0x534). Note that some +(early) boards implemented 4 additional registers 4 location before +(usually 0x530) to store configuration information. This is a source +of confusion in that one never knows what address to specify. The +(current) convention is to use the old address (0x530) in the kernel +configuration file and consider MSS registers start four location +ahead. + + * + */ + +/* + * The four visible registers of the MSS : + * + */ + +#define io_Index_Addr(d) ((d)->io_base + 4) +#define IA_BUSY 0x80 /* readonly, set when busy */ +#define IA_MCE 0x40 /* the MCE bit. */ + /* + * the MCE bit must be set whenever the current mode of the + * codec is changed; this in particular is true for the + * Data Format (I8, I28) and Interface Config(I9) registers. + * Only exception are CEN and PEN which can be changed on the fly. + * The DAC output is muted when MCE is set. + */ +#define IA_TRD 0x20 /* Transfer request disable */ + /* + * When TRD is set, DMA transfers cease when the INT bit in + * the MSS status reg is set. Must be cleared for automode + * DMA, set otherwise. + */ +#define IA_AMASK 0x1f /* mask for indirect address */ + +#define io_Indexed_Data(d) ((d)->io_base+1+4) + /* + * data to be transferred to the indirect register addressed + * by index addr. During init and sw. powerdown, cannot be + * written to, and is always read as 0x80 (consistent with the + * busy flag). + */ + +#define io_Status(d) ((d)->io_base+2+4) + +#define IS_CUL 0x80 /* capture upper/lower */ +#define IS_CLR 0x40 /* capture left/right */ +#define IS_CRDY 0x20 /* capture ready for programmed i/o */ +#define IS_SER 0x10 /* sample error (overrun/underrun) */ +#define IS_PUL 0x08 /* playback upper/lower */ +#define IS_PLR 0x04 /* playback left/right */ +#define IS_PRDY 0x02 /* playback ready for programmed i/o */ +#define IS_INT 0x01 /* int status (1 = active) */ + /* + * IS_INT is clreared by any write to the status register. + */ + +#define io_Polled_IO(d) ((d)->io_base+3+4) + /* + * this register is used in case of polled i/o + */ + +/* + * The MSS has a set of 16 (or 32 depending on the model) indirect + * registers accessible through the data port by specifying the + * appropriate address in the address register. + * + * The 16 low registers are uniformly handled in AD1848/CS4248 compatible + * mode (often called MODE1). For the upper 16 registers there are + * some differences among different products, mainly Crystal uses them + * differently from OPTi. + * + */ + +/* + * volume registers + */ + +#define I6_MUTE 0x80 + +/* + * register I9 -- interface configuration. + */ + +#define I9_PEN 0x01 /* playback enable */ +#define I9_CEN 0x02 /* capture enable */ + +/* + * values used in bd_flags + */ +#define BD_F_MCE_BIT 0x0001 +#define BD_F_IRQ_OK 0x0002 +#define BD_F_TMR_RUN 0x0004 + + +/* + * sound/ad1848_mixer.h + * + * Definitions for the mixer of AD1848 and compatible codecs. + * + * Copyright by Hannu Savolainen 1994 + * + * 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. + */ +/* + * The AD1848 codec has generic input lines called Line, Aux1 and Aux2. + * Soundcard manufacturers have connected actual inputs (CD, synth, line, + * etc) to these inputs in different order. Therefore it's difficult + * to assign mixer channels to to these inputs correctly. The following + * contains two alternative mappings. The first one is for GUS MAX and + * the second is just a generic one (line1, line2 and line3). + * (Actually this is not a mapping but rather some kind of interleaving + * solution). + */ +#define GUSMAX_MIXER +#ifdef GUSMAX_MIXER +#define MODE1_REC_DEVICES \ + (SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD|SOUND_MASK_IMIX) + +#define MODE1_MIXER_DEVICES \ + (SOUND_MASK_SYNTH | SOUND_MASK_MIC | SOUND_MASK_CD | \ + SOUND_MASK_IGAIN | SOUND_MASK_PCM|SOUND_MASK_IMIX) + +#define MODE2_MIXER_DEVICES \ + (SOUND_MASK_SYNTH | SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_CD | SOUND_MASK_SPEAKER | SOUND_MASK_IGAIN | \ + SOUND_MASK_PCM | SOUND_MASK_IMIX) + +#else /* Generic mapping */ + +#define MODE1_REC_DEVICES \ + (SOUND_MASK_LINE3 | SOUND_MASK_MIC | SOUND_MASK_LINE1|SOUND_MASK_IMIX) + +#define MODE1_MIXER_DEVICES \ + (SOUND_MASK_LINE1 | SOUND_MASK_MIC | SOUND_MASK_LINE2 | \ + SOUND_MASK_IGAIN | SOUND_MASK_PCM | SOUND_MASK_IMIX) + +#define MODE2_MIXER_DEVICES \ + (SOUND_MASK_LINE1 | SOUND_MASK_MIC | SOUND_MASK_LINE2 | \ + SOUND_MASK_LINE3 | SOUND_MASK_SPEAKER | \ + SOUND_MASK_IGAIN | SOUND_MASK_PCM | SOUND_MASK_IMIX) +#endif + +#define OPTI931_MIXER_DEVICES \ + (SOUND_MASK_VOLUME | SOUND_MASK_SYNTH | SOUND_MASK_PCM | \ + SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_IGAIN ) + +/* + * Most of the mixer entries work in backwards. Setting the polarity field + * makes them to work correctly. + * + * The channel numbering used by individual soundcards is not fixed. + * Some cards have assigned different meanings for the AUX1, AUX2 + * and LINE inputs. Some have different features... + * The current version doesn't try to compensate this. + * + */ + +mixer_ent mix_devices[32][2] = { /* As used in GUS MAX */ +MIX_ENT(SOUND_MIXER_VOLUME, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_BASS, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_TREBLE, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_SYNTH, 4, 1, 0, 5, 5, 1, 0, 5), +MIX_ENT(SOUND_MIXER_PCM, 6, 1, 0, 6, 7, 1, 0, 6), +MIX_ENT(SOUND_MIXER_SPEAKER, 26, 1, 0, 4, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_LINE, 18, 1, 0, 5, 19, 1, 0, 5), +MIX_ENT(SOUND_MIXER_MIC, 0, 0, 5, 1, 1, 0, 5, 1), +MIX_ENT(SOUND_MIXER_CD, 2, 1, 0, 5, 3, 1, 0, 5), +MIX_ENT(SOUND_MIXER_IMIX, 13, 1, 2, 6, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_ALTPCM, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_RECLEV, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_IGAIN, 0, 0, 0, 4, 1, 0, 0, 4), +MIX_ENT(SOUND_MIXER_OGAIN, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_LINE1, 2, 1, 0, 5, 3, 1, 0, 5), +MIX_ENT(SOUND_MIXER_LINE2, 4, 1, 0, 5, 5, 1, 0, 5), +MIX_ENT(SOUND_MIXER_LINE3, 18, 1, 0, 5, 19, 1, 0, 5) +}; + +mixer_ent opti931_devices[32][2] = { /* for the opti931 */ +MIX_ENT(SOUND_MIXER_VOLUME, 22, 1, 1, 5, 23, 1, 1, 5), +MIX_ENT(SOUND_MIXER_BASS, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_TREBLE, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_SYNTH, 4, 1, 1, 4, 5, 1, 1, 4), +MIX_ENT(SOUND_MIXER_PCM, 6, 1, 0, 5, 7, 1, 0, 5), +MIX_ENT(SOUND_MIXER_SPEAKER, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_LINE, 18, 1, 1, 4, 19, 1, 1, 4), +MIX_ENT(SOUND_MIXER_MIC, 0, 0, 5, 1, 1, 0, 5, 1), +MIX_ENT(SOUND_MIXER_CD, 2, 1, 1, 4, 3, 1, 1, 4), +MIX_ENT(SOUND_MIXER_IMIX, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_ALTPCM, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_RECLEV, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_IGAIN, 0, 0, 0, 4, 1, 0, 0, 4), +MIX_ENT(SOUND_MIXER_OGAIN, 0, 0, 0, 0, 0, 0, 0, 0), +MIX_ENT(SOUND_MIXER_LINE1, 2, 1, 0, 5, 3, 1, 0, 5), +MIX_ENT(SOUND_MIXER_LINE2, 4, 1, 0, 5, 5, 1, 0, 5), +MIX_ENT(SOUND_MIXER_LINE3, 18, 1, 0, 5, 19, 1, 0, 5) +}; + +static u_short default_mixer_levels[SOUND_MIXER_NRDEVICES] = { + 0x5a5a, /* Master Volume */ + 0x3232, /* Bass */ + 0x3232, /* Treble */ + 0x4b4b, /* FM */ + 0x4040, /* PCM */ + 0x4b4b, /* PC Speaker */ + 0x2020, /* Ext Line */ + 0x4040, /* Mic */ + 0x4b4b, /* CD */ + 0x0000, /* Recording monitor */ + 0x4b4b, /* SB PCM */ + 0x4b4b, /* Recording level */ + 0x2525, /* Input gain */ + 0x0000, /* Output gain */ + /* 0x4040, Line1 */ + 0x0000, /* Line1 */ + 0x0000, /* Line2 */ + 0x1515 /* Line3 (usually line in)*/ +}; + diff --git a/sys/dev/sound/isa/sb.c b/sys/dev/sound/isa/sb.c new file mode 100644 index 0000000..4ca1f1f --- /dev/null +++ b/sys/dev/sound/isa/sb.c @@ -0,0 +1,1080 @@ +/* + * sound/sb_dsp.c + * + * driver for the SoundBlaster and clones. + * + * Copyright 1997 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 sbintr; +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); + +/* + * 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, + + sbintr, + 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. + * + * Remebber, isa probe routines are supposed to return the + * size of io space used. + */ + +static int +sb_probe(struct isa_device *dev) +{ + if (dev->id_iobase == -1) { + dev->id_iobase = 0x220; + 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] ; + + sb_dsp_init(d, dev); + return 0 ; +} + +/* + * here are the main routines from the switches. + */ + +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->esel.si_pid = 0; + d->esel.si_flags = 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 ; + } + + d->flags |= SND_F_BUSY ; + d->play_speed = d->rec_speed = DSP_DEFAULT_SPEED ; + + if (flags & O_NONBLOCK) + d->flags |= SND_F_NBIO ; + + reset_dbuf(& (d->dbuf_in) ); + reset_dbuf(& (d->dbuf_out) ); + sb_reset_dsp(d->io_base); + 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, int 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. + */ + + return ENOSYS ; +} + +static void +sbintr(int unit) +{ + snddev_info *d = &pcm_info[unit]; + int reason = 3, c=1, io_base = d->io_base; + + DEB(printf("got sbintr for unit %d, flags 0x%08x\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. + */ +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->dma1 < 4) + reason |= 1; + if (d->dma2 < 4) + reason |= 2; + } + if ( c & 2 ) { /* 16-bit dma */ + if (d->dma1 >= 4) + reason |= 1; + if (d->dma2 >= 4) + reason |= 2; + } + } + if ( c & 2 ) + inb(DSP_DATA_AVL16); /* 16-bit int ack */ + if (c & 1) + inb(DSP_DATA_AVAIL); /* 8-bit int ack */ + +DEB(printf("sbintr, flags 0x%08x reason %d\n", d->flags, reason)); + if ( (d->flags & SND_F_WR_DMA) && (reason & 1) ) + dsp_wrintr(d); + if ( (d->flags & SND_F_RD_DMA) && (reason & 2) ) + dsp_rdintr(d); + + /* + * 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 done 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) +{ + u_long s ; + int rd = reason & SND_CB_RD ; + int l = (rd) ? d->dbuf_in.dl0 : d->dbuf_out.dl0 ; + + switch (reason & SND_CB_REASON_MASK) { + case SND_CB_INIT : /* called with int enabled and no pending io */ + dsp_speed(d); + snd_set_blocksize(d); + if (d->play_fmt & AFMT_MU_LAW) + d->flags |= SND_F_XLAT8 ; + else + d->flags &= ~SND_F_XLAT8 ; + return 1; + break ; + + case SND_CB_START : /* called with int disabled */ + sb_cmd(d->io_base, rd ? DSP_CMD_SPKOFF : DSP_CMD_SPKON); + d->flags &= ~SND_F_INIT ; + if (d->bd_flags & BD_F_SB16) { + /* the SB16 can do full duplex using one 16-bit channel + * and one 8-bit channel. It needs to be programmed to + * use split format though. + */ + int b16 ; + int swap = 0 ; + + b16 = (rd) ? d->rec_fmt : d->play_fmt ; + b16 = (b16 == AFMT_S16_LE) ? 1 : 0; + /* + * check if I have to swap dma channels. Swap if + * - !rd, dma1 <4, b16 + * - !rd, dma1 >=4, !b16 + * - rd, dma2 <4, b16 + * - rd, dma2 >=4, !b16 + */ + if (!rd) { + if ( (d->dma1 <4 && b16) || (d->dma1 >=4 && !b16) ) swap = 1; + } else { + if ( (d->dma2 <4 && b16) || (d->dma2 >=4 && !b16) ) swap = 1; + } + /* + * before swapping should make sure that there is no + * pending DMA on the other channel... + */ + if (swap) { + int c = d->dma2 ; + d->dma2 = d->dma1; + d->dma1 = c ; + } + DEB(printf("sb_init: play %d rec %d dma1 %d dma2 %d\n", + d->play_fmt, d->rec_fmt, d->dma1, d->dma2)); + } + /* fallthrough */ + case SND_CB_RESTART: + if (d->bd_flags & BD_F_SB16) { + u_char c, c1 ; + /* + * SB16 support still not completely working!!! + * + * in principle, on the SB16, I could support simultaneous + * play & rec. + * However, there is no way to ask explicitly for 8 or + * 16 bit transfer. As a consequence, if we do 8-bit, + * we need to use the 8-bit channel, and if we do 16-bit, + * we need to use the other one. The only way I find to + * do this is to swap d->dma1 and d->dma2 ... + * + */ + + if (rd) { + c = ((d->dma2 > 3) ? DSP_DMA16 : DSP_DMA8) | + DSP_F16_FIFO_ON | DSP_F16_ADC ; + c1 = (d->play_fmt == AFMT_U8) ? 0 : DSP_F16_SIGNED ; + if (d->play_fmt == AFMT_MU_LAW) c1 = 0 ; + if (d->rec_fmt == AFMT_S16_LE) + l /= 2 ; + } else { + c = ((d->dma1 > 3) ? DSP_DMA16 : DSP_DMA8) | + DSP_F16_FIFO_ON | DSP_F16_DAC ; + c1 = (d->rec_fmt == AFMT_U8) ? 0 : DSP_F16_SIGNED ; + if (d->play_fmt == AFMT_MU_LAW) c1 = 0 ; + if (d->play_fmt == AFMT_S16_LE) + l /= 2 ; + } + + 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 { + u_char c ; + if (d->bd_flags & BD_F_HISPEED) + c = (rd) ? DSP_CMD_HSADC : DSP_CMD_HSDAC ; + else + c = (rd) ? DSP_CMD_ADC8 : DSP_CMD_DAC8 ; + sb_cmd3(d->io_base, c , l - 1) ; + } + break; + + case SND_CB_STOP : + /* 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(DSP_RESET, 1); + DELAY(100); + outb(DSP_RESET, 0); + for (loopc = 0; loopc<100 && !(inb(DSP_DATA_AVAIL) & 0x80); loopc++) + DELAY(30); + + if (inb(DSP_READ) != 0xAA) { + DEB(printf("sb_reset_dsp failed\n")); + 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->dma2 = d->dma1 ; /* 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); + + sb_setmixer(io_base, DMA_NR, (1 << d->dma1) | (1 << d->dma2)); + break ; + + case 3 : + d->dma2 = d->dma1 ; /* 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; + } + } + } + + if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) + printf("Hmm... Could this be an ESS488 based card (rev %d)\n", + ess_minor & 0x0f); + else if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) + printf("Hmm... Could this be an ESS688 based card (rev %d)\n", + ess_minor & 0x0f); + } + + if (d->bd_flags & BD_F_JAZZ16) { + if (d->bd_flags & BD_F_JAZZ16_2) + fmt = "SoundMan Wave %d.%d"; + else + fmt = "MV Jazz16 %d.%d"; + d->audio_fmt |= AFMT_S16_LE; /* 16 bits */ + } + } + + 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 + */ + +int +sb_cmd(int io_base, u_char val) +{ + int i; + + for (i = 0; i < 1000 ; i++) { + if ((inb(DSP_STATUS) & 0x80) == 0) { + outb(DSP_COMMAND, 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; +} + +void +sb_setmixer(int io_base, u_int port, u_int value) +{ + u_long flags; + + flags = spltty(); + outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + DELAY(10); + outb(MIXER_DATA, (u_char) (value & 0xff)); + DELAY(10); + splx(flags); +} + +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 +sb_getmixer(int io_base, u_int port) +{ + int val; + u_long flags; + + flags = spltty(); + outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + DELAY(10); + val = inb(MIXER_DATA); + DELAY(10); + splx(flags); + + return val; +} + + +/* + * 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 ; + + 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 ; + } + /* + * only some models can do stereo, and only if not + * simultaneously using midi. + */ + if ( (d->bd_id & 0xff00) < 0x300 || d->bd_flags & BD_F_MIDIBUSY) + d->flags &= ~SND_F_STEREO; + + /* + * 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); + + /* + * Logitech SoundMan Games and Jazz16 cards can support 44.1kHz + * stereo + */ +#if !defined (SM_GAMES) + /* + * Max. stereo speed is 22050 + */ + if (d->flags & SND_F_STEREO && speed > 22050 && !(d->bd_flags & BD_F_JAZZ16)) + speed = 22050; +#endif + + if ((speed > 22050) && d->bd_flags & BD_F_MIDIBUSY) + speed = 22050; + + if (d->flags & SND_F_STEREO) + 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, DSP_CMD_TCONST, tconst); + 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, DSP_CMD_TCONST, tconst); + splx(flags); + + tmp = 256 - tconst; + speed = (1000000 + tmp / 2) / tmp; + } + + if (d->flags & SND_F_STEREO) + 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 outmask; + 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 *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 = sb_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[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_intr = pcmintr ; + dev->id_flags = 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 + * 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 ; + if (vend_id == 0x01009305) + s = "Avance Asound 100" ; + if (vend_id == 0x2b008c0e) + s = "SB16 Value PnP" ; + /* + * The SB16/AWE64 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 AWE64 PnP: 0x39008c0e 0x9d008c0e 0xc3008c0e + */ + if ( (vend_id & 0xffffff) == (0x9d008c0e & 0xffffff) ) + s = "SB AWE64 PnP"; + 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]; + 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; + snddev_last_probed->probe(dev); /* not really necessary but doesn't harm */ + + pcmattach(dev); +} +#endif /* NPNP */ + +#endif diff --git a/sys/dev/sound/isa/sb.h b/sys/dev/sound/isa/sb.h new file mode 100644 index 0000000..8548997 --- /dev/null +++ b/sys/dev/sound/isa/sb.h @@ -0,0 +1,387 @@ +/* + * file: sbcard.h + */ + +typedef struct _sbdev_info { + +} sbdev_info ; + +extern int sbc_major, sbc_minor ; +/* + * sound blaster registers + */ + +#define DSP_RESET (io_base + 0x6) +#define DSP_READ (io_base + 0xA) +#define DSP_WRITE (io_base + 0xC) +#define DSP_COMMAND (io_base + 0xC) +#define DSP_STATUS (io_base + 0xC) +#define DSP_DATA_AVAIL (io_base + 0xE) +#define DSP_DATA_AVL16 (io_base + 0xF) +#define MIXER_ADDR (io_base + 0x4) +#define MIXER_DATA (io_base + 0x5) +#define OPL3_LEFT (io_base + 0x0) +#define OPL3_RIGHT (io_base + 0x2) +#define OPL3_BOTH (io_base + 0x8) + +/* + * DSP Commands. There are many, and in many cases they are used explicitly + */ + +#define DSP_DAC8 0x10 /* direct DAC output */ +#define DSP_CMD_DAC8 0x14 /* single cycle 8-bit dma out */ +#define DSP_CMD_DAC2 0x16 /* 2-bit adpcm dma out (cont) */ +#define DSP_CMD_DAC2S 0x17 /* 2-bit adpcm dma out (start) */ + +#define DSP_CMD_DAC8_A 0x1c /* auto 8-bit dma out */ +#define DSP_CMD_DAC2S_A 0x1f /* auto 2-bit adpcm dma out (start) */ + +#define DSP_ADC8 0x20 /* direct ADC input */ + +#define DSP_CMD_ADC8 0x24 /* single cycle 8-bit dma in */ +#define DSP_CMD_ADC8_A 0x2c /* auto 8-bit dma out */ + +#define DSP_CMD_O16 0xb0 +#define DSP_CMD_I16 0xb8 +#define DSP_CMD_O8 0xc0 +#define DSP_CMD_I8 0xc8 + +#define DSP_MODE_U8MONO 0x00 +#define DSP_MODE_U8STEREO 0x20 +#define DSP_MODE_S16MONO 0x10 +#define DSP_MODE_S16STEREO 0x30 + +#define DSP_CMD_SPKON 0xD1 +#define DSP_CMD_SPKOFF 0xD3 +#define DSP_CMD_SPKR(on) (0xD1 | (on ? 0:2)) +#define DSP_CMD_DMAON 0xD0 /* ??? the comment says Halt DMA */ +#define DSP_CMD_DMAOFF 0xD4 /* ??? comment says continue dma */ + +#define DSP_CMD_DMAHALT 0xD0 +#define DSP_CMD_TCONST 0x40 /* set time constant */ +#define DSP_CMD_HSSIZE 0x48 /* high speed dma count */ +#define DSP_CMD_HSDAC 0x91 /* high speed dac */ +#define DSP_CMD_HSADC 0x99 /* high speed adc */ + +#define DSP_CMD_GETVER 0xE1 +#define DSP_CMD_GETID 0xE7 /* return id bytes */ + + /* prepare for dma input */ +#define DSP_CMD_DMAMODE(stereo, bit16) (0xA0 | (stereo ? 8:0) | (bit16 ? 4:0)) + +#define DSP_CMD_OUT16 0x41 /* send parms for dma out on sb16 */ +#define DSP_CMD_IN16 0x42 /* send parms for dma in on sb16 */ +#if 0 /*** unknown ***/ + /* + * D9 and D5 are used on the sb16 on close... maybe a reset of + * some subsystem ? + */ +#define DSP_CMD_D9 0xD9 +#define DSP_CMD_D5 0xD5 +#define DSP_CMD_FA 0xFA /* get version from prosonic*/ +#define DSP_CMD_FB 0xFB /* set irq/dma for prosonic*/ +#endif + +/* + * in fact, for the SB16, dma commands are as follows: + * + * cmd, mode, len_low, len_high. + * + * cmd is a combination of DSP_DMA16 or DSP_DMA8 and + */ + +#define DSP_DMA16 0xb0 +#define DSP_DMA8 0xc0 +# define DSP_F16_DAC 0x00 +# define DSP_F16_ADC 0x08 +# define DSP_F16_AUTO 0x04 +# define DSP_F16_FIFO_ON 0x02 + +/* + * mode is a combination of the following: + */ +#define DSP_F16_STEREO 0x20 +#define DSP_F16_SIGNED 0x10 + +#define IMODE_NONE 0 +#define IMODE_OUTPUT PCM_ENABLE_OUTPUT +#define IMODE_INPUT PCM_ENABLE_INPUT +#define IMODE_INIT 3 +#define IMODE_MIDI 4 + +#define NORMAL_MIDI 0 +#define UART_MIDI 1 + +/* + * values used for bd_flags in SoundBlaster driver + */ +#define BD_F_HISPEED 0x0001 /* doing high speed ... */ + +#define BD_F_JAZZ16 0x0002 /* jazz16 detected */ +#define BD_F_JAZZ16_2 0x0004 /* jazz16 type 2 */ + +#define BD_F_DUP_MIDI 0x0008 /* duplex midi */ + +#define BD_F_MIX_MASK 0x0070 /* up to 8 mixers (I know of 3) */ +#define BD_F_MIX_CT1335 0x0010 /* CT1335 */ +#define BD_F_MIX_CT1345 0x0020 /* CT1345 */ +#define BD_F_MIX_CT1745 0x0030 /* CT1745 */ + +#define BD_F_SB16 0x0100 /* this is a SB16 */ +#define BD_F_NOREC 0x0200 /* recording not supported on this board */ +#define BD_F_MIDIBUSY 0x0400 /* midi busy */ + + +/* + * sound/sb_mixer.h + * + * Definitions for the SB Pro and SB16 mixers + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * Modified: Hunyue Yau Jan 6 1994 Added defines for the Sound Galaxy NX Pro + * mixer. + * + */ + +#define SBPRO_RECORDING_DEVICES \ + (SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD) + +/* Same as SB Pro, unless I find otherwise */ +#define SGNXPRO_RECORDING_DEVICES SBPRO_RECORDING_DEVICES + +#define SBPRO_MIXER_DEVICES \ + (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_CD | SOUND_MASK_VOLUME) + +/* + * SG NX Pro has treble and bass settings on the mixer. The 'speaker' channel + * is the COVOX/DisneySoundSource emulation volume control on the mixer. It + * does NOT control speaker volume. Should have own mask eventually? + */ +#define SGNXPRO_MIXER_DEVICES \ + (SBPRO_MIXER_DEVICES | SOUND_MASK_BASS | \ + SOUND_MASK_TREBLE | SOUND_MASK_SPEAKER ) + +#define SB16_RECORDING_DEVICES \ + (SOUND_MASK_SYNTH | SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD) + +#define SB16_MIXER_DEVICES \ + (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | \ + SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | \ + SOUND_MASK_IGAIN | SOUND_MASK_OGAIN | \ + SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE) + +/* + * Mixer registers + * + * NOTE! RECORD_SRC == IN_FILTER + */ + +/* + * Mixer registers of SB Pro + */ +#define VOC_VOL 0x04 +#define MIC_VOL 0x0A +#define MIC_MIX 0x0A +#define RECORD_SRC 0x0C +#define IN_FILTER 0x0C +#define OUT_FILTER 0x0E +#define MASTER_VOL 0x22 +#define FM_VOL 0x26 +#define CD_VOL 0x28 +#define LINE_VOL 0x2E +#define IRQ_NR 0x80 +#define DMA_NR 0x81 +#define IRQ_STAT 0x82 + +/* + * Additional registers on the SG NX Pro + */ +#define COVOX_VOL 0x42 +#define TREBLE_LVL 0x44 +#define BASS_LVL 0x46 + +#define FREQ_HI (1 << 3)/* Use High-frequency ANFI filters */ +#define FREQ_LOW 0 /* Use Low-frequency ANFI filters */ +#define FILT_ON 0 /* Yes, 0 to turn it on, 1 for off */ +#define FILT_OFF (1 << 5) + +#define MONO_DAC 0x00 +#define STEREO_DAC 0x02 + +/* + * Mixer registers of SB16 + */ +#define SB16_IMASK_L 0x3d +#define SB16_IMASK_R 0x3e +#define SB16_OMASK 0x3c + + +#ifndef __SB_MIXER_C__ +mixer_tab sbpro_mix; +mixer_tab sb16_mix; +#ifdef __SGNXPRO__ +mixer_tab sgnxpro_mix; +#endif +static u_char sb16_recmasks_L[SOUND_MIXER_NRDEVICES]; +static u_char sb16_recmasks_R[SOUND_MIXER_NRDEVICES]; +#else /* __SB_MIXER_C__ defined */ +mixer_tab sbpro_mix = { + PMIX_ENT(SOUND_MIXER_VOLUME, 0x22, 7, 4, 0x22, 3, 4), + PMIX_ENT(SOUND_MIXER_BASS, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_TREBLE, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_SYNTH, 0x26, 7, 4, 0x26, 3, 4), + PMIX_ENT(SOUND_MIXER_PCM, 0x04, 7, 4, 0x04, 3, 4), + PMIX_ENT(SOUND_MIXER_SPEAKER, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_LINE, 0x2e, 7, 4, 0x2e, 3, 4), + PMIX_ENT(SOUND_MIXER_MIC, 0x0a, 2, 3, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_CD, 0x28, 7, 4, 0x28, 3, 4), + PMIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_RECLEV, 0x00, 0, 0, 0x00, 0, 0) +}; + +#ifdef __SGNXPRO__ +mixer_tab sgnxpro_mix = { + PMIX_ENT(SOUND_MIXER_VOLUME, 0x22, 7, 4, 0x22, 3, 4), + PMIX_ENT(SOUND_MIXER_BASS, 0x46, 2, 3, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_TREBLE, 0x44, 2, 3, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_SYNTH, 0x26, 7, 4, 0x26, 3, 4), + PMIX_ENT(SOUND_MIXER_PCM, 0x04, 7, 4, 0x04, 3, 4), + PMIX_ENT(SOUND_MIXER_SPEAKER, 0x42, 2, 3, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_LINE, 0x2e, 7, 4, 0x2e, 3, 4), + PMIX_ENT(SOUND_MIXER_MIC, 0x0a, 2, 3, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_CD, 0x28, 7, 4, 0x28, 3, 4), + PMIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_RECLEV, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_IGAIN, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_OGAIN, 0x00, 0, 0, 0x00, 0, 0) +}; +#endif + +mixer_tab sb16_mix = { + PMIX_ENT(SOUND_MIXER_VOLUME, 0x30, 3, 5, 0x31, 3, 5), + PMIX_ENT(SOUND_MIXER_BASS, 0x46, 4, 4, 0x47, 4, 4), + PMIX_ENT(SOUND_MIXER_TREBLE, 0x44, 4, 4, 0x45, 4, 4), + PMIX_ENT(SOUND_MIXER_SYNTH, 0x34, 3, 5, 0x35, 3, 5), + PMIX_ENT(SOUND_MIXER_PCM, 0x32, 3, 5, 0x33, 3, 5), + PMIX_ENT(SOUND_MIXER_SPEAKER, 0x3b, 6, 2, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_LINE, 0x38, 3, 5, 0x39, 3, 5), + PMIX_ENT(SOUND_MIXER_MIC, 0x3a, 3, 5, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_CD, 0x36, 3, 5, 0x37, 3, 5), + PMIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0), + PMIX_ENT(SOUND_MIXER_RECLEV, 0x3f, 6, 2, 0x40, 6, 2), /* Obsol,Use IGAIN*/ + PMIX_ENT(SOUND_MIXER_IGAIN, 0x3f, 6, 2, 0x40, 6, 2), + PMIX_ENT(SOUND_MIXER_OGAIN, 0x41, 6, 2, 0x42, 6, 2) +}; + +#ifdef SM_GAMES /* Master volume is lower and PCM & FM + * volumes higher than with SB Pro. This + * improves the sound quality */ + +static u_short levels[SOUND_MIXER_NRDEVICES] = +{ + 0x2020, /* Master Volume */ + 0x4b4b, /* Bass */ + 0x4b4b, /* Treble */ + 0x6464, /* FM */ + 0x6464, /* PCM */ + 0x4b4b, /* PC Speaker */ + 0x4b4b, /* Ext Line */ + 0x0000, /* Mic */ + 0x4b4b, /* CD */ + 0x4b4b, /* Recording monitor */ + 0x4b4b, /* SB PCM */ + 0x4b4b, /* Recording level */ + 0x4b4b, /* Input gain */ +0x4b4b}; /* Output gain */ + +#else /* If the user selected just plain SB Pro */ + +static u_short levels[SOUND_MIXER_NRDEVICES] = +{ + 0x5a5a, /* Master Volume */ + 0x4b4b, /* Bass */ + 0x4b4b, /* Treble */ + 0x4b4b, /* FM */ + 0x4b4b, /* PCM */ + 0x4b4b, /* PC Speaker */ + 0x4b4b, /* Ext Line */ + 0x1010, /* Mic */ + 0x4b4b, /* CD */ + 0x4b4b, /* Recording monitor */ + 0x4b4b, /* SB PCM */ + 0x4b4b, /* Recording level */ + 0x4b4b, /* Input gain */ +0x4b4b}; /* Output gain */ +#endif /* SM_GAMES */ + +static u_char sb16_recmasks_L[SOUND_MIXER_NRDEVICES] = +{ + 0x00, /* SOUND_MIXER_VOLUME */ + 0x00, /* SOUND_MIXER_BASS */ + 0x00, /* SOUND_MIXER_TREBLE */ + 0x40, /* SOUND_MIXER_SYNTH */ + 0x00, /* SOUND_MIXER_PCM */ + 0x00, /* SOUND_MIXER_SPEAKER */ + 0x10, /* SOUND_MIXER_LINE */ + 0x01, /* SOUND_MIXER_MIC */ + 0x04, /* SOUND_MIXER_CD */ + 0x00, /* SOUND_MIXER_IMIX */ + 0x00, /* SOUND_MIXER_ALTPCM */ + 0x00, /* SOUND_MIXER_RECLEV */ + 0x00, /* SOUND_MIXER_IGAIN */ + 0x00 /* SOUND_MIXER_OGAIN */ +}; + +static u_char sb16_recmasks_R[SOUND_MIXER_NRDEVICES] = +{ + 0x00, /* SOUND_MIXER_VOLUME */ + 0x00, /* SOUND_MIXER_BASS */ + 0x00, /* SOUND_MIXER_TREBLE */ + 0x20, /* SOUND_MIXER_SYNTH */ + 0x00, /* SOUND_MIXER_PCM */ + 0x00, /* SOUND_MIXER_SPEAKER */ + 0x08, /* SOUND_MIXER_LINE */ + 0x01, /* SOUND_MIXER_MIC */ + 0x02, /* SOUND_MIXER_CD */ + 0x00, /* SOUND_MIXER_IMIX */ + 0x00, /* SOUND_MIXER_ALTPCM */ + 0x00, /* SOUND_MIXER_RECLEV */ + 0x00, /* SOUND_MIXER_IGAIN */ + 0x00 /* SOUND_MIXER_OGAIN */ +}; + +/* + * Recording sources (SB Pro) + */ +#endif /* __SB_MIXER_C__ */ + +#define SRC_MIC 1 /* Select Microphone recording source */ +#define SRC_CD 3 /* Select CD recording source */ +#define SRC_LINE 7 /* Use Line-in for recording source */ + + diff --git a/sys/dev/sound/isa/sb16.c b/sys/dev/sound/isa/sb16.c new file mode 100644 index 0000000..4ca1f1f --- /dev/null +++ b/sys/dev/sound/isa/sb16.c @@ -0,0 +1,1080 @@ +/* + * sound/sb_dsp.c + * + * driver for the SoundBlaster and clones. + * + * Copyright 1997 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 sbintr; +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); + +/* + * 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, + + sbintr, + 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. + * + * Remebber, isa probe routines are supposed to return the + * size of io space used. + */ + +static int +sb_probe(struct isa_device *dev) +{ + if (dev->id_iobase == -1) { + dev->id_iobase = 0x220; + 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] ; + + sb_dsp_init(d, dev); + return 0 ; +} + +/* + * here are the main routines from the switches. + */ + +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->esel.si_pid = 0; + d->esel.si_flags = 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 ; + } + + d->flags |= SND_F_BUSY ; + d->play_speed = d->rec_speed = DSP_DEFAULT_SPEED ; + + if (flags & O_NONBLOCK) + d->flags |= SND_F_NBIO ; + + reset_dbuf(& (d->dbuf_in) ); + reset_dbuf(& (d->dbuf_out) ); + sb_reset_dsp(d->io_base); + 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, int 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. + */ + + return ENOSYS ; +} + +static void +sbintr(int unit) +{ + snddev_info *d = &pcm_info[unit]; + int reason = 3, c=1, io_base = d->io_base; + + DEB(printf("got sbintr for unit %d, flags 0x%08x\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. + */ +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->dma1 < 4) + reason |= 1; + if (d->dma2 < 4) + reason |= 2; + } + if ( c & 2 ) { /* 16-bit dma */ + if (d->dma1 >= 4) + reason |= 1; + if (d->dma2 >= 4) + reason |= 2; + } + } + if ( c & 2 ) + inb(DSP_DATA_AVL16); /* 16-bit int ack */ + if (c & 1) + inb(DSP_DATA_AVAIL); /* 8-bit int ack */ + +DEB(printf("sbintr, flags 0x%08x reason %d\n", d->flags, reason)); + if ( (d->flags & SND_F_WR_DMA) && (reason & 1) ) + dsp_wrintr(d); + if ( (d->flags & SND_F_RD_DMA) && (reason & 2) ) + dsp_rdintr(d); + + /* + * 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 done 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) +{ + u_long s ; + int rd = reason & SND_CB_RD ; + int l = (rd) ? d->dbuf_in.dl0 : d->dbuf_out.dl0 ; + + switch (reason & SND_CB_REASON_MASK) { + case SND_CB_INIT : /* called with int enabled and no pending io */ + dsp_speed(d); + snd_set_blocksize(d); + if (d->play_fmt & AFMT_MU_LAW) + d->flags |= SND_F_XLAT8 ; + else + d->flags &= ~SND_F_XLAT8 ; + return 1; + break ; + + case SND_CB_START : /* called with int disabled */ + sb_cmd(d->io_base, rd ? DSP_CMD_SPKOFF : DSP_CMD_SPKON); + d->flags &= ~SND_F_INIT ; + if (d->bd_flags & BD_F_SB16) { + /* the SB16 can do full duplex using one 16-bit channel + * and one 8-bit channel. It needs to be programmed to + * use split format though. + */ + int b16 ; + int swap = 0 ; + + b16 = (rd) ? d->rec_fmt : d->play_fmt ; + b16 = (b16 == AFMT_S16_LE) ? 1 : 0; + /* + * check if I have to swap dma channels. Swap if + * - !rd, dma1 <4, b16 + * - !rd, dma1 >=4, !b16 + * - rd, dma2 <4, b16 + * - rd, dma2 >=4, !b16 + */ + if (!rd) { + if ( (d->dma1 <4 && b16) || (d->dma1 >=4 && !b16) ) swap = 1; + } else { + if ( (d->dma2 <4 && b16) || (d->dma2 >=4 && !b16) ) swap = 1; + } + /* + * before swapping should make sure that there is no + * pending DMA on the other channel... + */ + if (swap) { + int c = d->dma2 ; + d->dma2 = d->dma1; + d->dma1 = c ; + } + DEB(printf("sb_init: play %d rec %d dma1 %d dma2 %d\n", + d->play_fmt, d->rec_fmt, d->dma1, d->dma2)); + } + /* fallthrough */ + case SND_CB_RESTART: + if (d->bd_flags & BD_F_SB16) { + u_char c, c1 ; + /* + * SB16 support still not completely working!!! + * + * in principle, on the SB16, I could support simultaneous + * play & rec. + * However, there is no way to ask explicitly for 8 or + * 16 bit transfer. As a consequence, if we do 8-bit, + * we need to use the 8-bit channel, and if we do 16-bit, + * we need to use the other one. The only way I find to + * do this is to swap d->dma1 and d->dma2 ... + * + */ + + if (rd) { + c = ((d->dma2 > 3) ? DSP_DMA16 : DSP_DMA8) | + DSP_F16_FIFO_ON | DSP_F16_ADC ; + c1 = (d->play_fmt == AFMT_U8) ? 0 : DSP_F16_SIGNED ; + if (d->play_fmt == AFMT_MU_LAW) c1 = 0 ; + if (d->rec_fmt == AFMT_S16_LE) + l /= 2 ; + } else { + c = ((d->dma1 > 3) ? DSP_DMA16 : DSP_DMA8) | + DSP_F16_FIFO_ON | DSP_F16_DAC ; + c1 = (d->rec_fmt == AFMT_U8) ? 0 : DSP_F16_SIGNED ; + if (d->play_fmt == AFMT_MU_LAW) c1 = 0 ; + if (d->play_fmt == AFMT_S16_LE) + l /= 2 ; + } + + 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 { + u_char c ; + if (d->bd_flags & BD_F_HISPEED) + c = (rd) ? DSP_CMD_HSADC : DSP_CMD_HSDAC ; + else + c = (rd) ? DSP_CMD_ADC8 : DSP_CMD_DAC8 ; + sb_cmd3(d->io_base, c , l - 1) ; + } + break; + + case SND_CB_STOP : + /* 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(DSP_RESET, 1); + DELAY(100); + outb(DSP_RESET, 0); + for (loopc = 0; loopc<100 && !(inb(DSP_DATA_AVAIL) & 0x80); loopc++) + DELAY(30); + + if (inb(DSP_READ) != 0xAA) { + DEB(printf("sb_reset_dsp failed\n")); + 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->dma2 = d->dma1 ; /* 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); + + sb_setmixer(io_base, DMA_NR, (1 << d->dma1) | (1 << d->dma2)); + break ; + + case 3 : + d->dma2 = d->dma1 ; /* 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; + } + } + } + + if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) + printf("Hmm... Could this be an ESS488 based card (rev %d)\n", + ess_minor & 0x0f); + else if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) + printf("Hmm... Could this be an ESS688 based card (rev %d)\n", + ess_minor & 0x0f); + } + + if (d->bd_flags & BD_F_JAZZ16) { + if (d->bd_flags & BD_F_JAZZ16_2) + fmt = "SoundMan Wave %d.%d"; + else + fmt = "MV Jazz16 %d.%d"; + d->audio_fmt |= AFMT_S16_LE; /* 16 bits */ + } + } + + 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 + */ + +int +sb_cmd(int io_base, u_char val) +{ + int i; + + for (i = 0; i < 1000 ; i++) { + if ((inb(DSP_STATUS) & 0x80) == 0) { + outb(DSP_COMMAND, 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; +} + +void +sb_setmixer(int io_base, u_int port, u_int value) +{ + u_long flags; + + flags = spltty(); + outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + DELAY(10); + outb(MIXER_DATA, (u_char) (value & 0xff)); + DELAY(10); + splx(flags); +} + +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 +sb_getmixer(int io_base, u_int port) +{ + int val; + u_long flags; + + flags = spltty(); + outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + DELAY(10); + val = inb(MIXER_DATA); + DELAY(10); + splx(flags); + + return val; +} + + +/* + * 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 ; + + 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 ; + } + /* + * only some models can do stereo, and only if not + * simultaneously using midi. + */ + if ( (d->bd_id & 0xff00) < 0x300 || d->bd_flags & BD_F_MIDIBUSY) + d->flags &= ~SND_F_STEREO; + + /* + * 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); + + /* + * Logitech SoundMan Games and Jazz16 cards can support 44.1kHz + * stereo + */ +#if !defined (SM_GAMES) + /* + * Max. stereo speed is 22050 + */ + if (d->flags & SND_F_STEREO && speed > 22050 && !(d->bd_flags & BD_F_JAZZ16)) + speed = 22050; +#endif + + if ((speed > 22050) && d->bd_flags & BD_F_MIDIBUSY) + speed = 22050; + + if (d->flags & SND_F_STEREO) + 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, DSP_CMD_TCONST, tconst); + 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, DSP_CMD_TCONST, tconst); + splx(flags); + + tmp = 256 - tconst; + speed = (1000000 + tmp / 2) / tmp; + } + + if (d->flags & SND_F_STEREO) + 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 outmask; + 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 *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 = sb_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[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_intr = pcmintr ; + dev->id_flags = 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 + * 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 ; + if (vend_id == 0x01009305) + s = "Avance Asound 100" ; + if (vend_id == 0x2b008c0e) + s = "SB16 Value PnP" ; + /* + * The SB16/AWE64 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 AWE64 PnP: 0x39008c0e 0x9d008c0e 0xc3008c0e + */ + if ( (vend_id & 0xffffff) == (0x9d008c0e & 0xffffff) ) + s = "SB AWE64 PnP"; + 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]; + 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; + snddev_last_probed->probe(dev); /* not really necessary but doesn't harm */ + + pcmattach(dev); +} +#endif /* NPNP */ + +#endif diff --git a/sys/dev/sound/isa/sb8.c b/sys/dev/sound/isa/sb8.c new file mode 100644 index 0000000..4ca1f1f --- /dev/null +++ b/sys/dev/sound/isa/sb8.c @@ -0,0 +1,1080 @@ +/* + * sound/sb_dsp.c + * + * driver for the SoundBlaster and clones. + * + * Copyright 1997 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 sbintr; +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); + +/* + * 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, + + sbintr, + 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. + * + * Remebber, isa probe routines are supposed to return the + * size of io space used. + */ + +static int +sb_probe(struct isa_device *dev) +{ + if (dev->id_iobase == -1) { + dev->id_iobase = 0x220; + 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] ; + + sb_dsp_init(d, dev); + return 0 ; +} + +/* + * here are the main routines from the switches. + */ + +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->esel.si_pid = 0; + d->esel.si_flags = 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 ; + } + + d->flags |= SND_F_BUSY ; + d->play_speed = d->rec_speed = DSP_DEFAULT_SPEED ; + + if (flags & O_NONBLOCK) + d->flags |= SND_F_NBIO ; + + reset_dbuf(& (d->dbuf_in) ); + reset_dbuf(& (d->dbuf_out) ); + sb_reset_dsp(d->io_base); + 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, int 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. + */ + + return ENOSYS ; +} + +static void +sbintr(int unit) +{ + snddev_info *d = &pcm_info[unit]; + int reason = 3, c=1, io_base = d->io_base; + + DEB(printf("got sbintr for unit %d, flags 0x%08x\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. + */ +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->dma1 < 4) + reason |= 1; + if (d->dma2 < 4) + reason |= 2; + } + if ( c & 2 ) { /* 16-bit dma */ + if (d->dma1 >= 4) + reason |= 1; + if (d->dma2 >= 4) + reason |= 2; + } + } + if ( c & 2 ) + inb(DSP_DATA_AVL16); /* 16-bit int ack */ + if (c & 1) + inb(DSP_DATA_AVAIL); /* 8-bit int ack */ + +DEB(printf("sbintr, flags 0x%08x reason %d\n", d->flags, reason)); + if ( (d->flags & SND_F_WR_DMA) && (reason & 1) ) + dsp_wrintr(d); + if ( (d->flags & SND_F_RD_DMA) && (reason & 2) ) + dsp_rdintr(d); + + /* + * 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 done 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) +{ + u_long s ; + int rd = reason & SND_CB_RD ; + int l = (rd) ? d->dbuf_in.dl0 : d->dbuf_out.dl0 ; + + switch (reason & SND_CB_REASON_MASK) { + case SND_CB_INIT : /* called with int enabled and no pending io */ + dsp_speed(d); + snd_set_blocksize(d); + if (d->play_fmt & AFMT_MU_LAW) + d->flags |= SND_F_XLAT8 ; + else + d->flags &= ~SND_F_XLAT8 ; + return 1; + break ; + + case SND_CB_START : /* called with int disabled */ + sb_cmd(d->io_base, rd ? DSP_CMD_SPKOFF : DSP_CMD_SPKON); + d->flags &= ~SND_F_INIT ; + if (d->bd_flags & BD_F_SB16) { + /* the SB16 can do full duplex using one 16-bit channel + * and one 8-bit channel. It needs to be programmed to + * use split format though. + */ + int b16 ; + int swap = 0 ; + + b16 = (rd) ? d->rec_fmt : d->play_fmt ; + b16 = (b16 == AFMT_S16_LE) ? 1 : 0; + /* + * check if I have to swap dma channels. Swap if + * - !rd, dma1 <4, b16 + * - !rd, dma1 >=4, !b16 + * - rd, dma2 <4, b16 + * - rd, dma2 >=4, !b16 + */ + if (!rd) { + if ( (d->dma1 <4 && b16) || (d->dma1 >=4 && !b16) ) swap = 1; + } else { + if ( (d->dma2 <4 && b16) || (d->dma2 >=4 && !b16) ) swap = 1; + } + /* + * before swapping should make sure that there is no + * pending DMA on the other channel... + */ + if (swap) { + int c = d->dma2 ; + d->dma2 = d->dma1; + d->dma1 = c ; + } + DEB(printf("sb_init: play %d rec %d dma1 %d dma2 %d\n", + d->play_fmt, d->rec_fmt, d->dma1, d->dma2)); + } + /* fallthrough */ + case SND_CB_RESTART: + if (d->bd_flags & BD_F_SB16) { + u_char c, c1 ; + /* + * SB16 support still not completely working!!! + * + * in principle, on the SB16, I could support simultaneous + * play & rec. + * However, there is no way to ask explicitly for 8 or + * 16 bit transfer. As a consequence, if we do 8-bit, + * we need to use the 8-bit channel, and if we do 16-bit, + * we need to use the other one. The only way I find to + * do this is to swap d->dma1 and d->dma2 ... + * + */ + + if (rd) { + c = ((d->dma2 > 3) ? DSP_DMA16 : DSP_DMA8) | + DSP_F16_FIFO_ON | DSP_F16_ADC ; + c1 = (d->play_fmt == AFMT_U8) ? 0 : DSP_F16_SIGNED ; + if (d->play_fmt == AFMT_MU_LAW) c1 = 0 ; + if (d->rec_fmt == AFMT_S16_LE) + l /= 2 ; + } else { + c = ((d->dma1 > 3) ? DSP_DMA16 : DSP_DMA8) | + DSP_F16_FIFO_ON | DSP_F16_DAC ; + c1 = (d->rec_fmt == AFMT_U8) ? 0 : DSP_F16_SIGNED ; + if (d->play_fmt == AFMT_MU_LAW) c1 = 0 ; + if (d->play_fmt == AFMT_S16_LE) + l /= 2 ; + } + + 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 { + u_char c ; + if (d->bd_flags & BD_F_HISPEED) + c = (rd) ? DSP_CMD_HSADC : DSP_CMD_HSDAC ; + else + c = (rd) ? DSP_CMD_ADC8 : DSP_CMD_DAC8 ; + sb_cmd3(d->io_base, c , l - 1) ; + } + break; + + case SND_CB_STOP : + /* 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(DSP_RESET, 1); + DELAY(100); + outb(DSP_RESET, 0); + for (loopc = 0; loopc<100 && !(inb(DSP_DATA_AVAIL) & 0x80); loopc++) + DELAY(30); + + if (inb(DSP_READ) != 0xAA) { + DEB(printf("sb_reset_dsp failed\n")); + 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->dma2 = d->dma1 ; /* 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); + + sb_setmixer(io_base, DMA_NR, (1 << d->dma1) | (1 << d->dma2)); + break ; + + case 3 : + d->dma2 = d->dma1 ; /* 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; + } + } + } + + if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) + printf("Hmm... Could this be an ESS488 based card (rev %d)\n", + ess_minor & 0x0f); + else if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) + printf("Hmm... Could this be an ESS688 based card (rev %d)\n", + ess_minor & 0x0f); + } + + if (d->bd_flags & BD_F_JAZZ16) { + if (d->bd_flags & BD_F_JAZZ16_2) + fmt = "SoundMan Wave %d.%d"; + else + fmt = "MV Jazz16 %d.%d"; + d->audio_fmt |= AFMT_S16_LE; /* 16 bits */ + } + } + + 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 + */ + +int +sb_cmd(int io_base, u_char val) +{ + int i; + + for (i = 0; i < 1000 ; i++) { + if ((inb(DSP_STATUS) & 0x80) == 0) { + outb(DSP_COMMAND, 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; +} + +void +sb_setmixer(int io_base, u_int port, u_int value) +{ + u_long flags; + + flags = spltty(); + outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + DELAY(10); + outb(MIXER_DATA, (u_char) (value & 0xff)); + DELAY(10); + splx(flags); +} + +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 +sb_getmixer(int io_base, u_int port) +{ + int val; + u_long flags; + + flags = spltty(); + outb(MIXER_ADDR, (u_char) (port & 0xff)); /* Select register */ + DELAY(10); + val = inb(MIXER_DATA); + DELAY(10); + splx(flags); + + return val; +} + + +/* + * 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 ; + + 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 ; + } + /* + * only some models can do stereo, and only if not + * simultaneously using midi. + */ + if ( (d->bd_id & 0xff00) < 0x300 || d->bd_flags & BD_F_MIDIBUSY) + d->flags &= ~SND_F_STEREO; + + /* + * 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); + + /* + * Logitech SoundMan Games and Jazz16 cards can support 44.1kHz + * stereo + */ +#if !defined (SM_GAMES) + /* + * Max. stereo speed is 22050 + */ + if (d->flags & SND_F_STEREO && speed > 22050 && !(d->bd_flags & BD_F_JAZZ16)) + speed = 22050; +#endif + + if ((speed > 22050) && d->bd_flags & BD_F_MIDIBUSY) + speed = 22050; + + if (d->flags & SND_F_STEREO) + 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, DSP_CMD_TCONST, tconst); + 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, DSP_CMD_TCONST, tconst); + splx(flags); + + tmp = 256 - tconst; + speed = (1000000 + tmp / 2) / tmp; + } + + if (d->flags & SND_F_STEREO) + 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 outmask; + 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 *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 = sb_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[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_intr = pcmintr ; + dev->id_flags = 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 + * 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 ; + if (vend_id == 0x01009305) + s = "Avance Asound 100" ; + if (vend_id == 0x2b008c0e) + s = "SB16 Value PnP" ; + /* + * The SB16/AWE64 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 AWE64 PnP: 0x39008c0e 0x9d008c0e 0xc3008c0e + */ + if ( (vend_id & 0xffffff) == (0x9d008c0e & 0xffffff) ) + s = "SB AWE64 PnP"; + 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]; + 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; + snddev_last_probed->probe(dev); /* not really necessary but doesn't harm */ + + pcmattach(dev); +} +#endif /* NPNP */ + +#endif |