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 | d77c6d8be9c8cc0c98327f4e3d573575fb1e8550 (patch) | |
tree | ef367537ba76248741b9455557d3062857d71850 /sys/i386/isa/snd | |
download | FreeBSD-src-d77c6d8be9c8cc0c98327f4e3d573575fb1e8550.zip FreeBSD-src-d77c6d8be9c8cc0c98327f4e3d573575fb1e8550.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>
Diffstat (limited to 'sys/i386/isa/snd')
-rw-r--r-- | sys/i386/isa/snd/README | 78 | ||||
-rw-r--r-- | sys/i386/isa/snd/ad1848.c | 1509 | ||||
-rw-r--r-- | sys/i386/isa/snd/clones.c | 262 | ||||
-rw-r--r-- | sys/i386/isa/snd/dmabuf.c | 864 | ||||
-rw-r--r-- | sys/i386/isa/snd/dmabuf_auto.c | 802 | ||||
-rw-r--r-- | sys/i386/isa/snd/mss.h | 250 | ||||
-rw-r--r-- | sys/i386/isa/snd/sb_dsp.c | 1080 | ||||
-rw-r--r-- | sys/i386/isa/snd/sbcard.h | 387 | ||||
-rw-r--r-- | sys/i386/isa/snd/sound.c | 1254 | ||||
-rw-r--r-- | sys/i386/isa/snd/sound.h | 492 | ||||
-rw-r--r-- | sys/i386/isa/snd/soundcard.h | 1297 | ||||
-rw-r--r-- | sys/i386/isa/snd/ulaw.h | 93 |
12 files changed, 8368 insertions, 0 deletions
diff --git a/sys/i386/isa/snd/README b/sys/i386/isa/snd/README new file mode 100644 index 0000000..70e3c11 --- /dev/null +++ b/sys/i386/isa/snd/README @@ -0,0 +1,78 @@ + --- A new FreeBSD sound driver --- + by Luigi Rizzo (luigi@iet.unipi.it) + + +This is an experimental version of the sound driver for FreeBSD. +I have almost completely rewritten the main parts of the code, +starting from the Voxware 3.5-alpha release with patches from +Amancio Hasty. The only file which is largely similar to the original +ones is "soundcard.h" (for compatibility reasons, since it contains +the definition of all the ioctls). + +Visit http://www.iet.unipi.it/~luigi/FreeBSD.html for the latest +information on the drivers. There you can obtain the latest source +package that includes documentation on the design of the driver. + + +CARD SUPPORT INFORMATION: + +For PnP cards, I also include the vendor_id and serial numbers of +cards I have encountered. + +CS4236: PnP id 0x3642630e + + works like a charm. All modes, including full duplex, supported in + MSS mode. + +CS4237: PnP id 0x3742630e + + I had early reports of success with this board, which is almost + the same as the CS4236. + +CS4232: PnP id 0x3242630e + + this chip is reported as broken in the OSS documentation. As a + matter of fact, on my Intel Zappa motherboard, I have problems in + make it use the secondary DMA channel. I have it working in + half duplex (both capture and playback) in SB3.2 emulation, + and working in playback mode in MSS emulation. + +OPTi931: PnP id 0x3109143e + + The data sheets of this chip are very cryptic. I have it working + in full duplex in all modes _except_ capture of uLAW/ALAW data. + I am strongly convinced of a bug in the chip. I have sent email + to OPTI but got no reply so far. In SB emulation mode the + driver does not work yet (maybe I do not initialize it the + right way). + + Another bug seems to affect full duplex operation -- it appears + that at times DMA transfer are requested but not counted by + the device. In normal DMA mode this causes deadlocks. The only + solution I have found is to fetch the count from the ISA DMA + registers, but this does not seem to work very well either. + +SB16 PnP: PnP id 0xXX008c0e + + There are many such cards (plain SB16 PnP, AWE32, AWE64, Vibra16, + etc. all differing in the PnP id (and with different synthesis + devices, which we do not support anyways). + + Since 970903 the driver supports them all, both capture and + playback, 8 and 16 bits. + +GusPnP: PnP id 0x0100561e + + I have code to recognize the board as MSS, but have not tested it + since I don't own the board. Hopefully someone will test it soon. + +OPTI925: PnP id 0x2509143e + + there is code to recognize it as a SB clone. I have reports that + it probes ok, but not sure if it works. + +OPTI930: + + should work as an MSS clone, but support for it is not implemented + yet. + diff --git a/sys/i386/isa/snd/ad1848.c b/sys/i386/isa/snd/ad1848.c new file mode 100644 index 0000000..97b328d0 --- /dev/null +++ b/sys/i386/isa/snd/ad1848.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/i386/isa/snd/clones.c b/sys/i386/isa/snd/clones.c new file mode 100644 index 0000000..edb64f6 --- /dev/null +++ b/sys/i386/isa/snd/clones.c @@ -0,0 +1,262 @@ +/* + * sound/clones.c + * + * init code for enabling clone cards to work in sb/mss emulation. + * + * Note -- this code is currently unused! + * + * Copyright by Luigi Rizzo - 1997 + * + * 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. + * + * This file has been written using information from various sources + * in the Voxware 3.5 distribution. + */ + +#include <i386/isa/snd/sound.h> +#if NPCM > 0 + +/* + * Known clones card include: + * + * Trix (emulating MSS) + * MAD16 (emulating MSS) + * OPTi930 -- same as the OPTi931, but no PnP ? + */ + + +#ifdef JAZZ16 + +/* + * Initialization of a Media Vision ProSonic 16 Soundcard. The function + * initializes a ProSonic 16 like PROS.EXE does for DOS. It sets the base + * address, the DMA-channels, interrupts and enables the joystickport. + * + * Also used by Jazz 16 (same card, different name) + * + * written 1994 by Rainer Vranken E-Mail: + * rvranken@polaris.informatik.uni-essen.de + */ + +#ifdef SM_WAVE +/* + * Logitech Soundman Wave detection and initialization by Hannu Savolainen. + * + * There is a microcontroller (8031) in the SM Wave card for MIDI emulation. + * it's located at address MPU_BASE+4. MPU_BASE+7 is a SM Wave specific + * control register for MC reset, SCSI, OPL4 and DSP (future expansion) + * address decoding. Otherwise the SM Wave is just a ordinary MV Jazz16 based + * soundcard. + */ + +static void +smw_putmem(int base, int addr, u_char val) +{ + u_long s; + + s = spltty(); + + outb(base + 1, addr & 0xff); /* Low address bits */ + outb(base + 2, addr >> 8); /* High address bits */ + outb(base, val); /* Data */ + + splx(s); +} + +static u_char +smw_getmem(int base, int addr) +{ + u_long s; + u_char val; + + s = spltty(); + + outb(base + 1, addr & 0xff); /* Low address bits */ + outb(base + 2, addr >> 8); /* High address bits */ + val = inb(base); /* Data */ + + splx(s); + return val; +} + +#ifdef SMW_MIDI0001_INCLUDED +#include </sys/i386/isa/snd/smw-midi0001.h> +#else +u_char *smw_ucode = NULL; +int smw_ucodeLen = 0; +#endif /* SWM_MIDI0001_INCLUDED */ + +static int +initialize_smw(int mpu_base) +{ + + int i, mp_base = mpu_base + 4; /* Microcontroller base */ + u_char control; + + /* + * Reset the microcontroller so that the RAM can be accessed + */ + + control = inb(mpu_base + 7); + outb(mpu_base + 7, control | 3); /* Set last two bits to 1 (?) */ + outb(mpu_base + 7, (control & 0xfe) | 2); /* xxxxxxx0 resets the mc */ + DELAY(3000); /* Wait at least 1ms */ + + outb(mpu_base + 7, control & 0xfc); /* xxxxxx00 enables RAM */ + + /* + * Detect microcontroller by probing the 8k RAM area + */ + smw_putmem(mp_base, 0, 0x00); + smw_putmem(mp_base, 1, 0xff); + DELAY(10); + + if (smw_getmem(mp_base, 0) != 0x00 || smw_getmem(mp_base, 1) != 0xff) { + printf("\nSM Wave: No microcontroller RAM detected (%02x, %02x)\n", + smw_getmem(mp_base, 0), smw_getmem(mp_base, 1)); + return 0; /* No RAM */ + } + /* + * There is RAM so assume it's really a SM Wave + */ + + if (smw_ucodeLen > 0) { + if (smw_ucodeLen != 8192) { + printf("\nSM Wave: Invalid microcode (MIDI0001.BIN) length\n"); + return 1; + } + /* + * Download microcode + */ + + for (i = 0; i < 8192; i++) + smw_putmem(mp_base, i, smw_ucode[i]); + + /* + * Verify microcode + */ + + for (i = 0; i < 8192; i++) + if (smw_getmem(mp_base, i) != smw_ucode[i]) { + printf("SM Wave: Microcode verification failed\n"); + return 0; + } + } + control = 0; +#ifdef SMW_SCSI_IRQ + /* + * Set the SCSI interrupt (IRQ2/9, IRQ3 or IRQ10). The SCSI interrupt + * is disabled by default. + * + * Btw the Zilog 5380 SCSI controller is located at MPU base + 0x10. + */ + { + static u_char scsi_irq_bits[] = + {0, 0, 3, 1, 0, 0, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0}; + + control |= scsi_irq_bits[SMW_SCSI_IRQ] << 6; + } +#endif + +#ifdef SMW_OPL4_ENABLE + /* + * Make the OPL4 chip visible on the PC bus at 0x380. + * + * There is no need to enable this feature since VoxWare doesn't support + * OPL4 yet. Also there is no RAM in SM Wave so enabling OPL4 is + * pretty useless. + */ + control |= 0x10; /* Uses IRQ12 if bit 0x20 == 0 */ + /* control |= 0x20; Uncomment this if you want to use IRQ7 */ +#endif + + outb(mpu_base + 7, control | 0x03); /* xxxxxx11 restarts */ + return 1; +} + +#endif + +/* + * this is only called during the probe. Variables are found in + * sb_probed. + */ +static sbdev_info sb_probed ; +static int +initialize_ProSonic16(snddev_info *d) +{ + int x; + static u_char int_translat[16] = + {0, 0, 2, 3, 0, 1, 0, 4, 0, 2, 5, 0, 0, 0, 0, 6}, + dma_translat[8] = + {0, 1, 0, 2, 0, 3, 0, 4}; + + struct address_info *mpu_config; + + int mpu_base, mpu_irq; + + if ((mpu_config = NULL)) { + mpu_base = mpu_config->io_base; + mpu_irq = mpu_config->irq; + } else { + mpu_base = mpu_irq = 0; + } + + outb(0x201, 0xAF); /* ProSonic/Jazz16 wakeup */ + DELAY(15000); /* wait at least 10 milliseconds */ + outb(0x201, 0x50); + outb(0x201, (sb_probed.io_base & 0x70) | ((mpu_base & 0x30) >> 4)); + + if (sb_reset_dsp(sb_probed.io_base)) { /* OK. We have at least a SB */ + + /* Check the version number of ProSonic (I guess) */ + + if (!sb_cmd(sb_probed.io_base, 0xFA)) + return 1; + if (sb_get_byte(sb_probed.io_base) != 0x12) + return 1; + + if (sb_cmd(sb_probed.io_base, 0xFB) && /* set DMA and irq */ + sb_cmd(sb_probed.io_base, + (dma_translat[JAZZ_DMA16]<<4)|dma_translat[sb_probed.dma1]) && + sb_cmd(sb_probed.io_base, + (int_translat[mpu_irq]<<4)|int_translat[sb_probed.irq])) { + d->bf_flags |= BD_F_JAZZ16 ; + if (mpu_base == 0) + printf("Jazz16: No MPU401 devices configured " + "- MIDI port not initialized\n"); + +#ifdef SM_WAVE + if (mpu_base != 0) + if (initialize_smw(mpu_base)) + d->bf_flags |= BD_F_JAZZ16_2 ; +#endif + /* sb_dsp_disable_midi(); */ + } + return 1; /* There was at least a SB */ + } + return 0; /* No SB or ProSonic16 detected */ +} + +#endif /* ifdef JAZZ16 */ + +#endif /* NPCM */ diff --git a/sys/i386/isa/snd/dmabuf.c b/sys/i386/isa/snd/dmabuf.c new file mode 100644 index 0000000..d4ac55e --- /dev/null +++ b/sys/i386/isa/snd/dmabuf.c @@ -0,0 +1,864 @@ +/* + * snd/dmabuf.c + * + * New DMA routines -- Luigi Rizzo, 19 jul 97 + * This file implements the new DMA routines for the sound driver. + * + * Copyright by Luigi Rizzo - 1997 + * + * 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. + * + */ + +#include <i386/isa/snd/sound.h> +#include <i386/isa/snd/ulaw.h> + +#define MIN_CHUNK_SIZE 256 /* for uiomove etc. */ +#define DMA_ALIGN_BITS 2 /* i.e. 4 bytes */ +#define DMA_ALIGN_THRESHOLD (1<< DMA_ALIGN_BITS) +#define DMA_ALIGN_MASK (~ (DMA_ALIGN_THRESHOLD - 1)) + +static void dsp_wr_dmadone(snddev_info *d); +static void dsp_rd_dmadone(snddev_info *d); + +/* + * + +SOUND OUTPUT + +We use a circular buffer to store samples directed to the DAC. +The buffer is split into three variable-size regions, each identified +by an offset in the buffer (dp,rp,fp) and a lenght (dl,rl,fl). + + 0 dp,dl rp,rl fp,fl bufsize + |__________|_____>______|_____________|________| + FREE DMA READY FREE + + READY region: contains data written from the process and ready + to be sent to the DAC; + + FREE region: is the empty region of the buffer, where a process + can write new data. + + DMA region: contains data being sent to the DAC by the DMA engine. + the actual boundary between the FREE and READY regions is in + fact within the DMA region (indicated by the > symbol above), + and advances as the DMA engine makes progress. + +Both the "READY" and "FREE" regions can wrap around the end of the +buffer. The "DMA" region can only wrap if AUTO DMA is used, otherwise +it cannot cross the end of the buffer. + +Since dl can be updated on the fly, dl0 marks the value used when the +operation was started. When using AUTO DMA, bufsize-(count in the ISA DMA +register) directly reflects the position of dp. + +At initialization, DMA and READY are empty, and FREE takes all the +available space: + + dp = rp = fp = 0 ; -- beginning of buffer + dl0 = dl = 0 ; -- meaning no dma activity + rl = 0 ; -- meaning no data ready + fl = bufsize ; + +The DMA region is also empty whenever there is no DMA activity, for +whatever reason (e.g. no ready data, or output is paused). +The code works as follows: the user write routine dsp_write_body() +fills up the READY region with new data (reclaiming space from the +FREE region) and starts the write DMA engine if inactive (activity +is indicated by d->flags & SND_F_WR_DMA ). The size of each +DMA transfer is chosen according to a series of rules which will be +discussed later. When a DMA transfer is complete, an interrupt causes +dsp_wrintr() to be called which empties the DMA region, extends +the FREE region and possibly starts the next transfer. + +In some cases, the code tries to track the current status of DMA +operations by calling isa_dmastatus() and advancing the boundary +between FREE and DMA regions accordingly. + +The size of a DMA transfer is selected according to the following +rules: + + 1. when not using AUTO DMA, do not wrap around the end of the + buffer, and do not let fp move too close to the end of the + buffer; + + 2. do not use more than half of the buffer size. + This serves to allow room for a next write operation concurrent + with the dma transfer, and to reduce the time which is necessary + to wait before a pending dma will end (optionally, the max + size could be further limited to a fixed amount of play time, + depending on number of channels, sample size and sample speed); + + 3. use the current blocksize (either specified by the user, or + corresponding roughly to 0.25s of data); + + * + */ + + +/* + * dsp_wr_dmadone moves the write DMA region into the FREE region. + * It is assumed to be called at spltty() and with a write dma + * previously started. + */ +static void +dsp_wr_dmadone(snddev_info *d) +{ + snd_dbuf *b = & (d->dbuf_out) ; + + isa_dmadone(B_WRITE, b->buf + b->dp, b->dl, d->dma1); + b->fl += b->dl ; /* make dl bytes free */ + /* + * XXX here it would be more efficient to record if there + * actually is a sleeping process, but this should still work. + */ + wakeup(b); /* wakeup possible sleepers */ + if (d->wsel.si_pid && + ( !(d->flags & SND_F_HAS_SIZE) || b->fl >= d->play_blocksize ) ) + selwakeup( & d->wsel ); + b->dp = b->rp ; + b->dl0 = b->dl = 0 ; +} + +/* + * The following function tracks the status of a (write) dma transfer, + * and moves the boundary between the FREE and the DMA regions. + * It works under the following assumptions: + * - the DMA engine is running; + * - the routine is called with interrupts blocked. + * BEWARE: when using AUTO DMA, dl can go negative! We assume that it + * does not wrap! + */ +void +dsp_wr_dmaupdate(snddev_info *d) +{ + snd_dbuf *b = & (d->dbuf_out) ; + int tmp; + + tmp = b->dl - isa_dmastatus1(d->dma1) ; + tmp &= DMA_ALIGN_MASK; /* align... */ + if (tmp > 0) { /* we have some new data */ + b->dp += tmp; + if (b->dp >= b->bufsize) + b->dp -= b->bufsize; + b->dl -= tmp ; + b->fl += tmp ; + } +} + +/* + * Write interrupt routine. Can be called from other places, but + * with interrupts disabled. + */ +void +dsp_wrintr(snddev_info *d) +{ + snd_dbuf *b = & (d->dbuf_out) ; + int cb_reason = SND_CB_WR ; + + DEB(printf("dsp_wrintr: start on dl %d, rl %d, fl %d\n", + b->dl, b->rl, b->fl)); + if (d->flags & SND_F_WR_DMA) { /* dma was active */ + b->int_count++; + d->flags &= ~SND_F_WR_DMA; + cb_reason = SND_CB_WR | SND_CB_RESTART ; + dsp_wr_dmadone(d); + } else + cb_reason = SND_CB_WR | SND_CB_START ; + + /* + * start another dma operation only if have ready data in the + * buffer, there is no pending abort, have a full-duplex device + * (dma1 != dma2) or have half duplex device and there is no + * pending op on the other side. + * + * Force transfer to be aligned to a boundary of 4, which is + * needed when doing stereo and 16-bit. We could make this + * adaptive, but why bother for now... + */ + if ( b->rl >= DMA_ALIGN_THRESHOLD && + ! (d->flags & SND_F_ABORTING) && + ( (d->dma1 != d->dma2) || ! (d->flags & SND_F_READING) ) ) { + b->dl = min(b->rl, b->bufsize - b->rp ) ; /* do not wrap */ + b->dl = min(b->dl, d->play_blocksize ); /* avoid too large transfer */ + b->dl &= DMA_ALIGN_MASK ; /* realign things */ + b->rl -= b->dl ; + b->rp += b->dl ; + if (b->rp == b->bufsize) + b->rp = 0 ; + /* + * now try to avoid too small dma transfers in the near future. + * This can happen if I let rp start too close to the end of + * the buffer. If this happens, and have enough data, try to + * split the available block in two approx. equal parts. + * XXX this code can go when we use auto dma. + */ + if (b->bufsize - b->rp < MIN_CHUNK_SIZE && + b->bufsize - b->dp > 2*MIN_CHUNK_SIZE) { + b->dl = (b->bufsize - b->dp) / 2; + b->dl &= ~3 ; /* align to a boundary of 4 */ + b->rl += (b->rp - (b->dp + b->dl) ) ; + b->rp = b->dp + b->dl ; /* no need to check for wraps */ + } + /* + * how important is the order of operations ? + */ + if (b->dl == 0) { + printf("ouch... want to start for 0 bytes!\n"); + goto ferma; + } + b->dl0 = b->dl ; /* XXX */ + if (d->callback) + d->callback(d, cb_reason ); /* start/restart dma */ + isa_dmastart( B_WRITE , b->buf + b->dp, b->dl, d->dma1); + d->flags |= SND_F_WR_DMA; + } else { +ferma: + if (d->callback && (cb_reason & SND_CB_REASON_MASK) == SND_CB_RESTART ) + d->callback(d, SND_CB_WR | SND_CB_STOP ); /* stop dma */ + /* + * if switching to read, should start the read dma... + */ + if ( d->dma1 == d->dma2 && (d->flags & SND_F_READING) ) + dsp_rdintr(d); + DEB(printf("cannot start wr-dma flags 0x%08x dma_dl %d rl %d\n", + d->flags, isa_dmastatus1(d->dma1), b->rl)); + } +} + +/* + * user write routine + * + * advance the boundary between READY and FREE, fill the space with + * uiomove(), and possibly start DMA. Do the above until the transfer + * is complete. + * + * To minimize latency in case a pending DMA transfer is about to end, + * we do the transfer in pieces of increasing sizes, extending the + * READY area at every checkpoint. In the (necessary) assumption that + * memory bandwidth is larger than the rate at which the dma consumes + * data, we reduce the latency to something proportional to the length + * of the first piece, while keeping the overhead low and being able + * to feed the DMA with large blocks. + */ + +int +dsp_write_body(snddev_info *d, struct uio *buf) +{ + int timeout = 1, n, l, bsz, ret = 0 ; + long s; + snd_dbuf *b = & (d->dbuf_out) ; + + /* assume d->flags |= SND_F_WRITING ; has been done before */ + /* + * bsz is the max size for the next transfer. If the dma was + * idle, we want it as large as possible. Otherwise, start with + * a small block to avoid underruns if we are close to the end of + * the previous operation. + */ + bsz = (d->flags & SND_F_WR_DMA) ? MIN_CHUNK_SIZE : b->bufsize ; + while ( n = buf->uio_resid ) { + l = min (n, bsz); /* at most n bytes ... */ + s = spltty(); /* no interrupts here ... */ + /* + * if i) the dma engine is running, ii) we do not have enough space + * in the FREE region, and iii) the current DMA transfer might let + * us complete the _whole_ transfer without sleeping, or we are doing + * non-blocking I/O, then try to extend the FREE region. + * Otherwise do not bother, we will need to sleep anyways, and + * make the timeout longer. + */ + + if ( d->flags & SND_F_WR_DMA && b->fl < l && + ( b->fl + b->dl >= n || d->flags & SND_F_NBIO ) ) + dsp_wr_dmaupdate(d); /* should really change timeout... */ + else + timeout = hz; + l = min( l, b->fl ); /* no more than avail. space */ + l = min( l, b->bufsize - b->fp ); /* do not wrap ... */ + DEB(printf("dsp_write_body: prepare %d bytes out of %d\n", l,n)); + /* + * at this point, we assume that if l==0 the dma engine + * must (or will, in cause it is paused) be running. + */ + if (l == 0) { /* no space, must sleep */ + if (d->flags & SND_F_NBIO) { + /* unless of course we are doing non-blocking i/o */ + splx(s); + break; + } + DEB(printf("dsp_write_body: l=0, (fl %d) sleeping\n", b->fl)); + ret = tsleep( (caddr_t)b, PRIBIO|PCATCH, "dspwr", timeout); + if (ret == EINTR) + d->flags |= SND_F_ABORTING ; + splx(s); + if (ret == ERESTART || ret == EINTR) + break ; + timeout = min(2*timeout, hz); + continue; + } + splx(s); + timeout = 1 ; /* we got some data... */ + + /* + * copy data to the buffer, and possibly do format + * conversions (here, from ULAW to U8). + * NOTE: I can use fp here since it is not modified by the + * interrupt routines. + */ + ret = uiomove(b->buf + b->fp, l, buf) ; + if (ret !=0 ) { /* an error occurred ... */ + printf("uiomove error %d\n", ret); + break ; + } + if (d->flags & SND_F_XLAT8) + translate_bytes(ulaw_dsp, b->buf + b->fp, l); + + s = spltty(); /* no interrupts here ... */ + b->rl += l ; /* this more ready bytes */ + b->fl -= l ; /* this less free bytes */ + b->fp += l ; + if (b->fp >= b->bufsize) /* handle wraps */ + b->fp -= b->bufsize ; + if ( !(d->flags & SND_F_WR_DMA) ) {/* dma was idle, restart it */ + if ( (d->flags & (SND_F_INIT|SND_F_WR_DMA|SND_F_RD_DMA)) == + SND_F_INIT) { + /* want to init but no pending DMA activity */ + splx(s); + d->callback(d, SND_CB_INIT); /* this is slow! */ + s = spltty(); + } + dsp_wrintr(d) ; + } + splx(s) ; + bsz = min(b->bufsize, bsz*2); + } + s = spltty(); /* no interrupts here ... */ + d->flags &= ~SND_F_WRITING ; + if (d->flags & SND_F_ABORTING) { + d->flags &= ~SND_F_ABORTING; + splx(s); + dsp_wrabort(d); + } + splx(s) ; + return ret ; +} + +/* + * SOUND INPUT + * + +The input part is similar to the output one. The only difference is in +the ordering of regions, which is the following: + + 0 rp,rl dp,dl fp,fl bufsize + |__________|____________|______>______|________| + FREE READY DMA FREE + +and the fact that input data are in the READY region. + +At initialization, as for the write routine, DMA and READY are empty, +and FREE takes all the space: + + dp = rp = fp = 0 ; -- beginning of buffer + dl0 = dl = 0 ; -- meaning no dma activity + rl = 0 ; -- meaning no data ready + fl = bufsize ; + +Operation is as follows: upon user read (dsp_read_body()) a DMA read +is started if not already active (marked by d->flags & SND_F_RD_DMA), +then as soon as data are available in the READY region they are +transferred to the user buffer, thus advancing the boundary between FREE +and READY. Upon interrupts, caused by a completion of a DMA transfer, +the READY region is extended and possibly a new transfer is started. + +When necessary, isa_dmastatus() is called to advance the boundary +between READY and DMA to the real position. + +The rules to choose the size of the new DMA area are similar to +the other case, i.e: + + 1. if not using AUTO mode, do not wrap around the end of the + buffer, and do not let fp move too close to the end of the + buffer; + + 2. do not use more than half the buffer size; this serves to + leave room for the next dma operation. + + 3. use the default blocksize, either user-specified, or + corresponding to 0.25s of data; + + * + */ + +/* + * dsp_rd_dmadone moves bytes in the input buffer from DMA region to + * READY region. We assume it is called at spltty() and with dl>0 + */ +static void +dsp_rd_dmadone(snddev_info *d) +{ + snd_dbuf *b = & (d->dbuf_in) ; + + isa_dmadone(B_READ, b->buf + b->dp, b->dl, d->dma2); + b->rl += b->dl ; /* make dl bytes available */ + wakeup(b) ; /* wakeup possibly sleeping processes */ + if (d->rsel.si_pid && + ( !(d->flags & SND_F_HAS_SIZE) || b->rl >= d->rec_blocksize ) ) + selwakeup( & d->rsel ); + b->dp = b->fp ; + b->dl0 = b->dl = 0 ; +} + +/* + * The following function tracks the status of a (read) dma transfer, + * and moves the boundary between the READY and the DMA regions. + * It works under the following assumptions: + * - the DMA engine is running; + * - the function is called with interrupts blocked. + */ +void +dsp_rd_dmaupdate(snddev_info *d) +{ + snd_dbuf *b = & (d->dbuf_in) ; + int tmp ; + + tmp = b->dl - isa_dmastatus1(d->dma2) ; + tmp &= DMA_ALIGN_MASK; /* align... */ + if (tmp > 0) { /* we have some data */ + b->dp += tmp; + if (b->dp >= b->bufsize) + b->dp -= b->bufsize; + b->dl -= tmp ; + b->rl += tmp ; + } +} + +/* + * read interrupt routine. Must be called with interrupts blocked. + */ +void +dsp_rdintr(snddev_info *d) +{ + snd_dbuf *b = & (d->dbuf_in) ; + int cb_reason = SND_CB_RD ; + + DEB(printf("dsp_rdintr: start dl = %d fp %d blocksize %d\n", + b->dl, b->fp, d->rec_blocksize)); + if (d->flags & SND_F_RD_DMA) { /* dma was active */ + b->int_count++; + d->flags &= ~SND_F_RD_DMA; + cb_reason = SND_CB_RD | SND_CB_RESTART ; + dsp_rd_dmadone(d); + } else + cb_reason = SND_CB_RD | SND_CB_START ; + /* + * Same checks as in the write case (mutatis mutandis) to decide + * whether or not to restart a dma transfer. + */ + if ( b->fl >= DMA_ALIGN_THRESHOLD && + ((d->flags & (SND_F_ABORTING|SND_F_CLOSING)) == 0) && + ( (d->dma1 != d->dma2) || ( (d->flags & SND_F_WRITING) == 0) ) ) { + b->dl = min(b->fl, b->bufsize - b->fp ) ; /* do not wrap */ + b->dl = min(b->dl, d->rec_blocksize ); + b->dl &= DMA_ALIGN_MASK ; /* realign sizes */ + b->fl -= b->dl ; + b->fp += b->dl ; + if (b->fp == b->bufsize) + b->fp = 0 ; + /* + * now try to avoid too small dma transfers in the near future. + * This can happen if I let fp start too close to the end of + * the buffer. If this happens, and have enough data, try to + * split the available block in two approx. equal parts. + */ + if (b->bufsize - b->fp < MIN_CHUNK_SIZE && + b->bufsize - b->dp > 2*MIN_CHUNK_SIZE) { + b->dl = (b->bufsize - b->dp) / 2; + b->dl &= DMA_ALIGN_MASK ; /* align to multiples of 3 */ + b->fl += (b->fp - (b->dp + b->dl) ) ; + b->fp = b->dp + b->dl ; /* no need to check that fp wraps */ + } + if (b->dl == 0) { + printf("ouch! want to read 0 bytes\n"); + goto ferma; + } + b->dl0 = b->dl ; /* XXX */ + if (d->callback) + d->callback(d, cb_reason); /* restart_dma(); */ + isa_dmastart( B_READ , b->buf + b->dp, b->dl, d->dma2); + d->flags |= SND_F_RD_DMA; + } else { +ferma: + if (d->callback && (cb_reason & SND_CB_REASON_MASK) == SND_CB_RESTART) + d->callback(d, SND_CB_RD | SND_CB_STOP); + /* + * if switching to write, start write dma engine + */ + if ( d->dma1 == d->dma2 && (d->flags & SND_F_WRITING) ) + dsp_wrintr(d) ; + DEB(printf("cannot start rd-dma flags 0x%08x dma_dl %d fl %d\n", + d->flags, isa_dmastatus1(d->dma2), b->fl)); + } +} + +/* + * body of user-read routine + * + * Start DMA if not active; wait for READY not empty. + * Transfer data from READY region using uiomove(), advance boundary + * between FREE and READY. Repeat until transfer is complete. + * + * To avoid excessive latency in freeing up space for the DMA + * engine, transfers are done in blocks of increasing size, so that + * the latency is proportional to the size of the smallest block, but + * we have a low overhead and are able to feed the dma engine with + * large blocks. + * + * When we enter this routine, we assume that d->flags |= SND_F_READING + * was done before. + * + * NOTE: in the current version, read will not return more than + * blocksize bytes at once (unless more are already available), to + * avoid that requests using very large buffers block for too long. + */ + +int +dsp_read_body(snddev_info *d, struct uio *buf) +{ + int limit, l, n, bsz, ret = 0 ; + long s; + snd_dbuf *b = & (d->dbuf_in) ; + + int timeout = 1 ; /* counter of how many ticks we sleep */ + + /* + * "limit" serves to return after at most one blocksize of data + * (unless more are already available). Otherwise, things like + * cat /dev/audio would use a 64K buffer and would start returning + * data after a _very_ long time... + * Note -- some applications depend on reads not returning short + * blocks. But I believe these apps are broken, since interrupted + * system calls might return short reads anyways, and the + * application should better check that. + */ + + if (buf->uio_resid > d->rec_blocksize) + limit = buf->uio_resid - d->rec_blocksize; + else + limit = 0; + bsz = MIN_CHUNK_SIZE ; /* the current transfer (doubles at each step) */ + while ( (n = buf->uio_resid) > limit ) { + DEB(printf("dsp_read_body: start waiting for %d bytes\n", n)); + /* + * here compute how many bytes to transfer, enforcing various + * limitations: + */ + l = min (n, bsz); /* 1': at most bsz bytes ... */ + s = spltty(); /* no interrupts here ! */ + /* + * if i) the dma engine is running, ii) we do not have enough + * ready bytes, and iii) the current DMA transfer could give + * us what we need, or we are doing non-blocking IO, then try + * to extend the READY region. + * Otherwise do not bother, we will need to sleep anyways, + * and make the timeout longer. + */ + + if ( d->flags & SND_F_RD_DMA && b->rl < l && + ( d->flags & SND_F_NBIO || b->rl + b->dl >= n - limit ) ) + dsp_rd_dmaupdate(d); + else + timeout = hz ; + l = min( l, b->rl ); /* 2': no more than avail. data */ + l = min( l, b->bufsize - b->rp ); /* 3': do not wrap buffer. */ + /* the above limitation can be removed if we use auto DMA + * on the ISA controller. But then we have to make a check + * when doing the uiomove... + */ + if ( !(d->flags & SND_F_RD_DMA) ) { /* dma was idle, start it */ + /* + * There are two reasons the dma can be idle: either this + * is the first read, or the buffer has become full. In + * the latter case, the dma cannot be restarted until + * we have removed some data, which will be true at the + * second round. + * + * Call dsp_rdintr to start the dma. It would be nice to + * have a "need" field in the snd_dbuf, so that we do + * not start a long operation unnecessarily. However, + * the restart code will ask for at most d->blocksize + * bytes, and since we are sure we are the only reader, + * and the routine is not interrupted, we patch and + * restore d->blocksize around the call. A bit dirty, + * but it works, and saves some overhead :) + */ + + int old_bs = d->rec_blocksize; + + if ( (d->flags & (SND_F_INIT|SND_F_WR_DMA|SND_F_RD_DMA)) == + SND_F_INIT) { + /* want to init but no pending DMA activity */ + splx(s); + d->callback(d, SND_CB_INIT); /* this is slow! */ + s = spltty(); + } + if (l < MIN_CHUNK_SIZE) + d->rec_blocksize = MIN_CHUNK_SIZE ; + else if (l < d->rec_blocksize) + d->rec_blocksize = l ; + dsp_rdintr(d); + d->rec_blocksize = old_bs ; + } + + if (l == 0) { + /* + * If, after all these efforts, we still have no data ready, + * then we must sleep (unless of course we have doing + * non-blocking i/o. But use exponential delays, starting + * at 1 tick and doubling each time. + */ + if (d->flags & SND_F_NBIO) { + splx(s); + break; + } + DEB(printf("dsp_read_body: sleeping %d waiting for %d bytes\n", + timeout, buf->uio_resid)); + ret = tsleep( (caddr_t)b, PRIBIO | PCATCH , "dsprd", timeout ) ; + if (ret == EINTR) + d->flags |= SND_F_ABORTING ; + splx(s); /* necessary before the goto again... */ + if (ret == ERESTART || ret == EINTR) + break ; + DEB(printf("woke up, ret %d, rl %d\n", ret, b->rl)); + timeout = min(timeout*2, hz); + continue; + } + splx(s); + + timeout = 1 ; /* we got some data, so reset exp. wait */ + /* + * if we are using /dev/audio and the device does not + * support it natively, we should do a format conversion. + * (in this case from uLAW to natural format). + * This can be messy in that it can require an intermediate + * buffer, and also screw up the byte count. + */ + /* + * NOTE: I _can_ use rp here because it is not modified by the + * interrupt routines. + */ + if (d->flags & SND_F_XLAT8) + translate_bytes(dsp_ulaw, b->buf + b->rp, l); + ret = uiomove(b->buf + b->rp, l, buf) ; + if (ret !=0 ) /* an error occurred ... */ + break ; + + s = spltty(); /* no interrupts here ... */ + b->fl += l ; /* this more free bytes */ + b->rl -= l ; /* this less ready bytes */ + b->rp += l ; /* advance ready pointer */ + if (b->rp == b->bufsize) /* handle wraps */ + b->rp = 0 ; + splx(s) ; + bsz = min(b->bufsize, bsz*2); + } + s = spltty(); /* no interrupts here ... */ + d->flags &= ~SND_F_READING ; + if (d->flags & SND_F_ABORTING) { + d->flags |= ~SND_F_ABORTING; + splx(s); + dsp_rdabort(d); + } + splx(s) ; + return ret ; +} + + +/* + * short routine to initialize a dma buffer descriptor (usually + * located in the XXX_desc structure). The first parameter is + * the buffer size, the second one specifies the dma channel in use + * At the moment we do not support more than 64K, since for some + * cards I need to switch between dma1 and dma2. The channel is + * unused. + */ +void +alloc_dbuf(snd_dbuf *b, int size, int chan) +{ + if (size > 0x10000) + panic("max supported size is 64k"); + b->buf = contigmalloc(size, M_DEVBUF, M_NOWAIT, + 0ul, 0xfffffful, 1ul, 0x10000ul); + /* should check that it does not fail... */ + b->dp = b->rp = b->fp = 0 ; + b->dl0 = b->dl = b->rl = 0 ; + b->bufsize = b->fl = size ; +} + +void +reset_dbuf(snd_dbuf *b) +{ + b->dp = b->rp = b->fp = 0 ; + b->dl0 = b->dl = b->rl = 0 ; + b->fl = b->bufsize ; +} + +/* + * snd_sync waits until the space in the given channel goes above + * a threshold. chan = 1 : play, 2: capture. The threshold is + * checked against fl or rl respectively. + * Assume that the condition can become true, do not check here... + */ +int +snd_sync(snddev_info *d, int chan, int threshold) +{ + u_long s; + int ret; + snd_dbuf *b; + + b = (chan == 1) ? &(d->dbuf_out ) : &(d->dbuf_in ) ; + + for (;;) { + s=spltty(); + if ( chan==1 ) + dsp_wr_dmaupdate(d); + else + dsp_rd_dmaupdate(d); + if ( (chan == 1 && b->fl <= threshold) || + (chan == 2 && b->rl <= threshold) ) { + ret = tsleep((caddr_t)b, PRIBIO|PCATCH, "sndsyn", 1); + splx(s); + if (ret == ERESTART || ret == EINTR) { + printf("tsleep returns %d\n", ret); + return -1 ; + } + } else + break; + } + splx(s); + return 0 ; +} + +/* + * dsp_wrabort(d) and dsp_rdabort(d) are non-blocking functions + * which abort a pending DMA transfer and flush the buffers. + */ +int +dsp_wrabort(snddev_info *d) +{ + long s; + int missing = 0; + snd_dbuf *b = & (d->dbuf_out) ; + + s = spltty(); + if ( d->flags & SND_F_WR_DMA ) { + d->flags &= ~(SND_F_WR_DMA | SND_F_WRITING); + if (d->callback) + d->callback(d, SND_CB_WR | SND_CB_ABORT); + missing = isa_dmastop(d->dma1) ; /* this many missing bytes... */ + + b->rl += missing ; /* which are part of the ready area */ + b->rp -= missing ; + if (b->rp < 0) + b->rp += b->bufsize; + DEB(printf("dsp_wrabort: stopped after %d bytes out of %d\n", + b->dl - missing, b->dl)); + b->dl -= missing; + dsp_wr_dmadone(d); + missing = b->rl; + } + reset_dbuf(b); + splx(s); + return missing; +} + +int +dsp_rdabort(snddev_info *d) +{ + long s; + int missing = 0; + snd_dbuf *b = & (d->dbuf_in) ; + + s = spltty(); + if ( d->flags & SND_F_RD_DMA ) { + d->flags &= ~(SND_F_RD_DMA | SND_F_READING); + if (d->callback) + d->callback(d, SND_CB_RD | SND_CB_ABORT); + missing = isa_dmastop(d->dma2) ; /* this many missing bytes... */ + + b->fl += missing ; /* which are part of the free area */ + b->fp -= missing ; + if (b->fp < 0) + b->fp += b->bufsize; + DEB(printf("dsp_rdabort: stopped after %d bytes out of %d\n", + b->dl - missing, b->dl)); + b->dl -= missing; + dsp_rd_dmadone(d); + missing = b->rl ; + } + reset_dbuf(b); + splx(s); + return missing; +} + +/* + * this routine tries to flush the dma transfer. It is called + * on a close. The caller must set SND_F_CLOSING, and insure that + * interrupts are enabled. We immediately abort any read DMA + * operation, and then wait for the play buffer to drain. + */ + +int +snd_flush(snddev_info *d) +{ + int ret, res, res1; + int count=10; + +DEB(printf("snd_flush d->flags 0x%08x\n", d->flags)); + dsp_rdabort(d); + if ( d->flags & SND_F_WR_DMA ) { + /* close write */ + while ( d->flags & SND_F_WR_DMA ) { + /* + * still pending output data. + */ + ret = tsleep( (caddr_t)&(d->dbuf_out), PRIBIO|PCATCH, "dmafl1", hz); + if (ret == ERESTART || ret == EINTR) { + printf("tsleep returns %d\n", ret); + return -1 ; + } + if ( ret && --count == 0) { + printf("timeout flushing dma1, cnt 0x%x flags 0x%08x\n", + isa_dmastatus1(d->dma1), d->flags); + return -1 ; + } + } + d->flags &= ~SND_F_CLOSING ; + } + reset_dbuf(& (d->dbuf_out) ); + return 0 ; +} + +/* + * end of new code for dma buffer handling + */ diff --git a/sys/i386/isa/snd/dmabuf_auto.c b/sys/i386/isa/snd/dmabuf_auto.c new file mode 100644 index 0000000..3972be8 --- /dev/null +++ b/sys/i386/isa/snd/dmabuf_auto.c @@ -0,0 +1,802 @@ +/* + * snd/dmabuf.c + * + * New DMA routines -- Luigi Rizzo, 05 sep 97 + * This file implements the new DMA routines for the sound driver. + * AUTO DMA MODE (ISA DMA SIDE). + * + * Copyright by Luigi Rizzo - 1997 + * + * 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. + * + */ + +#include <i386/isa/snd/sound.h> +#include <i386/isa/snd/ulaw.h> + +#define MIN_CHUNK_SIZE 256 /* for uiomove etc. */ +#define DMA_ALIGN_BITS 2 /* i.e. 4 bytes */ +#define DMA_ALIGN_THRESHOLD (1<< DMA_ALIGN_BITS) +#define DMA_ALIGN_MASK (~ (DMA_ALIGN_THRESHOLD - 1)) + +static void dsp_wr_dmadone(snddev_info *d); +static void dsp_rd_dmadone(snddev_info *d); + +/* + * + +SOUND OUTPUT + +We use a circular buffer to store samples directed to the DAC. +The buffer is split into two variable-size regions, each identified +by an offset in the buffer (rp,fp) and a lenght (rl,fl). + + 0 rp,rl fp,fl bufsize + |__________|____________|________| + FREE d> READY w> FREE + + READY region: contains data written from the process and ready + to be sent to the DAC; + + FREE region: is the empty region of the buffer, where a process + can write new data. + +The first block of the READY region is used for DMA transfers. The +transfer is started at rp and with chunks of length dl0 computed +according to some rules. During DMA operations, rp advances (d>) +and rl,fl are updated by dsp_wr_dmaupdate(). When a new block is +written, fp advances (w>) and rl,fl are updated accordingly. + +Both the "READY" and "FREE" regions can wrap around the end of the +buffer. + +At initialization, READY is empty, FREE takes all the +available space, and dma is idle + +dl0 contains the new blocksize to be used in dma transfers (passed to +the callback). dl contains the previous blocksize (needed when the +codec uses AUTO DMA). + + rp = fp = 0 ; -- beginning of buffer + dl0 = dl = 0 ; + rl = 0 ; -- meaning no data ready + fl = bufsize ; + +The code works as follows: the user write routine dsp_write_body() +fills up the READY region with new data (reclaiming space from the +FREE region) and starts the write DMA engine if inactive (activity +is indicated by d->flags & SND_F_WR_DMA ). The size of each +DMA transfer is chosen according to a series of rules which will be +discussed later. When a DMA transfer is complete, an interrupt causes +dsp_wrintr() to be called which empties the DMA region, extends +the FREE region and possibly starts the next transfer. + +In some cases, the code tries to track the current status of DMA +operations by calling isa_dmastatus() and advancing the boundary +between FREE and DMA regions accordingly. + +The size of a DMA transfer is selected according to the following +rules: + + 1. when not using AUTO DMA, do not wrap around the end of the + buffer, and do not let fp move too close to the end of the + buffer; + + 2. do not use more than half of the buffer size. + This serves to allow room for a next write operation concurrent + with the dma transfer, and to reduce the time which is necessary + to wait before a pending dma will end (optionally, the max + size could be further limited to a fixed amount of play time, + depending on number of channels, sample size and sample speed); + + 3. use the current blocksize (either specified by the user, or + corresponding roughly to 0.25s of data); + + * + */ + + +/* + * dsp_wr_dmadone moves the write DMA region into the FREE region. + * It is assumed to be called at spltty() and with a write dma + * previously started. + */ +static void +dsp_wr_dmadone(snddev_info *d) +{ + snd_dbuf *b = & (d->dbuf_out) ; + + dsp_wr_dmaupdate(d); + /* + * XXX here it would be more efficient to record if there + * actually is a sleeping process, but this should still work. + */ + wakeup(b); /* wakeup possible sleepers */ + if (d->wsel.si_pid && + ( !(d->flags & SND_F_HAS_SIZE) || b->fl >= d->play_blocksize ) ) + selwakeup( & d->wsel ); +} + +/* + * The following function tracks the status of a (write) dma transfer, + * and moves the boundary between the FREE and the DMA regions. + * It works under the following assumptions: + * - the DMA engine is active; + * - the routine is called with interrupts blocked. + */ +void +dsp_wr_dmaupdate(snddev_info *d) +{ + snd_dbuf *b = & (d->dbuf_out) ; + int tmp, delta; + + tmp = b->bufsize - isa_dmastatus1(d->dma1) ; + tmp &= DMA_ALIGN_MASK; /* align... */ + delta = tmp - b->rp; + if (delta < 0) /* wrapped */ + delta += b->bufsize ; + b->rp = tmp; + b->rl -= delta ; + b->fl += delta ; +} + +/* + * Write interrupt routine. Can be called from other places, but + * with interrupts disabled. + */ +void +dsp_wrintr(snddev_info *d) +{ + snd_dbuf *b = & (d->dbuf_out) ; + + DEB(printf("dsp_wrintr: start on dl %d, rl %d, fl %d\n", + b->dl, b->rl, b->fl)); + if (d->flags & SND_F_WR_DMA) { /* dma was active */ + b->int_count++; + d->flags &= ~SND_F_WR_DMA; + dsp_wr_dmadone(d); + } else + b->dl = 0 ; + + /* + * start another dma operation only if have ready data in the + * buffer, there is no pending abort, have a full-duplex device + * (dma1 != dma2) or have half duplex device and there is no + * pending op on the other side. + * + * Force transfer to be aligned to a boundary of 4, which is + * needed when doing stereo and 16-bit. We could make this + * adaptive, but why bother for now... + */ + if ( b->rl >= DMA_ALIGN_THRESHOLD && + ! (d->flags & SND_F_ABORTING) && + ( (d->dma1 != d->dma2) || ! (d->flags & SND_F_READING) ) ) { + int l = min(b->rl, d->play_blocksize ); /* avoid too large transfer */ + l &= DMA_ALIGN_MASK ; /* realign things */ + + b->dl0 = l ; + if (d->callback) + d->callback(d, SND_CB_WR | SND_CB_START ); + d->flags |= SND_F_WR_DMA; + b->dl = b->dl0; + } else { + if (b->dl > 0) { /* was active */ + b->dl0 = 0; + d->callback(d, SND_CB_WR | SND_CB_STOP ); /* stop dma */ + } + /* + * if switching to read, should start the read dma... + */ + if ( d->dma1 == d->dma2 && (d->flags & SND_F_READING) ) + dsp_rdintr(d); + DEB(printf("cannot start wr-dma flags 0x%08x dma_dl %d rl %d\n", + d->flags, isa_dmastatus1(d->dma1), b->rl)); + } +} + +/* + * user write routine + * + * advance the boundary between READY and FREE, fill the space with + * uiomove(), and possibly start DMA. Do the above until the transfer + * is complete. + * + * To minimize latency in case a pending DMA transfer is about to end, + * we do the transfer in pieces of increasing sizes, extending the + * READY area at every checkpoint. In the (necessary) assumption that + * memory bandwidth is larger than the rate at which the dma consumes + * data, we reduce the latency to something proportional to the length + * of the first piece, while keeping the overhead low and being able + * to feed the DMA with large blocks. + */ + +int +dsp_write_body(snddev_info *d, struct uio *buf) +{ + int timeout = 1, n, l, bsz, ret = 0 ; + long s; + snd_dbuf *b = & (d->dbuf_out) ; + + /* assume d->flags |= SND_F_WRITING ; has been done before */ + /* + * bsz is the max size for the next transfer. If the dma was + * idle, we want it as large as possible. Otherwise, start with + * a small block to avoid underruns if we are close to the end of + * the previous operation. + */ + bsz = (d->flags & SND_F_WR_DMA) ? MIN_CHUNK_SIZE : b->bufsize ; + while ( n = buf->uio_resid ) { + l = min (n, bsz); /* at most n bytes ... */ + s = spltty(); /* no interrupts here ... */ + /* + * if i) the dma engine is running, ii) we do not have enough space + * in the FREE region, and iii) the current DMA transfer might let + * us complete the _whole_ transfer without sleeping, or we are doing + * non-blocking I/O, then try to extend the FREE region. + * Otherwise do not bother, we will need to sleep anyways, and + * make the timeout longer. + * Note that the check for iii) is only approximate since we + * might have fewer than dl0 residual bytes. + */ + + if ( d->flags & SND_F_WR_DMA && b->fl < l && + ( b->fl + b->dl0 >= n || d->flags & SND_F_NBIO ) ) + dsp_wr_dmaupdate(d); /* should really change timeout... */ + else + timeout = hz; + l = min( l, b->fl ); /* no more than avail. space */ + DEB(printf("dsp_write_body: prepare %d bytes out of %d\n", l,n)); + /* + * at this point, we assume that if l==0 the dma engine + * must (or will, in cause it is paused) be running. + */ + if (l == 0) { /* no space, must sleep */ + if (d->flags & SND_F_NBIO) { + /* unless of course we are doing non-blocking i/o */ + splx(s); + break; + } + DEB(printf("dsp_write_body: l=0, (fl %d) sleeping\n", b->fl)); + ret = tsleep( (caddr_t)b, PRIBIO|PCATCH, "dspwr", timeout); + if (ret == EINTR) + d->flags |= SND_F_ABORTING ; + splx(s); + if (ret == ERESTART || ret == EINTR) + break ; + timeout = min(2*timeout, hz); + continue; + } + splx(s); + timeout = 1 ; /* we got some data... */ + + /* + * copy data to the buffer, and possibly do format + * conversions (here, from ULAW to U8). + * NOTE: I can use fp here since it is not modified by the + * interrupt routines. + */ + if (b->fp + l > b->bufsize) { + int l1 = b->bufsize - b->fp ; + uiomove(b->buf + b->fp, l1, buf) ; + uiomove(b->buf, l - l1, buf) ; + if (d->flags & SND_F_XLAT8) { + translate_bytes(ulaw_dsp, b->buf + b->fp, l1); + translate_bytes(ulaw_dsp, b->buf , l - l1); + } + } else { + uiomove(b->buf + b->fp, l, buf) ; + if (d->flags & SND_F_XLAT8) + translate_bytes(ulaw_dsp, b->buf + b->fp, l); + } + + s = spltty(); /* no interrupts here ... */ + b->rl += l ; /* this more ready bytes */ + b->fl -= l ; /* this less free bytes */ + b->fp += l ; + if (b->fp >= b->bufsize) /* handle wraps */ + b->fp -= b->bufsize ; + if ( !(d->flags & SND_F_WR_DMA) ) /* dma was idle, restart it */ + dsp_wrintr(d) ; + splx(s) ; + bsz = min(b->bufsize, bsz*2); + } + s = spltty(); /* no interrupts here ... */ + d->flags &= ~SND_F_WRITING ; + if (d->flags & SND_F_ABORTING) { + d->flags &= ~SND_F_ABORTING; + splx(s); + dsp_wrabort(d); + } + splx(s) ; + return ret ; +} + +/* + * SOUND INPUT + * + +The input part is similar to the output one. The only difference is in +the ordering of regions, which is the following: + + 0 rp,rl fp,fl bufsize + |__________|____________|________| + FREE r> READY d> FREE + +and the fact that input data are in the READY region. + +At initialization, as for the write routine, READY is empty, +and FREE takes all the space: + + rp = fp = 0 ; -- beginning of buffer + dl0 = dl = 0 ; + rl = 0 ; -- meaning no data ready + fl = bufsize ; + +Operation is as follows: upon user read (dsp_read_body()) a DMA read +is started if not already active (marked by d->flags & SND_F_RD_DMA), +then as soon as data are available in the READY region they are +transferred to the user buffer, thus advancing the boundary between FREE +and READY. Upon interrupts, caused by a completion of a DMA transfer, +the READY region is extended and possibly a new transfer is started. + +When necessary, dsp_rd_dmaupdate() is called to advance fp (and update +rl,fl accordingly). Upon user reads, rp is advanced and rl,fl are +updated accordingly. + + +The rules to choose the size of the new DMA area are similar to +the other case, i.e: + + 1. if not using AUTO mode, do not wrap around the end of the + buffer, and do not let fp move too close to the end of the + buffer; + + 2. do not use more than half the buffer size; this serves to + leave room for the next dma operation. + + 3. use the default blocksize, either user-specified, or + corresponding to 0.25s of data; + + * + */ + +/* + * dsp_rd_dmadone moves bytes in the input buffer from DMA region to + * READY region. We assume it is called at spltty() and with dl>0 + */ +static void +dsp_rd_dmadone(snddev_info *d) +{ + snd_dbuf *b = & (d->dbuf_in) ; + + dsp_rd_dmaupdate(d); + b->rl += b->dl ; /* make dl bytes available */ + wakeup(b) ; /* wakeup possibly sleeping processes */ + if (d->rsel.si_pid && + ( !(d->flags & SND_F_HAS_SIZE) || b->rl >= d->rec_blocksize ) ) + selwakeup( & d->rsel ); + b->dp = b->fp ; + b->dl0 = b->dl = 0 ; +} + +/* + * The following function tracks the status of a (read) dma transfer, + * and moves the boundary between the READY and the DMA regions. + * It works under the following assumptions: + * - the DMA engine is running; + * - the function is called with interrupts blocked. + */ +void +dsp_rd_dmaupdate(snddev_info *d) +{ + snd_dbuf *b = & (d->dbuf_in) ; + int tmp ; + + tmp = b->bufsize - isa_dmastatus1(d->dma2) ; + tmp &= DMA_ALIGN_MASK; /* align... */ + delta = tmp - b->fp; + if (delta < 0) /* wrapped */ + delta += b->bufsize ; + b->fp = tmp; + b->fl -= delta ; + b->rl += delta ; +} + +/* + * read interrupt routine. Must be called with interrupts blocked. + */ +void +dsp_rdintr(snddev_info *d) +{ + snd_dbuf *b = & (d->dbuf_in) ; + + if (d->flags & SND_F_RD_DMA) { /* dma was active */ + b->int_count++; + d->flags &= ~SND_F_RD_DMA; + dsp_rd_dmadone(d); + } else + b->dl = 0 ; + /* + * Same checks as in the write case (mutatis mutandis) to decide + * whether or not to restart a dma transfer. + */ + if ( b->fl >= DMA_ALIGN_THRESHOLD && + ((d->flags & (SND_F_ABORTING|SND_F_CLOSING)) == 0) && + ( (d->dma1 != d->dma2) || ( (d->flags & SND_F_WRITING) == 0) ) ) { + int l = min(b->fl, d->rec_blocksize); + l &= DMA_ALIGN_MASK ; /* realign sizes */ + b->dl0 = l ; + if (d->callback) + d->callback(d, SND_CB_RD | SND_CB_START ); + d->flags |= SND_F_RD_DMA; + b->dl = b->dl0; + } else { + if (b->dl > 0) { + b->dl0 = 0 ; /* was active */ + d->callback(d, SND_CB_RD | SND_CB_STOP); + } + /* + * if switching to write, start write dma engine + */ + if ( d->dma1 == d->dma2 && (d->flags & SND_F_WRITING) ) + dsp_wrintr(d) ; + DEB(printf("cannot start rd-dma flags 0x%08x dma_dl %d fl %d\n", + d->flags, isa_dmastatus1(d->dma2), b->fl)); + } +} + +/* + * body of user-read routine + * + * Start DMA if not active; wait for READY not empty. + * Transfer data from READY region using uiomove(), advance boundary + * between FREE and READY. Repeat until transfer is complete. + * + * To avoid excessive latency in freeing up space for the DMA + * engine, transfers are done in blocks of increasing size, so that + * the latency is proportional to the size of the smallest block, but + * we have a low overhead and are able to feed the dma engine with + * large blocks. + * + * When we enter this routine, we assume that d->flags |= SND_F_READING + * was done before. + * + * NOTE: in the current version, read will not return more than + * blocksize bytes at once (unless more are already available), to + * avoid that requests using very large buffers block for too long. + */ + +int +dsp_read_body(snddev_info *d, struct uio *buf) +{ + int limit, l, n, bsz, ret = 0 ; + long s; + snd_dbuf *b = & (d->dbuf_in) ; + + int timeout = 1 ; /* counter of how many ticks we sleep */ + + /* + * "limit" serves to return after at most one blocksize of data + * (unless more are already available). Otherwise, things like + * cat /dev/audio would use a 64K buffer and would start returning + * data after a _very_ long time... + * Note -- some applications depend on reads not returning short + * blocks. But I believe these apps are broken, since interrupted + * system calls might return short reads anyways, and the + * application should better check that. + */ + + if (buf->uio_resid > d->rec_blocksize) + limit = buf->uio_resid - d->rec_blocksize; + else + limit = 0; + bsz = MIN_CHUNK_SIZE ; /* the current transfer (doubles at each step) */ + while ( (n = buf->uio_resid) > limit ) { + DEB(printf("dsp_read_body: start waiting for %d bytes\n", n)); + /* + * here compute how many bytes to transfer, enforcing various + * limitations: + */ + l = min (n, bsz); /* 1': at most bsz bytes ... */ + s = spltty(); /* no interrupts here ! */ + /* + * if i) the dma engine is running, ii) we do not have enough + * ready bytes, and iii) the current DMA transfer could give + * us what we need, or we are doing non-blocking IO, then try + * to extend the READY region. + * Otherwise do not bother, we will need to sleep anyways, + * and make the timeout longer. + */ + + if ( d->flags & SND_F_RD_DMA && b->rl < l && + ( d->flags & SND_F_NBIO || b->rl + b->dl >= n - limit ) ) + dsp_rd_dmaupdate(d); + else + timeout = hz ; + l = min( l, b->rl ); /* 2': no more than avail. data */ + if ( !(d->flags & SND_F_RD_DMA) ) { /* dma was idle, start it */ + /* + * There are two reasons the dma can be idle: either this + * is the first read, or the buffer has become full. In + * the latter case, the dma cannot be restarted until + * we have removed some data, which will be true at the + * second round. + * + * Call dsp_rdintr to start the dma. It would be nice to + * have a "need" field in the snd_dbuf, so that we do + * not start a long operation unnecessarily. However, + * the restart code will ask for at most d->blocksize + * bytes, and since we are sure we are the only reader, + * and the routine is not interrupted, we patch and + * restore d->blocksize around the call. A bit dirty, + * but it works, and saves some overhead :) + */ + + int old_bs = d->rec_blocksize; + if ( (d->flags & (SND_F_INIT|SND_F_WR_DMA|SND_F_RD_DMA)) == + SND_F_INIT) { + /* want to init but no pending DMA activity */ + splx(s); + d->callback(d, SND_CB_INIT); /* this is slow! */ + s = spltty(); + } + if (l < MIN_CHUNK_SIZE) + d->rec_blocksize = MIN_CHUNK_SIZE ; + else if (l < d->rec_blocksize) + d->rec_blocksize = l ; + d->dl0 = d->rec_blocksize ; + dsp_rdintr(d); + d->rec_blocksize = old_bs ; + } + + if (l == 0) { + /* + * If, after all these efforts, we still have no data ready, + * then we must sleep (unless of course we have doing + * non-blocking i/o. But use exponential delays, starting + * at 1 tick and doubling each time. + */ + if (d->flags & SND_F_NBIO) { + splx(s); + break; + } + DEB(printf("dsp_read_body: sleeping %d waiting for %d bytes\n", + timeout, buf->uio_resid)); + ret = tsleep( (caddr_t)b, PRIBIO | PCATCH , "dsprd", timeout ) ; + if (ret == EINTR) + d->flags |= SND_F_ABORTING ; + splx(s); /* necessary before the goto again... */ + if (ret == ERESTART || ret == EINTR) + break ; + DEB(printf("woke up, ret %d, rl %d\n", ret, b->rl)); + timeout = min(timeout*2, hz); + continue; + } + splx(s); + + timeout = 1 ; /* we got some data, so reset exp. wait */ + /* + * if we are using /dev/audio and the device does not + * support it natively, we should do a format conversion. + * (in this case from uLAW to natural format). + * This can be messy in that it can require an intermediate + * buffer, and also screw up the byte count. + */ + /* + * NOTE: I _can_ use rp here because it is not modified by the + * interrupt routines. + */ + if (d->flags & SND_F_XLAT8) + translate_bytes(dsp_ulaw, b->buf + b->rp, l); + ret = uiomove(b->buf + b->rp, l, buf) ; + if (ret !=0 ) /* an error occurred ... */ + break ; + + s = spltty(); /* no interrupts here ... */ + b->fl += l ; /* this more free bytes */ + b->rl -= l ; /* this less ready bytes */ + b->rp += l ; /* advance ready pointer */ + if (b->rp == b->bufsize) /* handle wraps */ + b->rp = 0 ; + splx(s) ; + bsz = min(b->bufsize, bsz*2); + } + s = spltty(); /* no interrupts here ... */ + d->flags &= ~SND_F_READING ; + if (d->flags & SND_F_ABORTING) { + d->flags |= ~SND_F_ABORTING; + splx(s); + dsp_rdabort(d); + } + splx(s) ; + return ret ; +} + + +/* + * short routine to initialize a dma buffer descriptor (usually + * located in the XXX_desc structure). The first parameter is + * the buffer size, the second one specifies that a 16-bit dma channel + * is used (hence the buffer must be properly aligned). + */ +void +alloc_dbuf(snd_dbuf *b, int size, int chan) +{ + if (size > 0x10000) + panic("max supported size is 64k"); + b->buf = contigmalloc(size, M_DEVBUF, M_NOWAIT, + 0ul, 0xfffffful, 1ul, 0x10000ul); + /* should check that it does not fail... */ + b->dp = b->rp = b->fp = 0 ; + b->dl0 = b->dl = b->rl = 0 ; + b->bufsize = b->fl = size ; +} + +void +reset_dbuf(snd_dbuf *b) +{ + b->dp = b->rp = b->fp = 0 ; + b->dl0 = b->dl = b->rl = 0 ; + b->fl = b->bufsize ; +} + +/* + * snd_sync waits until the space in the given channel goes above + * a threshold. chan = 1 : play, 2: capture. The threshold is + * checked against fl or rl respectively. + * Assume that the condition can become true, do not check here... + */ +int +snd_sync(snddev_info *d, int chan, int threshold) +{ + u_long s; + int ret; + snd_dbuf *b; + + b = (chan == 1) ? &(d->dbuf_out ) : &(d->dbuf_in ) ; + + for (;;) { + s=spltty(); + if ( chan==1 ) + dsp_wr_dmaupdate(d); + else + dsp_rd_dmaupdate(d); + if ( (chan == 1 && b->fl <= threshold) || + (chan == 2 && b->rl <= threshold) ) { + ret = tsleep((caddr_t)b, PRIBIO|PCATCH, "sndsyn", 1); + splx(s); + if (ret == ERESTART || ret == EINTR) { + printf("tsleep returns %d\n", ret); + return -1 ; + } + } else + break; + } + splx(s); + return 0 ; +} + +/* + * dsp_wrabort(d) and dsp_rdabort(d) are non-blocking functions + * which abort a pending DMA transfer and flush the buffers. + */ +int +dsp_wrabort(snddev_info *d) +{ + long s; + int missing = 0; + snd_dbuf *b = & (d->dbuf_out) ; + + s = spltty(); + if ( d->flags & SND_F_WR_DMA ) { + d->flags &= ~ ( SND_F_WR_DMA | SND_F_WRITING ) ; + if (d->callback) + d->callback(d, SND_CB_WR | SND_CB_ABORT); + missing = isa_dmastop(d->dma1) ; /* this many missing bytes... */ + + b->rl += missing ; /* which are part of the ready area */ + b->rp -= missing ; + if (b->rp < 0) + b->rp += b->bufsize; + DEB(printf("dsp_wrabort: stopped after %d bytes out of %d\n", + b->dl - missing, b->dl)); + b->dl -= missing; + dsp_wr_dmadone(d); + missing = b->rl; + } + reset_dbuf(b); + splx(s); + return missing; +} + +int +dsp_rdabort(snddev_info *d) +{ + long s; + int missing = 0; + snd_dbuf *b = & (d->dbuf_in) ; + + s = spltty(); + if ( d->flags & SND_F_RD_DMA ) { + d->flags &= ~ ( SND_F_RD_DMA | SND_F_READING ) ; + if (d->callback) + d->callback(d, SND_CB_RD | SND_CB_ABORT); + missing = isa_dmastop(d->dma2) ; /* this many missing bytes... */ + + b->fl += missing ; /* which are part of the free area */ + b->fp -= missing ; + if (b->fp < 0) + b->fp += b->bufsize; + DEB(printf("dsp_rdabort: stopped after %d bytes out of %d\n", + b->dl - missing, b->dl)); + b->dl -= missing; + dsp_rd_dmadone(d); + missing = b->rl ; + } + reset_dbuf(b); + splx(s); + return missing; +} + +/* + * this routine tries to flush the dma transfer. It is called + * on a close. The caller must set SND_F_CLOSING, and insure that + * interrupts are enabled. We immediately abort any read DMA + * operation, and then wait for the play buffer to drain. + */ + +int +snd_flush(snddev_info *d) +{ + int ret, res, res1; + int count=10; + + DEB(printf("snd_flush d->flags 0x%08x\n", d->flags)); + dsp_rdabort(d); + if ( d->flags & SND_F_WR_DMA ) { + /* close write */ + while ( d->flags & SND_F_WR_DMA ) { + /* + * still pending output data. + */ + ret = tsleep( (caddr_t)&(d->dbuf_out), PRIBIO|PCATCH, "dmafl1", hz); + if (ret == ERESTART || ret == EINTR) { + printf("tsleep returns %d\n", ret); + return -1 ; + } + if ( ret && --count == 0) { + printf("timeout flushing dma1, cnt 0x%x flags 0x%08x\n", + isa_dmastatus1(d->dma1), d->flags); + return -1 ; + } + } + d->flags &= ~SND_F_CLOSING ; + } + reset_dbuf(& (d->dbuf_out) ); + return 0 ; +} + +/* + * end of new code for dma buffer handling + */ diff --git a/sys/i386/isa/snd/mss.h b/sys/i386/isa/snd/mss.h new file mode 100644 index 0000000..4b34e51 --- /dev/null +++ b/sys/i386/isa/snd/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/i386/isa/snd/sb_dsp.c b/sys/i386/isa/snd/sb_dsp.c new file mode 100644 index 0000000..4ca1f1f --- /dev/null +++ b/sys/i386/isa/snd/sb_dsp.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/i386/isa/snd/sbcard.h b/sys/i386/isa/snd/sbcard.h new file mode 100644 index 0000000..8548997 --- /dev/null +++ b/sys/i386/isa/snd/sbcard.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/i386/isa/snd/sound.c b/sys/i386/isa/snd/sound.c new file mode 100644 index 0000000..a9b5fd5 --- /dev/null +++ b/sys/i386/isa/snd/sound.c @@ -0,0 +1,1254 @@ +/* + * sound/sound.c + * + * Main sound driver for FreeBSD. This file provides the main + * entry points for probe/attach and all i/o demultiplexing, including + * default routines for generic devices. + * + * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it) + * + * 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. + * + * + * For each board type a template "snddev_info" structure contains + * all the relevant parameters, both for configuration and runtime. + * + * In this file we build tables of pointers to the descriptors for + * the various supported boards. The generic probe routine scans + * the table(s) looking for a matching entry, then invokes the + * board-specific probe routine. If successful, a pointer to the + * correct snddev_info is stored in snddev_last_probed, for subsequent + * use in the attach routine. The generic attach routine copies + * the template to a permanent descriptor (pcm_info[unit] and + * friends), initializes all generic parameters, and calls the + * board-specific attach routine. + * + * On device calls, the generic routines do the checks on unit and + * device parameters, then call the board-specific routines if + * available, or try to perform the task using the default code. + * + */ + +#include <i386/isa/snd/sound.h> + +#if NPCM > 0 /* from "snd.h" */ + +#define SNDSTAT_BUF_SIZE 4000 +static char status_buf[SNDSTAT_BUF_SIZE] ; +static int status_len = 0 ; +static void init_status(snddev_info *d); + +static d_open_t sndopen; +static d_close_t sndclose; +static d_ioctl_t sndioctl; +static d_read_t sndread; +static d_write_t sndwrite; +static d_mmap_t sndmmap; + +#define CDEV_MAJOR 30 +static struct cdevsw snd_cdevsw = { + sndopen, sndclose, sndread, sndwrite, + sndioctl, nxstop, nxreset, nxdevtotty, + sndselect, sndmmap, nxstrategy, "snd", + NULL, -1, +}; + +/* + * descriptors for active devices. + * + */ +snddev_info pcm_info[NPCM_MAX] ; +snddev_info midi_info[NPCM_MAX] ; +snddev_info synth_info[NPCM_MAX] ; + +u_long nsnd = NPCM ; /* total number of sound devices */ + +/* + * the probe routine can only return an int to the upper layer. Hence, + * it leaves the pointer to the last successfully + * probed device descriptor in snddev_last_probed + */ +snddev_info *snddev_last_probed = NULL ; + +static void print_isadev_info(struct isa_device *d, char *s); +static snddev_info * +generic_snd_probe(struct isa_device * dev, snddev_info **p[], char *s); + +/* + * here are the lists of known devices. Similar devices (e.g. all + * sb clones, all mss clones, ... are in the same array. + * All lists of devices of the same type (eg. all pcm, all midi...) + * are in the same array. + * Each probe for a device type gets the pointer to the main array + * and then scans the sublists. + * + * XXX should use DATA_SET to create a linker set for sb_devs and other + * such structures. + */ + +extern snddev_info sb_op_desc; +extern snddev_info mss_op_desc; + +static snddev_info *sb_devs[] = { + &sb_op_desc, + NULL, +} ; + +static snddev_info *mss_devs[] = { + /* MSS-like devices */ + &mss_op_desc, + NULL, +} ; + +static snddev_info **pcm_devslist[] = { + /* all pcm devices */ + mss_devs, + sb_devs, + NULL +} ; + + +int +pcmprobe(struct isa_device * dev) +{ + bzero(&pcm_info[dev->id_unit], sizeof(pcm_info[dev->id_unit]) ); + return generic_snd_probe(dev, pcm_devslist, "pcm") ? 1 : 0 ; +} + +static snddev_info **midi_devslist[] = { + /* all midi devices */ + NULL +} ; + +int +midiprobe(struct isa_device * dev) +{ + bzero(&midi_info[dev->id_unit], sizeof(midi_info[dev->id_unit]) ); + return 0 ; + return generic_snd_probe(dev, midi_devslist, "midi") ? 1 : 0 ; +} + +int +synthprobe(struct isa_device * dev) +{ + bzero(&synth_info[dev->id_unit], sizeof(synth_info[dev->id_unit]) ); + return 0 ; +} + +/* + * this is the generic attach routine + */ + +int +pcmattach(struct isa_device * dev) +{ + snddev_info *d = NULL ; + struct isa_device *dvp; + int stat = 0; + dev_t isadev; + + if ( (dev->id_unit >= NPCM_MAX) || /* too many devs */ + (snddev_last_probed == NULL) || /* last probe failed */ + (snddev_last_probed->attach==NULL) ) /* no attach routine */ + return 0 ; /* fail */ + + /* + * default initialization: copy generic parameters for the routine, + * initialize from the isa_device structure, and allocate memory. + * If everything succeeds, then call the attach routine for + * further initialization. + */ + pcm_info[dev->id_unit] = *snddev_last_probed ; + d = &pcm_info[dev->id_unit] ; + + d->io_base = dev->id_iobase ; + d->irq = ffs(dev->id_irq) - 1 ; + d->dma1 = dev->id_drq ; + if (dev->id_flags != -1 && dev->id_flags & DV_F_DUAL_DMA) /* enable dma2 */ + d->dma2 = dev->id_flags & DV_F_DRQ_MASK ; + else + d->dma2 = d->dma1 ; + /* XXX should also set bd_id from flags ? */ + d->status_ptr = 0; + + /* + * Allocates memory and initializes the dma structs properly. We + * use independent buffers for each channel. For the time being, + * this is done independently of the dma setting. In future + * revisions, if we see that we have a single dma, we might decide + * to use a single buffer to save memory. + */ + alloc_dbuf( &(d->dbuf_out), d->bufsize, d->dma1 ); + alloc_dbuf( &(d->dbuf_in), d->bufsize, d->dma2 ); + + isa_dma_acquire(d->dma1); + if (d->dma2 != d->dma1) + isa_dma_acquire(d->dma2); + /* + * initialize standard parameters for the device. This can be + * overridden by device-specific configurations but better do + * here the generic things. + */ + + d->play_speed = d->rec_speed = 8000 ; + d->play_blocksize = d->rec_blocksize = 2048 ; + d->play_fmt = d->rec_fmt = AFMT_MU_LAW ; + + isadev = makedev(CDEV_MAJOR, 0); + cdevsw_add(&isadev, &snd_cdevsw, NULL); + + /* + * should try and find a suitable value for id_id, otherwise + * the interrupt is not registered and dispatched properly. + * This is important for PnP devices, where "dev" is built on + * the fly and many field are not initialized. + */ + if (dev->id_driver == NULL) { + dev->id_driver = &pcmdriver ; + dvp=find_isadev(isa_devtab_tty, &pcmdriver, 0); + if (dvp) + dev->id_id = dvp->id_id; + } + + /* + * and finally, call the device attach routine + */ + stat = snddev_last_probed->attach(dev); + snddev_last_probed = NULL ; + + return stat ; +} + +int midiattach(struct isa_device * dev) { return 0 ; } +int synthattach(struct isa_device * dev) { return 0 ; } + +struct isa_driver pcmdriver = { pcmprobe, pcmattach, "pcm" } ; + +struct isa_driver mididriver = { midiprobe, midiattach, "midi" } ; +struct isa_driver synthdriver = { synthprobe, synthattach, "synth" } ; + +void +pcmintr(int unit) +{ + DEB(printf("__/\\/ pcmintr -- unit %d\n", unit)); + pcm_info[unit].interrupts++; + if (pcm_info[unit].isr) + pcm_info[unit].isr(unit); + if (midi_info[unit].isr) + midi_info[unit].isr(unit); + if (synth_info[unit].isr) + synth_info[unit].isr(unit); +} + +static void +print_isadev_info(struct isa_device *d, char *s) +{ + if (d == NULL ) + return ; + printf("%s%d at 0x%x irq %d drq %d mem 0x%x flags 0x%x en %d confl %d\n", + d->id_driver ? d->id_driver->name : "NONAME", + d->id_unit, + (u_short)(d->id_iobase), ffs(d->id_irq) - 1 , + d->id_drq, d->id_maddr, d->id_flags, + d->id_enabled, d->id_conflicts); +} + + +static snddev_info * +generic_snd_probe(struct isa_device * dev, snddev_info **p[], char *s) +{ + snddev_info **q ; + struct isa_device saved_dev ; + + snddev_last_probed = NULL ; + + print_isadev_info(dev, s); + + saved_dev = *dev ; /* the probe routine might alter parameters */ + + for ( ; p[0] != NULL ; p++ ) + for ( q = *p ; q[0] ; q++ ) + if (q[0]->probe && q[0]->probe(dev)) + return (snddev_last_probed = q[0]) ; + else + *dev = saved_dev ; + + return NULL ; +} + + +/* + * a small utility function which, given a device number, returns + * a pointer to the associated snddev_info struct, and sets the unit + * number. + */ +static snddev_info * +get_snddev_info(dev_t dev, int *unit) +{ + int u; + snddev_info *d = NULL ; + + dev = minor(dev); + u = dev >> 4 ; + if (unit) + *unit = u ; + + if (u > NPCM_MAX) + return NULL ; + switch(dev & 0x0f) { + case SND_DEV_CTL : /* /dev/mixer handled by pcm */ + case SND_DEV_STATUS : /* /dev/sndstat handled by pcm */ + case SND_DEV_SNDPROC : /* /dev/sndproc handled by pcm */ + case SND_DEV_DSP : + case SND_DEV_DSP16 : + case SND_DEV_AUDIO : + d = & pcm_info[u] ; + break ; + case SND_DEV_SEQ : + case SND_DEV_SEQ2 : + d = & synth_info[u] ; + break ; + case SND_DEV_MIDIN: + d = & midi_info[u] ; + break ; + default: + return NULL ; + } + return d ; +} + +/* + * here are the switches for the main functions. The switches do + * all necessary checks on the device number to make sure + * that the device is configured. They also provide some default + * functionalities so that device-specific drivers have to deal + * only with special cases. + */ + +static int +sndopen(dev_t i_dev, int flags, int mode, struct proc * p) +{ + int dev, unit ; + snddev_info *d; + + dev = minor(i_dev); + d = get_snddev_info(dev, &unit); + + DEB(printf("open snd%d subdev %d flags 0x%08x mode 0x%08x\n", + unit, dev & 0xf, flags, mode)); + + if (d == NULL) { + printf("open: unit %d dev %d not configured, perhaps you want unit %d ?\n", + unit, dev & 0xf, unit + 1 ); + return (ENXIO) ; + } + + switch(dev & 0x0f) { + case SND_DEV_CTL : /* mixer ... */ + return 0 ; /* always succeed */ + + case SND_DEV_STATUS : /* implemented right here */ + init_status(&pcm_info[unit]); + d->status_ptr = 0 ; + return 0 ; + + default: + if (d->open == NULL) { + printf("open: missing for unit %d\n", unit ); + return (ENXIO) ; + } else + return d->open(i_dev, flags, mode, p); + } + return ENXIO ; +} + +static int +sndclose(dev_t i_dev, int flags, int mode, struct proc * p) +{ + int dev, unit ; + snddev_info *d; + + dev = minor(i_dev); + d = get_snddev_info(dev, &unit); + + DEB(printf("close snd%d subdev %d\n", unit, dev & 0xf)); + + if (d == NULL) { + printf("close: unit %d dev %d not configured\n", unit, dev & 0xf ); + return (ENXIO) ; + } + switch(dev & 0x0f) { /* only those for which close makes sense */ + case SND_DEV_AUDIO : + case SND_DEV_DSP : + case SND_DEV_DSP16 : + case SND_DEV_SEQ: + case SND_DEV_SEQ2: + case SND_DEV_MIDIN: + if (d->close) + return d->close(i_dev, flags, mode, p); + } + return 0 ; +} + +static int +sndread(dev_t i_dev, struct uio * buf, int flag) +{ + int ret, dev, unit; + snddev_info *d ; + u_long s; + + dev = minor(i_dev); + + d = get_snddev_info(dev, &unit); + DEB(printf("read snd%d subdev %d flag 0x%08x\n", unit, dev & 0xf, flag)); + + if (d == NULL) { + printf("read: unit %d not configured\n", unit ); + return ENXIO ; + } + if ( (dev & 0x0f) == SND_DEV_STATUS ) { + int l, c; + u_char *p; + + l = buf->uio_resid; + s=spltty(); + c = status_len - d->status_ptr ; + if (c < 0) /* should not happen! */ + c = 0 ; + if (c < l) + l = c ; + p = status_buf + d->status_ptr ; + d->status_ptr += l ; + splx(s); + ret = uiomove(p, l, buf) ; + if (ret) + printf("pcm-stat: bad copyout\n"); + return ret ; + } + if (d->read) /* device-specific read */ + return d->read(i_dev, buf, flag); + + /* + * the generic read routine. device-specific stuff should only + * be in the dma-handling procedures. + */ + s = spltty(); + if ( d->flags & SND_F_READING ) { + /* another reader is in, deny request */ + splx(s); + DDB(printf("read denied, another reader is in\n")); + return EBUSY ; + } + if (d->dma1 == d->dma2) { /* half duplex */ + if ( d->flags & SND_F_WRITING ) { + /* another writer is in, deny request */ + splx(s); + DDB(printf("read denied, half duplex and a writer is in\n")); + return EBUSY ; + } + while ( d->flags & SND_F_WR_DMA ) { + /* + * we have a pending dma operation, post a read request + * and wait for the write to complete. + */ + d->flags |= SND_F_READING ; + DDB(printf("sndread: sleeping waiting for write to end\n")); + ret = tsleep( (caddr_t)&(d->dbuf_out), + PRIBIO | PCATCH , "sndrdw", hz ) ; + if (ret == ERESTART || ret == EINTR) { + d->flags &= ~SND_F_READING ; + splx(s); + return EINTR ; + } + } + } + d->flags |= SND_F_READING ; + splx(s); + + return dsp_read_body(d, buf); +} + +static int +sndwrite(dev_t i_dev, struct uio * buf, int flag) +{ + int ret, dev, unit; + snddev_info *d; + u_long s; + + dev = minor(i_dev); + d = get_snddev_info(dev, &unit); + + DEB(printf("write snd%d subdev %d flag 0x%08x\n", unit, dev & 0xf, flag)); + + if (d == NULL) { + printf("write: unit %d not configured\n", unit ); + return (ENXIO) ; + } + switch( dev & 0x0f) { /* only writeable devices */ + case SND_DEV_MIDIN: /* XXX is this writable ? */ + case SND_DEV_SEQ : + case SND_DEV_SEQ2 : + case SND_DEV_DSP : + case SND_DEV_DSP16 : + case SND_DEV_AUDIO : + break ; + default: + return EPERM ; /* for non-writeable devices ; */ + } + if (d->write) + return d->write(i_dev, buf, flag); + + /* + * Otherwise, use the generic write routine. device-specific + * stuff should only be in the dma-handling procedures. + */ + + s = spltty(); + if ( d->flags & SND_F_WRITING ) { + /* another writer is in, deny request */ + splx(s); + DDB(printf("write denied, another writer is in\n")); + return EBUSY ; + } + if (d->dma1 == d->dma2) { /* half duplex */ + if ( d->flags & SND_F_READING ) { + DDB(printf("write denied, half duplex and a reader is in\n")); + /* another reader is in, deny request */ + splx(s); + return EBUSY ; + } + while ( d->flags & SND_F_RD_DMA ) { + /* + * we have a pending read dma. Post a write request + * and wait for the read to complete (in fact I could + * abort the read dma... + */ + d->flags |= SND_F_WRITING ; + DEB(printf("sndwrite: sleeping waiting for read to end\n")); + ret = tsleep( (caddr_t)&(d->dbuf_out), + PRIBIO | PCATCH , "sndwr", hz ) ; + if (ret == ERESTART || ret == EINTR) { + d->flags &= ~SND_F_WRITING ; + splx(s); + return EINTR ; + } + } + } + d->flags |= SND_F_WRITING ; + splx(s); + + return dsp_write_body(d, buf); +} + +/* + * generic sound ioctl. Functions of the default driver can be + * overridden by the device-specific ioctl call. + * If a device-specific call returns ENOSYS (Function not implemented), + * the default driver is called. Otherwise, the returned value + * is passed up. + * + * The default handler, for many parameters, sets the value in the + * descriptor, sets SND_F_INIT, and calls the callback function with + * reason INIT. If successful, the callback returns 1 and the caller + * can update the parameter. + */ + +static int +sndioctl(dev_t i_dev, int cmd, caddr_t arg, int mode, struct proc * p) +{ + int ret = ENOSYS, dev, unit ; + snddev_info *d; + u_long s; + + dev = minor(i_dev); + d = get_snddev_info(dev, &unit); + + if (d == NULL) { + printf("ioctl: unit %d not configured\n", unit ); + return (ENXIO) ; + } + if (d->ioctl) + ret = d->ioctl(dev, cmd, arg, mode, p); + if (ret != ENOSYS) + return ret ; + + /* + * pass control to the default ioctl handler. Set ret to 0 now. + */ + ret = 0 ; + + /* + * The linux ioctl interface for the sound driver has a thousand + * different calls, and it is unpractical to put the names in + * the switch(). So we have some tests before for common routines, + * such as the ones related to the mixer. But we really ought + * to redesign the interface! + * + * Reading from the mixer just requires to look at the cached + * copy in d->mix_levels[dev], so this routine should cover + * practically all needs for mixer reading. + */ + if ( (cmd & MIXER_READ(0)) == MIXER_READ(0) && (cmd & 0xff) < 32 ) { + int dev = cmd & 0x1f ; + if ( d->mix_devs & (1<<dev) ) { /* supported */ + *(int *)arg = d->mix_levels[dev]; + return 0 ; + } else + return EINVAL ; + } + + /* + * all routines are called with int. blocked. Make sure that + * ints are re-enabled when calling slow or blocking functions! + */ + s = spltty(); + switch(cmd) { + + /* + * we start with the new ioctl interface. + */ + case AIONWRITE : /* how many bytes can write ? */ + if (d->flags & SND_F_WR_DMA) + dsp_wr_dmaupdate(d); + *(int *)arg = d->dbuf_out.fl; + break; + + case AIOSSIZE : /* set the current blocksize */ + { + struct snd_size *p = (struct snd_size *)arg; + if (p->play_size <= 1 && p->rec_size <= 1) { /* means no blocks */ + d->flags &= ~SND_F_HAS_SIZE ; + } else { + RANGE (p->play_size, 40, d->bufsize /4); + d->play_blocksize = p->play_size & ~3 ; + RANGE (p->rec_size, 40, d->bufsize /4); + d->rec_blocksize = p->rec_size & ~3 ; + d->flags |= SND_F_HAS_SIZE ; + } + } + splx(s); + ask_init(d); + /* FALLTHROUGH */ + case AIOGSIZE : /* get the current blocksize */ + { + struct snd_size *p = (struct snd_size *)arg; + p->play_size = d->play_blocksize ; + p->rec_size = d->rec_blocksize ; + } + break ; + + case AIOSFMT : + { + snd_chan_param *p = (snd_chan_param *)arg; + /* + * at the moment, only support same format on play & rec + */ + d->play_speed = p->play_rate; + d->rec_speed = p->play_rate; /* XXX */ + if (p->play_format & SND_F_STEREO) + d->flags |= SND_F_STEREO ; + else + d->flags &= ~SND_F_STEREO ; + d->play_fmt = p->play_format & ~AFMT_STEREO ; + d->rec_fmt = p->rec_format & ~AFMT_STEREO ; + } + splx(s); + if (!ask_init(d)) + break ; /* could not reinit */ + /* FALLTHROUGH */ + + case AIOGFMT : + { + snd_chan_param *p = (snd_chan_param *)arg; + p->play_rate = d->play_speed; + p->rec_rate = d->rec_speed; + p->play_format = d->play_fmt; + p->rec_format = d->rec_fmt; + if (d->flags & SND_F_STEREO) { + p->play_format |= AFMT_STEREO ; + p->rec_format |= AFMT_STEREO ; + } + } + break; + + case AIOGCAP : /* get capabilities */ + /* this should really be implemented by the driver */ + { + snd_capabilities *p = (snd_capabilities *)arg; + p->rate_min = 5000; + p->rate_max = 48000; /* default */ + p->bufsize = d->bufsize; + p->formats = d->audio_fmt; /* default */ + p->mixers = 1 ; /* default: one mixer */ + p->inputs = d->mix_devs ; + p->left = p->right = 255 ; + } + break ; + + case AIOSTOP: + if (*(int *)arg == AIOSYNC_PLAY) /* play */ + *(int *)arg = dsp_wrabort(d); + else if (*(int *)arg == AIOSYNC_CAPTURE) + *(int *)arg = dsp_rdabort(d); + else { + splx(s); + printf("AIOSTOP: bad channel 0x%x\n", *(int *)arg); + *(int *)arg = 0 ; + } + break ; + + case AIOSYNC: + printf("AIOSYNC chan 0x%03x pos %d unimplemented\n", + ((snd_sync_parm *)arg)->chan, + ((snd_sync_parm *)arg)->pos); + break; + /* + * here follow the standard ioctls (filio.h etc.) + */ + case FIONREAD : /* get # bytes to read */ + if ( d->flags & SND_F_RD_DMA ) + dsp_rd_dmaupdate(d); + *(int *)arg = d->dbuf_in.rl; + break; + + case FIOASYNC: /*set/clear async i/o */ + printf("FIOASYNC\n"); + break; + + case SNDCTL_DSP_NONBLOCK : + case FIONBIO : /* set/clear non-blocking i/o */ + if ( *(int *)arg == 0 ) + d->flags &= ~SND_F_NBIO ; + else + d->flags |= SND_F_NBIO ; + break ; + + /* + * Finally, here is the linux-compatible ioctl interface + */ + case SNDCTL_DSP_GETBLKSIZE: + *(int *) arg = d->play_blocksize ; + break ; + + case SNDCTL_DSP_SETBLKSIZE : + { + int t = *(int *)arg; + if (t <= 1) { /* means no blocks */ + d->flags &= ~SND_F_HAS_SIZE ; + } else { + RANGE (t, 40, d->bufsize /4); + d->play_blocksize = + d->rec_blocksize = t & ~3 ; /* align to multiple of 4 */ + d->flags |= SND_F_HAS_SIZE ; + } + } + splx(s); + ask_init(d); + break ; + case SNDCTL_DSP_RESET: + printf("dsp reset\n"); + dsp_wrabort(d); + dsp_rdabort(d); + break ; + + case SNDCTL_DSP_SYNC: + printf("dsp sync\n"); + splx(s); + snd_sync(d, 1, d->bufsize - 4); /* DMA does not start with <4 bytes */ + break ; + + case SNDCTL_DSP_SPEED: + d->play_speed = d->rec_speed = *(int *)arg ; + splx(s); + if (ask_init(d)) + *(int *)arg = d->play_speed ; + break ; + + case SNDCTL_DSP_STEREO: + if ( *(int *)arg == 0 ) + d->flags &= ~SND_F_STEREO ; /* mono */ + else if ( *(int *)arg == 1 ) + d->flags |= SND_F_STEREO ; /* stereo */ + else { + printf("dsp stereo: %d is invalid, assuming 1\n", *(int *)arg ); + d->flags |= SND_F_STEREO ; /* stereo */ + } + splx(s); + if (ask_init(d)) + *(int *)arg = (d->flags & SND_F_STEREO) ? 1 : 0 ; + break ; + + case SOUND_PCM_WRITE_CHANNELS: + printf("dsp write channels %d\n", *(int *)arg); + if ( *(int *)arg == 1) + d->flags &= ~SND_F_STEREO ; /* mono */ + else if ( *(int *)arg == 2) + d->flags |= SND_F_STEREO ; /* stereo */ + else { + ret = EINVAL ; + break ; + } + splx(s); + if (ask_init(d)) + *(int *)arg = (d->flags & SND_F_STEREO) ? 2 : 1 ; + break ; + + case SOUND_PCM_READ_RATE: + *(int *)arg = d->play_speed; + break ; + + case SOUND_PCM_READ_CHANNELS: + *(int *)arg = (d->flags & SND_F_STEREO) ? 2 : 1; + break ; + + case SNDCTL_DSP_GETFMTS: /* returns a mask of supported fmts */ + *(int *)arg = (int)d->audio_fmt ; + break ; + + case SNDCTL_DSP_SETFMT: /* sets _one_ format */ + d->play_fmt = d->rec_fmt = *(int *)arg ; + splx(s); + if (ask_init(d)) + *(int *)arg = d->play_fmt ; + break ; + + case SNDCTL_DSP_SUBDIVIDE: + /* XXX watch out, this is RW! */ + printf("SNDCTL_DSP_SUBDIVIDE yet unimplemented\n"); + break; + + case SNDCTL_DSP_SETFRAGMENT: + /* XXX watch out, this is RW! */ + printf("SNDCTL_DSP_SETFRAGMENT 0x%08x\n", *(int *)arg); + { + int bytes, count; + bytes = *(int *)arg & 0xffff ; + count = ( *(int *)arg >> 16) & 0xffff ; + if (bytes < 7 || bytes > 15) + return EINVAL ; + d->play_blocksize = + d->rec_blocksize = min ( 1<< bytes, d->dbuf_in.bufsize) ; + count = d->dbuf_in.bufsize / d->play_blocksize ; + bytes = ffs(d->play_blocksize) - 1; +#if 0 + /* + * don't change arg, since it's fake anyways and some + * programs might fail if we do. + */ + *(int *)arg = (count << 16) | bytes ; +#endif + } + break ; + + case SNDCTL_DSP_GETISPACE: + /* return space available in the input queue */ + ((audio_buf_info *)arg)->bytes = d->dbuf_in.fl ; + ((audio_buf_info *)arg)->fragments = 1 ; + ((audio_buf_info *)arg)->fragstotal = + d->bufsize / d->play_blocksize ; + ((audio_buf_info *)arg)->fragsize = d->play_blocksize ; + break ; + + case SNDCTL_DSP_GETOSPACE: + /* return space available in the output queue */ + ((audio_buf_info *)arg)->bytes = d->dbuf_out.fl ; + ((audio_buf_info *)arg)->fragments = 1 ; + ((audio_buf_info *)arg)->fragstotal = + d->bufsize / d->play_blocksize ; + ((audio_buf_info *)arg)->fragsize = d->play_blocksize ; + break ; + + case SNDCTL_DSP_GETCAPS : + printf("dsp getcaps\n"); + break ; + + case SOUND_PCM_READ_BITS: + if (d->play_fmt == AFMT_S16_LE) + *(int *) arg = 16 ; + else + *(int *) arg = 8 ; + break ; + + /* + * mixer calls + */ + + case SOUND_MIXER_READ_DEVMASK : + case SOUND_MIXER_READ_STEREODEVS : + *(int *)arg = d->mix_devs; + break ; + + case SOUND_MIXER_READ_RECMASK : + *(int *)arg = d->mix_rec_devs; + break ; + + case SOUND_MIXER_READ_RECSRC : + *(int *)arg = d->mix_recsrc ; + break; + + default: + DEB(printf("default ioctl snd%d subdev %d fn 0x%08x fail\n", + unit, dev & 0xf, cmd)); + ret = EINVAL; + break ; + } + splx(s); + return ret ; +} + +int +sndselect(dev_t i_dev, int rw, struct proc * p) +{ + int dev, unit, c = 1 /* default: success */ ; + snddev_info *d ; + u_long flags; + + dev = minor(i_dev); + d = get_snddev_info(dev, &unit); + DEB(printf("sndselect dev 0x%04x rw 0x%08x\n",i_dev, rw)); + if (d == NULL ) { + printf("select: unit %d not configured\n", unit ); + return (ENXIO) ; + } + if (d->select == NULL) + return 1 ; /* always success ? */ + else if (d->select != sndselect ) + return d->select(i_dev, rw, p); + else { + /* handle it here with the generic code */ + + int lim ; + + /* + * if the user selected a block size, then we want to use the + * device as a block device, and select will return ready when + * we have a full block. + * In all other cases, select will return when 1 byte is ready. + */ + lim = 1; + switch(rw) { + case FWRITE : + if ( d->flags & SND_F_HAS_SIZE ) + lim = d->play_blocksize ; + /* XXX fix the test here for half duplex devices */ + if (1 /* write is compatible with current mode */) { + flags = spltty(); + if (d->flags & SND_F_WR_DMA) + dsp_wr_dmaupdate(d); + c = d->dbuf_out.fl ; + if (c < lim) /* no space available */ + selrecord(p, & (d->wsel)); + splx(flags); + } + return c < lim ? 0 : 1 ; + + case FREAD : + if ( d->flags & SND_F_HAS_SIZE ) + lim = d->rec_blocksize ; + /* XXX fix the test here */ + if (1 /* read is compatible with current mode */) { + flags = spltty(); + if ( !(d->flags & SND_F_RD_DMA) ) /* dma idle, restart it */ + dsp_rdintr(d); + else + dsp_rd_dmaupdate(d); + c = d->dbuf_in.rl ; + if (c < lim) /* no data available */ + selrecord(p, & (d->rsel)); + splx(flags); + } + DEB(printf("sndselect on read: %d >= %d flags 0x%08x\n", + c, lim, d->flags)); + return c < lim ? 0 : 1 ; + + case 0 : + DDB(printf("select on exceptions, unimplemented\n")); + return 1; + } + } + return ENXIO ; /* notreached */ +} + +/* + * The mmap interface allows access to the play and read buffer, + * plus the device descriptor. + * The various blocks are accessible at the following offsets: + * + * 0x00000000 ( 0 ) : write buffer ; + * 0x01000000 (16 MB) : read buffer ; + * 0x02000000 (32 MB) : device descriptor (dangerous!) + * + * WARNING: the mmap routines assume memory areas are aligned. This + * is true (probably) for the dma buffers, but likely false for the + * device descriptor. As a consequence, we do not know where it is + * located in the requested area. + */ +#include <sys/mman.h> +#include <vm/vm.h> +#include <vm/vm_kern.h> +#include <vm/vm_param.h> +#include <vm/pmap.h> +#include <vm/vm_extern.h> + +static int +sndmmap(dev_t dev, int offset, int nprot) +{ + snddev_info *d = get_snddev_info(dev, NULL); + + DEB(printf("sndmmap d 0x%08x dev 0x%04x ofs 0x%08x nprot 0x%08x\n", + d, dev, offset, nprot)); + + if (d == NULL || nprot & PROT_EXEC) + return -1 ; /* forbidden */ + + if (offset > d->bufsize && (nprot & PROT_WRITE) ) + return -1 ; /* can only write to the first block */ + + if (offset < d->bufsize) + return i386_btop(vtophys(d->dbuf_out.buf + offset)); + offset -= 1 << 24; + if ( (offset >= 0) && (offset < d->bufsize)) + return i386_btop(vtophys(d->dbuf_in.buf + offset)); + offset -= 1 << 24; + if ( (offset >= 0) && (offset < 0x2000)) { + printf("name %s\n", d->name); + return i386_btop(vtophys( ((int)d & ~0xfff) + offset)); + } + return -1 ; +} + + +/* + * ask_init sets the init flag in the device descriptor, and + * possibly calls the appropriate callback routine, returning 1 + * if the callback was successful. This enables ioctls handler for + * rw parameters to read back the updated value. + * Since the init callback can be slow, ask_init() should be called + * with interrupts enabled. + */ + +int +ask_init(snddev_info *d) +{ + u_long s; + + if ( d->callback == NULL ) + return 0 ; + s = spltty(); + if ( d->flags & SND_F_PENDING_IO ) { + /* cannot do it now, record the request and return */ + d->flags |= SND_F_INIT ; + splx(s); + return 0 ; + } else { + splx(s); + d->callback(d, SND_CB_INIT ); + return 1; + } +} + +/* + * these are the functions for the soundstat device. We copy parameters + * from the device info structure to static variables, and from there + * back to the structure when done. + */ + +static void +init_status(snddev_info *d) +{ + /* + * Write the status information to the status_buf and update + * status_len. There is a limit of SNDSTAT_BUF_SIZE bytes for the data. + * put_status handles this and returns 0 in case of failure. Since + * it never oveflows the buffer, we do not care to check. + */ + + int i; + + if (status_len != 0) /* only do init once */ + return ; + sprintf(status_buf, + "FreeBSD Sound Driver " __DATE__ " " __TIME__ "\n" + "Installed devices:\n"); + + for (i = 0; i < NPCM_MAX; i++) { + if (pcm_info[i].open) + sprintf(status_buf + strlen(status_buf), + "pcm%d: <%s> at 0x%x irq %d dma %d:%d\n", + i, pcm_info[i].name, pcm_info[i].io_base, + pcm_info[i].irq, + pcm_info[i].dma1, pcm_info[i].dma2); + if (midi_info[i].open) + sprintf(status_buf + strlen(status_buf), + "midi%d: <%s> at 0x%x irq %d dma %d:%d\n", + i, midi_info[i].name, midi_info[i].io_base, + midi_info[i].irq, + midi_info[i].dma1, midi_info[i].dma2); + if (synth_info[i].open) + sprintf(status_buf + strlen(status_buf), + "synth%d: <%s> at 0x%x irq %d dma %d:%d\n", + i, synth_info[i].name, synth_info[i].io_base, + synth_info[i].irq, + synth_info[i].dma1, synth_info[i].dma2); + } + status_len = strlen(status_buf) ; +} + +/* + * finally, some "libraries" + */ + +/* + * isa_dmastatus1() is a wrapper for isa_dmastatus(), which + * might return -1 or -2 in some cases (errors). Since for the + * user code it is more comfortable not to check for these cases, + * negative values are mapped back to 0 (which is reasonable). + */ + +int +isa_dmastatus1(int channel) +{ + int r = isa_dmastatus(channel); + if (r<0) r = 0; + return r; +} + +/* + * snd_conflict scans already-attached boards to see if + * the current address is conflicting with one of the already + * assigned ones. Returns 1 if a conflict is detected. + */ +int +snd_conflict(int io_base) +{ + int i; + for (i=0; i< NPCM_MAX ; i++) { + if ( (io_base == pcm_info[i].io_base ) || + (io_base == pcm_info[i].alt_base ) || + (io_base == pcm_info[i].conf_base) || + (io_base == pcm_info[i].mix_base ) || + (io_base == pcm_info[i].midi_base) || + (io_base == pcm_info[i].synth_base) ) { + printf("device at 0x%x already attached as unit %d\n", + io_base, i); + return 1 ; + } + } + return 0; +} + +void +snd_set_blocksize(snddev_info *d) +{ + /* + * now set the blocksize so as to guarantee approx 1/4s + * between callbacks. + */ + if ( (d->flags & SND_F_HAS_SIZE) == 0) { + /* make blocksize adaptive to 250ms or max 1/4 of the buf */ + int tmp ; + tmp = d->play_speed; + if (d->flags & SND_F_STEREO) tmp += tmp; + if (d->play_fmt & (AFMT_S16_LE|AFMT_U16_LE)) tmp += tmp; + d->play_blocksize = (tmp / 4) & ~3; /* 0.25s, aligned to 4 */ + RANGE (d->play_blocksize, 1024, (d->bufsize / 4) & ~3); + + tmp = d->rec_speed; + if (d->flags & SND_F_STEREO) tmp += tmp; + if (d->rec_fmt & (AFMT_S16_LE|AFMT_U16_LE)) tmp += tmp; + d->rec_blocksize = (tmp / 4) & ~3; /* 0.25s, aligned to 4 */ + RANGE (d->rec_blocksize, 1024, (d->bufsize / 4) & ~3); + } +} + +/* + * The various mixers use a variety of bitmasks etc. The Voxware + * driver had a very nice technique to describe a mixer and interface + * to it. A table defines, for each channel, which register, bits, + * offset, polarity to use. This procedure creates the new value + * using the table and the old value. + */ + +void +change_bits(mixer_tab *t, u_char *regval, int dev, int chn, int newval) +{ + u_char mask; + int shift; + + DEB(printf("ch_bits dev %d ch %d val %d old 0x%02x " + "r %d p %d bit %d off %d\n", + dev, chn, newval, *regval, + (*t)[dev][chn].regno, (*t)[dev][chn].polarity, + (*t)[dev][chn].nbits, (*t)[dev][chn].bitoffs ) ); + + if ( (*t)[dev][chn].polarity == 1) /* reverse */ + newval = 100 - newval ; + + mask = (1 << (*t)[dev][chn].nbits) - 1; + newval = (int) ((newval * mask) + 50) / 100; /* Scale it */ + shift = (*t)[dev][chn].bitoffs /*- (*t)[dev][LEFT_CHN].nbits + 1*/; + + *regval &= ~(mask << shift); /* Filter out the previous value */ + *regval |= (newval & mask) << shift; /* Set the new value */ +} + + +/* + * code for translating between U8 and ULAW. Needed to support + * /dev/audio on the SoundBlaster. Actually, we would also need + * ulaw -> 16 bits (for the soundblaster as well, when used in + * full-duplex) + */ + +#if 1 +void +translate_bytes (u_char *table, u_char *buff, int n) +{ + u_long i; + + if (n <= 0) + return; + + for (i = 0; i < n; ++i) + buff[i] = table[buff[i]]; +} +#else +/* inline */ +void +translate_bytes (const void *table, void *buff, int n) +{ + if (n > 0) { + __asm__ ( " cld\n" + "1: lodsb\n" + " xlatb\n" + " stosb\n" + " loop 1b\n": + : "b" ((long) table), "c" (n), "D" ((long) buff), "S" ((long) buff) + : "bx", "cx", "di", "si", "ax"); + } +} + +#endif + +#endif /* NPCM > 0 */ diff --git a/sys/i386/isa/snd/sound.h b/sys/i386/isa/snd/sound.h new file mode 100644 index 0000000..00e15ed --- /dev/null +++ b/sys/i386/isa/snd/sound.h @@ -0,0 +1,492 @@ +/* + * sound.h + * + * include file for kernel sources, sound driver. + * + * Copyright by Hannu Savolainen 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. + * + */ + +#include "pcm.h" +#if NPCM > 0 + +/* + * first, include kernel header files. + */ + +#ifndef _OS_H_ +#define _OS_H_ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/ioccom.h> + +#include <sys/filio.h> +#include <sys/sockio.h> +#include <sys/fcntl.h> +#include <sys/tty.h> +#include <sys/proc.h> + +#include <sys/kernel.h> /* for DATA_SET */ + +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/uio.h> +#include <sys/syslog.h> +#include <sys/errno.h> +#include <sys/malloc.h> +#include <sys/buf.h> +#include <i386/isa/isa_device.h> + +#include <machine/clock.h> /* for DELAY */ + +typedef void (irq_proc_t) (int irq); + +#endif /* _OS_H_ */ + +/* + * descriptor of a dma buffer. See dmabuf.c for documentation. + * (rp,rl) and (fp,fl) identify the READY and FREE regions of the + * buffer. (dp,dl) identify the region currently used by the DMA. + * Only dmabuf.c should test the value of dl. The reason is that + * in auto-dma mode looking at dl alone does not make much sense, + * since the transfer potentially spans the entire buffer. + */ + +typedef struct _snd_dbuf { + char *buf; + int bufsize ; + volatile int dp, rp, fp; + volatile int dl, rl, fl; + volatile int dl0; /* value used last time in dl */ + int int_count; +} snd_dbuf ; + +/* + * descriptor of audio operations ... + * + */ +typedef struct _snddev_info snddev_info ; +typedef int (snd_callback_t)(snddev_info *d, int reason); + +struct _snddev_info { + + /* + * the first part of the descriptor is filled up from a + * template. + */ + char name[64]; + + int type ; + + int (*probe)(struct isa_device * dev); + int (*attach)(struct isa_device * dev) ; + d_open_t *open ; + d_close_t *close ; + d_read_t *read ; + d_write_t *write ; + d_ioctl_t *ioctl ; + d_select_t *select ; + irq_proc_t *isr ; + snd_callback_t *callback; + + int bufsize; /* space used for buffers */ + + u_long audio_fmt ; /* supported audio formats */ + + + /* + * combinations of the following flags are used as second argument in + * the callback from the dma module to the device-specific routines. + */ + +#define SND_CB_RD 0x100 /* read callback */ +#define SND_CB_WR 0x200 /* write callback */ +#define SND_CB_REASON_MASK 0xff +#define SND_CB_START 0x01 /* start dma op */ +#define SND_CB_RESTART 0x02 /* restart dma op */ +#define SND_CB_STOP 0x03 /* stop dma op */ +#define SND_CB_ABORT 0x04 /* abort dma op */ +#define SND_CB_INIT 0x05 /* init board parameters */ + /* init can only be called with int enabled and + * no pending DMA activity. + */ + + /* + * whereas from here, parameters are set at runtime. + */ + + int io_base ; /* primary I/O address for the board */ + int alt_base ; /* some codecs are accessible as SB+WSS... */ + int conf_base ; /* and the opti931 also has a config space */ + int mix_base ; /* base for the mixer... */ + int midi_base ; /* base for the midi */ + int synth_base ; /* base for the synth */ + + int irq ; + int dma1, dma2 ; /* dma2=dma1 for half-duplex cards */ + + int bd_id ; /* used to hold board-id info, eg. sb version, + * mss codec type, etc. etc. + */ + + snd_dbuf dbuf_out, dbuf_in; + + int status_ptr; /* used to implement sndstat */ + + /* + * these parameters describe the operation of the board. + * Generic things like busy flag, speed, etc are here. + */ + + volatile u_long flags ; /* 32 bits, used for various purposes. */ + + /* + * we have separate flags for read and write, although in some + * cases this is probably not necessary (e.g. because we cannot + * know how many processes are using the device, we cannot + * distinguish if open, close, abort are for a write or for a + * read). + */ + + /* + * the following flag is used by open-close routines + * to mark the status of the device. + */ +#define SND_F_BUSY 0x0001 /* has been opened */ + /* + * Only the last close for a device will propagate to the driver. + * Unfortunately, voxware uses 3 different minor numbers + * (dsp, dsp16 and audio) to access the same unit. So, if + * we want to support multiple opens and still keep track of + * what is happening, we also need a separate flag for each minor + * number. These are below... + */ +#define SND_F_BUSY_AUDIO 0x10000000 +#define SND_F_BUSY_DSP 0x20000000 +#define SND_F_BUSY_DSP16 0x40000000 +#define SND_F_BUSY_ANY 0x70000000 + /* + * the next two are used to allow only one pending operation of + * each type. + */ +#define SND_F_READING 0x0004 /* have a pending read */ +#define SND_F_WRITING 0x0008 /* have a pending write */ + /* + * these mark pending DMA operations. When you have pending dma ops, + * you might get interrupts, so some manipulations of the + * descriptors must be done with interrupts blocked. + */ +#define SND_F_RD_DMA 0x0010 /* read-dma active */ +#define SND_F_WR_DMA 0x0020 /* write-dma active */ + +#define SND_F_PENDING_IN (SND_F_READING | SND_F_RD_DMA) +#define SND_F_PENDING_OUT (SND_F_WRITING | SND_F_WR_DMA) +#define SND_F_PENDING_IO (SND_F_PENDING_IN | SND_F_PENDING_OUT) + + /* + * flag used to mark a pending close. + */ +#define SND_F_CLOSING 0x0040 /* a pending close */ + + /* + * if user has not set block size, then make it adaptive + * (0.25s, or the perhaps last read/write ?) + */ +#define SND_F_HAS_SIZE 0x0080 /* user set block size */ + /* + * assorted flags related to operating mode. + */ +#define SND_F_STEREO 0x0100 /* doing stereo */ +#define SND_F_NBIO 0x0200 /* do non-blocking i/o */ + + /* + * the user requested ulaw, but the board does not support it + * natively, so a (software) format conversion is necessary. + * The kernel is not really the place to do this, but since + * many applications expect to use /dev/audio , we do it for + * portability. + */ +#define SND_F_XLAT8 0x0400 /* u-law <--> 8-bit unsigned */ +#define SND_F_XLAT16 0x0800 /* u-law <--> 16-bit signed */ + + /* + * these flags mark a pending abort on a r/w operation. + */ +#define SND_F_ABORTING 0x1000 /* a pending abort */ + + /* + * this is used to mark that board initialization is needed, e.g. + * because of a change in sampling rate, format, etc. -- It will + * be done at the next convenient time. + */ +#define SND_F_INIT 0x4000 /* changed parameters. need init */ +#define SND_F_AUTO_DMA 0x8000 /* use auto-dma */ + + u_long bd_flags; /* board-specific flags */ + int play_speed, rec_speed; + + int play_blocksize, rec_blocksize; /* blocksize for io and dma ops */ + u_long play_fmt, rec_fmt ; /* current audio format */ + + /* + * mixer parameters + */ + u_long mix_devs; /* existing devices for mixer */ + u_long mix_rec_devs; /* possible recording sources */ + u_long mix_recsrc; /* current recording source(s) */ + u_short mix_levels[32]; + + struct selinfo wsel, rsel, esel ; + u_long interrupts; /* counter of interrupts */ + void *device_data ; /* just in case it is needed...*/ +} ; + +/* + * then ioctls and other stuff + */ + +#define NPCM_MAX 8 /* Number of supported devices */ + +/* + * Supported card ID numbers (were in soundcard.h...) + */ + +#define SNDCARD_ADLIB 1 +#define SNDCARD_SB 2 +#define SNDCARD_PAS 3 +#define SNDCARD_GUS 4 +#define SNDCARD_MPU401 5 +#define SNDCARD_SB16 6 +#define SNDCARD_SB16MIDI 7 +#define SNDCARD_UART6850 8 +#define SNDCARD_GUS16 9 +#define SNDCARD_MSS 10 +#define SNDCARD_PSS 11 +#define SNDCARD_SSCAPE 12 +#define SNDCARD_PSS_MPU 13 +#define SNDCARD_PSS_MSS 14 +#define SNDCARD_SSCAPE_MSS 15 +#define SNDCARD_TRXPRO 16 +#define SNDCARD_TRXPRO_SB 17 +#define SNDCARD_TRXPRO_MPU 18 +#define SNDCARD_MAD16 19 +#define SNDCARD_MAD16_MPU 20 +#define SNDCARD_CS4232 21 +#define SNDCARD_CS4232_MPU 22 +#define SNDCARD_MAUI 23 +#define SNDCARD_PSEUDO_MSS 24 /* MSS without WSS regs.*/ +#define SNDCARD_AWE32 25 + +/* + * values used in bd_id for the mss boards + */ +#define MD_AD1848 0x91 +#define MD_AD1845 0x92 +#define MD_CS4248 0xA1 +#define MD_CS4231 0xA2 +#define MD_CS4231A 0xA3 +#define MD_CS4232 0xA4 +#define MD_CS4232A 0xA5 +#define MD_CS4236 0xA6 +#define MD_OPTI931 0xB1 + +/* + * TODO: add some card classes rather than specific types. + */ +#include <i386/isa/snd/soundcard.h> + +/* + * many variables should be reduced to a range. Here define a macro + */ + +#define RANGE(var, low, high) (var) = \ + ((var)<(low)?(low) : (var)>(high)?(high) : (var)) + +/* + * finally, all default parameters + */ +#define DSP_BUFFSIZE 65536 /* XXX */ + +#if 1 /* prepare for pnp support! */ +#include "pnp.h" +#if NPNP > 0 +#include <i386/isa/pnp.h> /* XXX pnp support */ +#endif +#endif + +/* + * Minor numbers for the sound driver. + * + * Unfortunately Creative called the codec chip of SB as a DSP. For this + * reason the /dev/dsp is reserved for digitized audio use. There is a + * device for true DSP processors but it will be called something else. + * In v3.0 it's /dev/sndproc but this could be a temporary solution. + */ + + +#define SND_DEV_CTL 0 /* Control port /dev/mixer */ +#define SND_DEV_SEQ 1 /* Sequencer output /dev/sequencer (FM + synthesizer and MIDI output) */ +#define SND_DEV_MIDIN 2 /* Raw midi access */ +#define SND_DEV_DSP 3 /* Digitized voice /dev/dsp */ +#define SND_DEV_AUDIO 4 /* Sparc compatible /dev/audio */ +#define SND_DEV_DSP16 5 /* Like /dev/dsp but 16 bits/sample */ +#define SND_DEV_STATUS 6 /* /dev/sndstat */ + /* #7 not in use now. Was in 2.4. Free for use after v3.0. */ +#define SND_DEV_SEQ2 8 /* /dev/sequencer, level 2 interface */ +#define SND_DEV_SNDPROC 9 /* /dev/sndproc for programmable devices */ +#define SND_DEV_PSS SND_DEV_SNDPROC + +#define DSP_DEFAULT_SPEED 8000 + +#define ON 1 +#define OFF 0 + + +#define SYNTH_MAX_VOICES 32 + +struct voice_alloc_info { + int max_voice; + int used_voices; + int ptr; /* For device specific use */ + u_short map[SYNTH_MAX_VOICES]; /* (ch << 8) | (note+1) */ + int timestamp; + int alloc_times[SYNTH_MAX_VOICES]; +}; + +struct channel_info { + int pgm_num; + int bender_value; + u_char controllers[128]; +}; + +/* + * mixer description structure and macros + */ + +struct mixer_def { + u_int regno:7; + u_int polarity:1; /* 1 means reversed */ + u_int bitoffs:4; + u_int nbits:4; +}; +typedef struct mixer_def mixer_ent; +typedef struct mixer_def mixer_tab[32][2]; + +#define MIX_ENT(name, reg_l, pol_l, pos_l, len_l, reg_r, pol_r, pos_r, len_r) \ + {{reg_l, pol_l, pos_l, len_l}, {reg_r, pol_r, pos_r, len_r}} +#define PMIX_ENT(name, reg_l, pos_l, len_l, reg_r, pos_r, len_r) \ + {{reg_l, 0, pos_l, len_l}, {reg_r, 0, pos_r, len_r}} + +#define DDB(x) x /* XXX */ + +#ifndef DEB +#define DEB(x) +#endif +#ifndef DDB +#define DDB(x) +#endif + +extern snddev_info pcm_info[NPCM_MAX] ; +extern snddev_info midi_info[NPCM_MAX] ; +extern snddev_info synth_info[NPCM_MAX] ; + +extern u_long nsnd ; +extern snddev_info *snddev_last_probed; + +int pcmprobe(struct isa_device * dev); +int midiprobe(struct isa_device * dev); +int synthprobe(struct isa_device * dev); +int pcmattach(struct isa_device * dev); +int midiattach(struct isa_device * dev); +int synthattach(struct isa_device * dev); + +/* + * functions in isa.c + */ + +int isa_dmastatus(int chan); +int isa_dmastop(int chan); +/* + * DMA buffer calls + */ + +void dsp_wrintr(snddev_info *d); +void dsp_rdintr(snddev_info *d); +int dsp_write_body(snddev_info *d, struct uio *buf); +int dsp_read_body(snddev_info *d, struct uio *buf); +void alloc_dbuf(snd_dbuf *d, int size, int b16); +void reset_dbuf(snd_dbuf *b); +int snd_flush(snddev_info *d); +int snd_sync(snddev_info *d, int chan, int threshold); +int dsp_wrabort(snddev_info *d); +int dsp_rdabort(snddev_info *d); +void dsp_wr_dmaupdate(snddev_info *d); +void dsp_rd_dmaupdate(snddev_info *d); + +d_select_t sndselect; + +/* + * library functions (in sound.c) + */ + +int ask_init(snddev_info *d); +void translate_bytes(u_char *table, u_char *buff, int n); +void change_bits(mixer_tab *t, u_char *regval, int dev, int chn, int newval); +int snd_conflict(int io_base); +void snd_set_blocksize(snddev_info *d); +int isa_dmastatus1(int channel); +/* + * routines in ad1848.c and sb_dsp.c which others might use + */ +int mss_detect (struct isa_device *dev); +int sb_cmd (int io_base, u_char cmd); +int sb_cmd2 (int io_base, u_char cmd, int val); +int sb_cmd3 (int io_base, u_char cmd, int val); +int sb_reset_dsp (int io_base); +void sb_setmixer (int io_base, u_int port, u_int value); +int sb_getmixer (int io_base, u_int port); + + +/* + * usage of flags in device config entry (config file) + */ + +#define DV_F_DRQ_MASK 0x00000007 /* mask for secondary drq */ +#define DV_F_DUAL_DMA 0x00000010 /* set to use secondary dma channel */ +#define DV_F_DEV_MASK 0x0000ff00 /* force device type/class */ +#define DV_F_DEV_SHIFT 8 /* force device type/class */ + +/* + * some flags are used in a device-specific manner, so that values can + * be used multiple times. + */ + +#define DV_F_TRUE_MSS 0x00010000 /* mss _with_ base regs */ + /* almost all modern cards do not have this set of registers, + * so it is better to make this the default behaviour + */ + +#endif diff --git a/sys/i386/isa/snd/soundcard.h b/sys/i386/isa/snd/soundcard.h new file mode 100644 index 0000000..276b936 --- /dev/null +++ b/sys/i386/isa/snd/soundcard.h @@ -0,0 +1,1297 @@ +/* + * soundcard.h + * + * Copyright by Hannu Savolainen 1993 + * Modified for the new FreeBSD sound driver by Luigi Rizzo, 1997 + * + * 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. + */ + +#ifndef SOUNDCARD_H +#define SOUNDCARD_H + /* + * If you make modifications to this file, please contact me before + * distributing the modified version. There is already enough + * diversity in the world. + * + * Regards, + * Hannu Savolainen + * hannu@voxware.pp.fi + * + ********************************************************************** + * PS. The Hacker's Guide to VoxWare available from + * nic.funet.fi:pub/OS/Linux/ALPHA/sound. The file is + * snd-sdk-doc-0.1.ps.gz (gzipped postscript). It contains + * some useful information about programming with VoxWare. + * (NOTE! The pub/OS/Linux/ALPHA/ directories are hidden. You have + * to cd inside them before the files are accessible.) + ********************************************************************** + */ + +#include <sys/types.h> +#ifndef _IOWR +#include <sys/ioccom.h> +#endif /* !_IOWR */ + +/* + * The first part of this file contains the new FreeBSD sound ioctl + * interface. Tries to minimize the number of different ioctls, and + * to be reasonably general. + * + * 970821: some of the new calls have not been implemented yet. + */ + +/* + * the following three calls extend the generic file descriptor + * interface. AIONWRITE is the dual of FIONREAD, i.e. returns the max + * number of bytes for a write operation to be non-blocking. + * + * AIOGSIZE/AIOSSIZE are used to change the behaviour of the device, + * from a character device (default) to a block device. In block mode, + * (not to be confused with blocking mode) the main difference for the + * application is that select() will return only when a complete + * block can be read/written to the device, whereas in character mode + * select will return true when one byte can be exchanged. For audio + * devices, character mode makes select almost useless since one byte + * will always be ready by the next sample time (which is often only a + * handful of microseconds away). + * Use a size of 0 or 1 to return to character mode. + */ +#define AIONWRITE _IOR('A', 10, int) /* get # bytes to write */ +struct snd_size { + int play_size; + int rec_size; +}; +#define AIOGSIZE _IOR('A', 11, struct snd_size)/* read current blocksize */ +#define AIOSSIZE _IOWR('A', 11, struct snd_size) /* sets blocksize */ + +/* + * The following constants define supported audio formats. The + * encoding follows voxware conventions, i.e. 1 bit for each supported + * format. We extend it by using bit 31 (RO) to indicate full-duplex + * capability, and bit 29 (RO) to indicate that the card supports/ + * needs different formats on capture & playback channels. + * Bit 29 (RW) is used to indicate/ask stereo. + */ + +# define AFMT_QUERY 0x00000000 /* Return current fmt */ +# define AFMT_MU_LAW 0x00000001 +# define AFMT_A_LAW 0x00000002 +# define AFMT_IMA_ADPCM 0x00000004 +# define AFMT_U8 0x00000008 +# define AFMT_S16_LE 0x00000010 /* Little endian signed 16*/ +# define AFMT_S16_BE 0x00000020 /* Big endian signed 16 */ +# define AFMT_S8 0x00000040 +# define AFMT_U16_LE 0x00000080 /* Little endian U16 */ +# define AFMT_U16_BE 0x00000100 /* Big endian U16 */ +# define AFMT_MPEG 0x00000200 /* MPEG (2) audio */ + +# define AFMT_STEREO 0x10000000 /* can do/want stereo */ + +/* + * the following are really capabilities + */ +# define AFMT_WEIRD 0x20000000 /* weird hardware... */ + /* + * AFMT_WEIRD reports that the hardware might need to operate + * with different formats in the playback and capture + * channels when operating in full duplex. + * As an example, SoundBlaster16 cards only support U8 in one + * direction and S16 in the other one, and applications should + * be aware of this limitation. + */ +# define AFMT_FULLDUPLEX 0x80000000 /* can do full duplex */ + +/* + * The following structure is used to get/set format and sampling rate. + * While it would be better to have things such as stereo, bits per + * sample, endiannes, etc split in different variables, it turns out + * that formats are not that many, and not all combinations are possible. + * So we followed the Voxware approach of associating one bit to each + * format. + */ + +typedef struct _snd_chan_param { + u_long play_rate; /* sampling rate */ + u_long rec_rate; /* sampling rate */ + u_long play_format; /* everything describing the format */ + u_long rec_format; /* everything describing the format */ +} snd_chan_param; +#define AIOGFMT _IOR('f', 12, snd_chan_param) /* get format */ +#define AIOSFMT _IOWR('f', 12, snd_chan_param) /* sets format */ + +/* + * The following structure is used to get/set the mixer setting. + * Up to 32 mixers are supported, each one with up to 32 channels. + */ +typedef struct _snd_mix_param { + u_char subdev; /* which output */ + u_char line; /* which input */ + u_char left,right; /* volumes, 0..255, 0 = mute */ +} snd_mix_param ; + +/* XXX AIOGMIX, AIOSMIX not implemented yet */ +#define AIOGMIX _IOWR('A', 13, snd_mix_param) /* return mixer status */ +#define AIOSMIX _IOWR('A', 14, snd_mix_param) /* sets mixer status */ + +/* + * channel specifiers used in AIOSTOP and AIOSYNC + */ +#define AIOSYNC_PLAY 0x1 /* play chan */ +#define AIOSYNC_CAPTURE 0x2 /* capture chan */ +/* AIOSTOP stop & flush a channel, returns the residual count */ +#define AIOSTOP _IOWR ('A', 15, int) + +/* alternate method used to notify the sync condition */ +#define AIOSYNC_SIGNAL 0x100 +#define AIOSYNC_SELECT 0x200 + +/* what the 'pos' field refers to */ +#define AIOSYNC_READY 0x400 +#define AIOSYNC_FREE 0x800 + +typedef struct _snd_sync_parm { + long chan ; /* play or capture channel, plus modifier */ + long pos; +} snd_sync_parm; +#define AIOSYNC _IOWR ('A', 15, snd_sync_parm) /* misc. synchronization */ + +/* + * The following is used to return device capabilities. If the structure + * passed to the ioctl is zeroed, default values are returned for rate + * and formats, a bitmap of available mixers is returned, and values + * (inputs, different levels) for the first one are returned. + * + * If formats, mixers, inputs are instantiated, then detailed info + * are returned depending on the call. + */ +typedef struct _snd_capabilities { + u_long rate_min, rate_max; /* min-max sampling rate */ + u_long formats; + u_long bufsize; /* DMA buffer size */ + u_long mixers; /* bitmap of available mixers */ + u_long inputs; /* bitmap of available inputs (per mixer) */ + u_short left, right; /* how many levels are supported */ +} snd_capabilities; +#define AIOGCAP _IOWR('A', 15, snd_capabilities) /* get capabilities */ + +/* + * here is the old (Voxware) ioctl interface + */ + +/* + * IOCTL Commands for /dev/sequencer + */ + +#define SNDCTL_SEQ_RESET _IO ('Q', 0) +#define SNDCTL_SEQ_SYNC _IO ('Q', 1) +#define SNDCTL_SYNTH_INFO _IOWR('Q', 2, struct synth_info) +#define SNDCTL_SEQ_CTRLRATE _IOWR('Q', 3, int) /* Set/get timer res.(hz) */ +#define SNDCTL_SEQ_GETOUTCOUNT _IOR ('Q', 4, int) +#define SNDCTL_SEQ_GETINCOUNT _IOR ('Q', 5, int) +#define SNDCTL_SEQ_PERCMODE _IOW ('Q', 6, int) +#define SNDCTL_FM_LOAD_INSTR _IOW ('Q', 7, struct sbi_instrument) /* Valid for FM only */ +#define SNDCTL_SEQ_TESTMIDI _IOW ('Q', 8, int) +#define SNDCTL_SEQ_RESETSAMPLES _IOW ('Q', 9, int) +#define SNDCTL_SEQ_NRSYNTHS _IOR ('Q',10, int) +#define SNDCTL_SEQ_NRMIDIS _IOR ('Q',11, int) +#define SNDCTL_MIDI_INFO _IOWR('Q',12, struct midi_info) +#define SNDCTL_SEQ_THRESHOLD _IOW ('Q',13, int) +#define SNDCTL_SEQ_TRESHOLD SNDCTL_SEQ_THRESHOLD /* there was once a typo */ +#define SNDCTL_SYNTH_MEMAVL _IOWR('Q',14, int) /* in=dev#, out=memsize */ +#define SNDCTL_FM_4OP_ENABLE _IOW ('Q',15, int) /* in=dev# */ +#define SNDCTL_PMGR_ACCESS _IOWR('Q',16, struct patmgr_info) +#define SNDCTL_SEQ_PANIC _IO ('Q',17) +#define SNDCTL_SEQ_OUTOFBAND _IOW ('Q',18, struct seq_event_rec) + +struct seq_event_rec { + u_char arr[8]; +}; + +#define SNDCTL_TMR_TIMEBASE _IOWR('T', 1, int) +#define SNDCTL_TMR_START _IO ('T', 2) +#define SNDCTL_TMR_STOP _IO ('T', 3) +#define SNDCTL_TMR_CONTINUE _IO ('T', 4) +#define SNDCTL_TMR_TEMPO _IOWR('T', 5, int) +#define SNDCTL_TMR_SOURCE _IOWR('T', 6, int) +# define TMR_INTERNAL 0x00000001 +# define TMR_EXTERNAL 0x00000002 +# define TMR_MODE_MIDI 0x00000010 +# define TMR_MODE_FSK 0x00000020 +# define TMR_MODE_CLS 0x00000040 +# define TMR_MODE_SMPTE 0x00000080 +#define SNDCTL_TMR_METRONOME _IOW ('T', 7, int) +#define SNDCTL_TMR_SELECT _IOW ('T', 8, int) + +/* + * Endian aware patch key generation algorithm. + */ + +#if defined(_AIX) || defined(AIX) +# define _PATCHKEY(id) (0xfd00|id) +#else +# define _PATCHKEY(id) ((id<<8)|0xfd) +#endif + +/* + * Sample loading mechanism for internal synthesizers (/dev/sequencer) + * The following patch_info structure has been designed to support + * Gravis UltraSound. It tries to be universal format for uploading + * sample based patches but is propably too limited. + */ + +struct patch_info { +/* u_short key; Use GUS_PATCH here */ + short key; /* Use GUS_PATCH here */ +#define GUS_PATCH _PATCHKEY(0x04) +#define OBSOLETE_GUS_PATCH _PATCHKEY(0x02) + + short device_no; /* Synthesizer number */ + short instr_no; /* Midi pgm# */ + + u_long mode; +/* + * The least significant byte has the same format than the GUS .PAT + * files + */ +#define WAVE_16_BITS 0x01 /* bit 0 = 8 or 16 bit wave data. */ +#define WAVE_UNSIGNED 0x02 /* bit 1 = Signed - Unsigned data. */ +#define WAVE_LOOPING 0x04 /* bit 2 = looping enabled-1. */ +#define WAVE_BIDIR_LOOP 0x08 /* bit 3 = Set is bidirectional looping. */ +#define WAVE_LOOP_BACK 0x10 /* bit 4 = Set is looping backward. */ +#define WAVE_SUSTAIN_ON 0x20 /* bit 5 = Turn sustaining on. (Env. pts. 3)*/ +#define WAVE_ENVELOPES 0x40 /* bit 6 = Enable envelopes - 1 */ + /* (use the env_rate/env_offs fields). */ +/* Linux specific bits */ +#define WAVE_VIBRATO 0x00010000 /* The vibrato info is valid */ +#define WAVE_TREMOLO 0x00020000 /* The tremolo info is valid */ +#define WAVE_SCALE 0x00040000 /* The scaling info is valid */ +/* Other bits must be zeroed */ + + long len; /* Size of the wave data in bytes */ + long loop_start, loop_end; /* Byte offsets from the beginning */ + +/* + * The base_freq and base_note fields are used when computing the + * playback speed for a note. The base_note defines the tone frequency + * which is heard if the sample is played using the base_freq as the + * playback speed. + * + * The low_note and high_note fields define the minimum and maximum note + * frequencies for which this sample is valid. It is possible to define + * more than one samples for a instrument number at the same time. The + * low_note and high_note fields are used to select the most suitable one. + * + * The fields base_note, high_note and low_note should contain + * the note frequency multiplied by 1000. For example value for the + * middle A is 440*1000. + */ + + u_int base_freq; + u_long base_note; + u_long high_note; + u_long low_note; + int panning; /* -128=left, 127=right */ + int detuning; + +/* New fields introduced in version 1.99.5 */ + + /* Envelope. Enabled by mode bit WAVE_ENVELOPES */ + u_char env_rate[ 6 ]; /* GUS HW ramping rate */ + u_char env_offset[ 6 ]; /* 255 == 100% */ + + /* + * The tremolo, vibrato and scale info are not supported yet. + * Enable by setting the mode bits WAVE_TREMOLO, WAVE_VIBRATO or + * WAVE_SCALE + */ + + u_char tremolo_sweep; + u_char tremolo_rate; + u_char tremolo_depth; + + u_char vibrato_sweep; + u_char vibrato_rate; + u_char vibrato_depth; + + int scale_frequency; + u_int scale_factor; /* from 0 to 2048 or 0 to 2 */ + + int volume; + int spare[4]; + char data[1]; /* The waveform data starts here */ +}; + +struct sysex_info { + short key; /* Use GUS_PATCH here */ +#define SYSEX_PATCH _PATCHKEY(0x05) +#define MAUI_PATCH _PATCHKEY(0x06) + short device_no; /* Synthesizer number */ + long len; /* Size of the sysex data in bytes */ + u_char data[1]; /* Sysex data starts here */ +}; + +/* + * Patch management interface (/dev/sequencer, /dev/patmgr#) + * Don't use these calls if you want to maintain compatibility with + * the future versions of the driver. + */ + +#define PS_NO_PATCHES 0 /* No patch support on device */ +#define PS_MGR_NOT_OK 1 /* Plain patch support (no mgr) */ +#define PS_MGR_OK 2 /* Patch manager supported */ +#define PS_MANAGED 3 /* Patch manager running */ + +#define SNDCTL_PMGR_IFACE _IOWR('P', 1, struct patmgr_info) + +/* + * The patmgr_info is a fixed size structure which is used for two + * different purposes. The intended use is for communication between + * the application using /dev/sequencer and the patch manager daemon + * associated with a synthesizer device (ioctl(SNDCTL_PMGR_ACCESS)). + * + * This structure is also used with ioctl(SNDCTL_PGMR_IFACE) which allows + * a patch manager daemon to read and write device parameters. This + * ioctl available through /dev/sequencer also. Avoid using it since it's + * extremely hardware dependent. In addition access trough /dev/sequencer + * may confuse the patch manager daemon. + */ + +struct patmgr_info { /* Note! size must be < 4k since kmalloc() is used */ + u_long key; /* Don't worry. Reserved for communication + between the patch manager and the driver. */ +#define PM_K_EVENT 1 /* Event from the /dev/sequencer driver */ +#define PM_K_COMMAND 2 /* Request from a application */ +#define PM_K_RESPONSE 3 /* From patmgr to application */ +#define PM_ERROR 4 /* Error returned by the patmgr */ + int device; + int command; + +/* + * Commands 0x000 to 0xfff reserved for patch manager programs + */ +#define PM_GET_DEVTYPE 1 /* Returns type of the patch mgr interface of dev */ +#define PMTYPE_FM2 1 /* 2 OP fm */ +#define PMTYPE_FM4 2 /* Mixed 4 or 2 op FM (OPL-3) */ +#define PMTYPE_WAVE 3 /* Wave table synthesizer (GUS) */ +#define PM_GET_NRPGM 2 /* Returns max # of midi programs in parm1 */ +#define PM_GET_PGMMAP 3 /* Returns map of loaded midi programs in data8 */ +#define PM_GET_PGM_PATCHES 4 /* Return list of patches of a program (parm1) */ +#define PM_GET_PATCH 5 /* Return patch header of patch parm1 */ +#define PM_SET_PATCH 6 /* Set patch header of patch parm1 */ +#define PM_READ_PATCH 7 /* Read patch (wave) data */ +#define PM_WRITE_PATCH 8 /* Write patch (wave) data */ + +/* + * Commands 0x1000 to 0xffff are for communication between the patch manager + * and the client + */ +#define _PM_LOAD_PATCH 0x100 + +/* + * Commands above 0xffff reserved for device specific use + */ + + long parm1; + long parm2; + long parm3; + + union { + u_char data8[4000]; + u_short data16[2000]; + u_long data32[1000]; + struct patch_info patch; + } data; +}; + +/* + * When a patch manager daemon is present, it will be informed by the + * driver when something important happens. For example when the + * /dev/sequencer is opened or closed. A record with key == PM_K_EVENT is + * returned. The command field contains the event type: + */ +#define PM_E_OPENED 1 /* /dev/sequencer opened */ +#define PM_E_CLOSED 2 /* /dev/sequencer closed */ +#define PM_E_PATCH_RESET 3 /* SNDCTL_RESETSAMPLES called */ +#define PM_E_PATCH_LOADED 4 /* A patch has been loaded by appl */ + +/* + * /dev/sequencer input events. + * + * The data written to the /dev/sequencer is a stream of events. Events + * are records of 4 or 8 bytes. The first byte defines the size. + * Any number of events can be written with a write call. There + * is a set of macros for sending these events. Use these macros if you + * want to maximize portability of your program. + * + * Events SEQ_WAIT, SEQ_MIDIPUTC and SEQ_ECHO. Are also input events. + * (All input events are currently 4 bytes long. Be prepared to support + * 8 byte events also. If you receive any event having first byte >= 128, + * it's a 8 byte event. + * + * The events are documented at the end of this file. + * + * Normal events (4 bytes) + * There is also a 8 byte version of most of the 4 byte events. The + * 8 byte one is recommended. + */ +#define SEQ_NOTEOFF 0 +#define SEQ_FMNOTEOFF SEQ_NOTEOFF /* Just old name */ +#define SEQ_NOTEON 1 +#define SEQ_FMNOTEON SEQ_NOTEON +#define SEQ_WAIT TMR_WAIT_ABS +#define SEQ_PGMCHANGE 3 +#define SEQ_FMPGMCHANGE SEQ_PGMCHANGE +#define SEQ_SYNCTIMER TMR_START +#define SEQ_MIDIPUTC 5 +#define SEQ_DRUMON 6 /*** OBSOLETE ***/ +#define SEQ_DRUMOFF 7 /*** OBSOLETE ***/ +#define SEQ_ECHO TMR_ECHO /* For synching programs with output */ +#define SEQ_AFTERTOUCH 9 +#define SEQ_CONTROLLER 10 + +/* + * Midi controller numbers + * + * Controllers 0 to 31 (0x00 to 0x1f) and 32 to 63 (0x20 to 0x3f) + * are continuous controllers. + * In the MIDI 1.0 these controllers are sent using two messages. + * Controller numbers 0 to 31 are used to send the MSB and the + * controller numbers 32 to 63 are for the LSB. Note that just 7 bits + * are used in MIDI bytes. + */ + +#define CTL_BANK_SELECT 0x00 +#define CTL_MODWHEEL 0x01 +#define CTL_BREATH 0x02 +/* undefined 0x03 */ +#define CTL_FOOT 0x04 +#define CTL_PORTAMENTO_TIME 0x05 +#define CTL_DATA_ENTRY 0x06 +#define CTL_MAIN_VOLUME 0x07 +#define CTL_BALANCE 0x08 +/* undefined 0x09 */ +#define CTL_PAN 0x0a +#define CTL_EXPRESSION 0x0b +/* undefined 0x0c - 0x0f */ +#define CTL_GENERAL_PURPOSE1 0x10 +#define CTL_GENERAL_PURPOSE2 0x11 +#define CTL_GENERAL_PURPOSE3 0x12 +#define CTL_GENERAL_PURPOSE4 0x13 +/* undefined 0x14 - 0x1f */ + +/* undefined 0x20 */ + +/* + * The controller numbers 0x21 to 0x3f are reserved for the + * least significant bytes of the controllers 0x00 to 0x1f. + * These controllers are not recognised by the driver. + * + * Controllers 64 to 69 (0x40 to 0x45) are on/off switches. + * 0=OFF and 127=ON (intermediate values are possible) + */ +#define CTL_DAMPER_PEDAL 0x40 +#define CTL_SUSTAIN CTL_DAMPER_PEDAL /* Alias */ +#define CTL_HOLD CTL_DAMPER_PEDAL /* Alias */ +#define CTL_PORTAMENTO 0x41 +#define CTL_SOSTENUTO 0x42 +#define CTL_SOFT_PEDAL 0x43 +/* undefined 0x44 */ +#define CTL_HOLD2 0x45 +/* undefined 0x46 - 0x4f */ + +#define CTL_GENERAL_PURPOSE5 0x50 +#define CTL_GENERAL_PURPOSE6 0x51 +#define CTL_GENERAL_PURPOSE7 0x52 +#define CTL_GENERAL_PURPOSE8 0x53 +/* undefined 0x54 - 0x5a */ +#define CTL_EXT_EFF_DEPTH 0x5b +#define CTL_TREMOLO_DEPTH 0x5c +#define CTL_CHORUS_DEPTH 0x5d +#define CTL_DETUNE_DEPTH 0x5e +#define CTL_CELESTE_DEPTH CTL_DETUNE_DEPTH /* Alias for the above one */ +#define CTL_PHASER_DEPTH 0x5f +#define CTL_DATA_INCREMENT 0x60 +#define CTL_DATA_DECREMENT 0x61 +#define CTL_NONREG_PARM_NUM_LSB 0x62 +#define CTL_NONREG_PARM_NUM_MSB 0x63 +#define CTL_REGIST_PARM_NUM_LSB 0x64 +#define CTL_REGIST_PARM_NUM_MSB 0x65 +/* undefined 0x66 - 0x78 */ +/* reserved 0x79 - 0x7f */ + +/* Pseudo controllers (not midi compatible) */ +#define CTRL_PITCH_BENDER 255 +#define CTRL_PITCH_BENDER_RANGE 254 +#define CTRL_EXPRESSION 253 /* Obsolete */ +#define CTRL_MAIN_VOLUME 252 /* Obsolete */ + +#define SEQ_BALANCE 11 +#define SEQ_VOLMODE 12 + +/* + * Volume mode decides how volumes are used + */ + +#define VOL_METHOD_ADAGIO 1 +#define VOL_METHOD_LINEAR 2 + +/* + * Note! SEQ_WAIT, SEQ_MIDIPUTC and SEQ_ECHO are used also as + * input events. + */ + +/* + * Event codes 0xf0 to 0xfc are reserved for future extensions. + */ + +#define SEQ_FULLSIZE 0xfd /* Long events */ +/* + * SEQ_FULLSIZE events are used for loading patches/samples to the + * synthesizer devices. These events are passed directly to the driver + * of the associated synthesizer device. There is no limit to the size + * of the extended events. These events are not queued but executed + * immediately when the write() is called (execution can take several + * seconds of time). + * + * When a SEQ_FULLSIZE message is written to the device, it must + * be written using exactly one write() call. Other events cannot + * be mixed to the same write. + * + * For FM synths (YM3812/OPL3) use struct sbi_instrument and write + * it to the /dev/sequencer. Don't write other data together with + * the instrument structure Set the key field of the structure to + * FM_PATCH. The device field is used to route the patch to the + * corresponding device. + * + * For Gravis UltraSound use struct patch_info. Initialize the key field + * to GUS_PATCH. + */ +#define SEQ_PRIVATE 0xfe /* Low level HW dependent events (8 bytes) */ +#define SEQ_EXTENDED 0xff /* Extended events (8 bytes) OBSOLETE */ + +/* + * Record for FM patches + */ + +typedef u_char sbi_instr_data[32]; + +struct sbi_instrument { + u_short key; /* FM_PATCH or OPL3_PATCH */ +#define FM_PATCH _PATCHKEY(0x01) +#define OPL3_PATCH _PATCHKEY(0x03) + short device; /* Synth# (0-4) */ + int channel; /* Program# to be initialized */ + sbi_instr_data operators; /* Reg. settings for operator cells + * (.SBI format) */ +}; + +struct synth_info { /* Read only */ + char name[30]; + int device; /* 0-N. INITIALIZE BEFORE CALLING */ + int synth_type; +#define SYNTH_TYPE_FM 0 +#define SYNTH_TYPE_SAMPLE 1 +#define SYNTH_TYPE_MIDI 2 /* Midi interface */ + + int synth_subtype; +#define FM_TYPE_ADLIB 0x00 +#define FM_TYPE_OPL3 0x01 + +#define SAMPLE_TYPE_GUS 0x10 + + int perc_mode; /* No longer supported */ + int nr_voices; + int nr_drums; /* Obsolete field */ + int instr_bank_size; + u_long capabilities; +#define SYNTH_CAP_PERCMODE 0x00000001 /* No longer used */ +#define SYNTH_CAP_OPL3 0x00000002 /* Set if OPL3 supported */ +#define SYNTH_CAP_INPUT 0x00000004 /* Input (MIDI) device */ + int dummies[19]; /* Reserve space */ +}; + +struct sound_timer_info { + char name[32]; + int caps; +}; + +#define MIDI_CAP_MPU401 1 /* MPU-401 intelligent mode */ + +struct midi_info { + char name[30]; + int device; /* 0-N. INITIALIZE BEFORE CALLING */ + u_long capabilities; /* To be defined later */ + int dev_type; + int dummies[18]; /* Reserve space */ +}; + +/* + * ioctl commands for the /dev/midi## + */ +typedef struct { + u_char cmd; + char nr_args, nr_returns; + u_char data[30]; +} mpu_command_rec; + +#define SNDCTL_MIDI_PRETIME _IOWR('m', 0, int) +#define SNDCTL_MIDI_MPUMODE _IOWR('m', 1, int) +#define SNDCTL_MIDI_MPUCMD _IOWR('m', 2, mpu_command_rec) + +/* + * IOCTL commands for /dev/dsp and /dev/audio + */ + +#define SNDCTL_DSP_RESET _IO ('P', 0) +#define SNDCTL_DSP_SYNC _IO ('P', 1) +#define SNDCTL_DSP_SPEED _IOWR('P', 2, int) +#define SNDCTL_DSP_STEREO _IOWR('P', 3, int) +#define SNDCTL_DSP_GETBLKSIZE _IOR('P', 4, int) +#define SNDCTL_DSP_SETBLKSIZE _IOW('P', 4, int) +#define SNDCTL_DSP_SETFMT _IOWR('P',5, int) /* Selects ONE fmt*/ + +/* + * SOUND_PCM_WRITE_CHANNELS is not that different + * from SNDCTL_DSP_STEREO + */ +#define SOUND_PCM_WRITE_CHANNELS _IOWR('P', 6, int) +#define SOUND_PCM_WRITE_FILTER _IOWR('P', 7, int) +#define SNDCTL_DSP_POST _IO ('P', 8) + +/* + * SNDCTL_DSP_SETBLKSIZE and the following two calls mostly do + * the same thing, i.e. set the block size used in DMA transfers. + */ +#define SNDCTL_DSP_SUBDIVIDE _IOWR('P', 9, int) +#define SNDCTL_DSP_SETFRAGMENT _IOWR('P',10, int) + + +#define SNDCTL_DSP_GETFMTS _IOR ('P',11, int) /* Returns a mask */ +/* + * Buffer status queries. + */ +typedef struct audio_buf_info { + int fragments; /* # of avail. frags (partly used ones not counted) */ + int fragstotal; /* Total # of fragments allocated */ + int fragsize; /* Size of a fragment in bytes */ + + int bytes; /* Avail. space in bytes (includes partly used fragments) */ + /* Note! 'bytes' could be more than fragments*fragsize */ +} audio_buf_info; + +#define SNDCTL_DSP_GETOSPACE _IOR ('P',12, audio_buf_info) +#define SNDCTL_DSP_GETISPACE _IOR ('P',13, audio_buf_info) + +/* + * SNDCTL_DSP_NONBLOCK is the same (but less powerful, since the + * action cannot be undone) of FIONBIO. The same can be achieved + * by opening the device with O_NDELAY + */ +#define SNDCTL_DSP_NONBLOCK _IO ('P',14) + +#define SNDCTL_DSP_GETCAPS _IOR ('P',15, int) +#define DSP_CAP_REVISION 0x000000ff /* revision level (0 to 255) */ +#define DSP_CAP_DUPLEX 0x00000100 /* Full duplex record/playback */ +#define DSP_CAP_REALTIME 0x00000200 /* Real time capability */ +#define DSP_CAP_BATCH 0x00000400 + /* + * Device has some kind of internal buffers which may + * cause some delays and decrease precision of timing + */ +#define DSP_CAP_COPROC 0x00000800 + /* Has a coprocessor, sometimes it's a DSP but usually not */ +#define DSP_CAP_TRIGGER 0x00001000 /* Supports SETTRIGGER */ + +/* + * What do these function do ? + */ +#define SNDCTL_DSP_GETTRIGGER _IOR ('P',16, int) +#define SNDCTL_DSP_SETTRIGGER _IOW ('P',16, int) +#define PCM_ENABLE_INPUT 0x00000001 +#define PCM_ENABLE_OUTPUT 0x00000002 + +typedef struct count_info { + int bytes; /* Total # of bytes processed */ + int blocks; /* # of fragment transitions since last time */ + int ptr; /* Current DMA pointer value */ +} count_info; + +/* + * GETIPTR and GETISPACE are not that different... same for out. + */ +#define SNDCTL_DSP_GETIPTR _IOR ('P',17, count_info) +#define SNDCTL_DSP_GETOPTR _IOR ('P',18, count_info) + +typedef struct buffmem_desc { + caddr_t buffer; + int size; +} buffmem_desc; + +#define SNDCTL_DSP_MAPINBUF _IOR ('P', 19, buffmem_desc) +#define SNDCTL_DSP_MAPOUTBUF _IOR ('P', 20, buffmem_desc) +#define SNDCTL_DSP_SETSYNCRO _IO ('P', 21) + +/* + * I guess these are the readonly version of the same + * functions that exist above as SNDCTL_DSP_... + */ +#define SOUND_PCM_READ_RATE _IOR ('P', 2, int) +#define SOUND_PCM_READ_CHANNELS _IOR ('P', 6, int) +#define SOUND_PCM_READ_BITS _IOR ('P', 5, int) +#define SOUND_PCM_READ_FILTER _IOR ('P', 7, int) + +/* + * ioctl calls to be used in communication with coprocessors and + * DSP chips. + */ + +typedef struct copr_buffer { + int command; /* Set to 0 if not used */ + int flags; +#define CPF_NONE 0x0000 +#define CPF_FIRST 0x0001 /* First block */ +#define CPF_LAST 0x0002 /* Last block */ + int len; + int offs; /* If required by the device (0 if not used) */ + + u_char data[4000]; /* NOTE! 4000 is not 4k */ +} copr_buffer; + +typedef struct copr_debug_buf { + int command; /* Used internally. Set to 0 */ + int parm1; + int parm2; + int flags; + int len; /* Length of data in bytes */ +} copr_debug_buf; + +typedef struct copr_msg { + int len; + u_char data[4000]; +} copr_msg; + +#define SNDCTL_COPR_RESET _IO ('C', 0) +#define SNDCTL_COPR_LOAD _IOWR('C', 1, copr_buffer) +#define SNDCTL_COPR_RDATA _IOWR('C', 2, copr_debug_buf) +#define SNDCTL_COPR_RCODE _IOWR('C', 3, copr_debug_buf) +#define SNDCTL_COPR_WDATA _IOW ('C', 4, copr_debug_buf) +#define SNDCTL_COPR_WCODE _IOW ('C', 5, copr_debug_buf) +#define SNDCTL_COPR_RUN _IOWR('C', 6, copr_debug_buf) +#define SNDCTL_COPR_HALT _IOWR('C', 7, copr_debug_buf) +#define SNDCTL_COPR_SENDMSG _IOW ('C', 8, copr_msg) +#define SNDCTL_COPR_RCVMSG _IOR ('C', 9, copr_msg) + +/* + * IOCTL commands for /dev/mixer + */ + +/* + * Mixer devices + * + * There can be up to 20 different analog mixer channels. The + * SOUND_MIXER_NRDEVICES gives the currently supported maximum. + * The SOUND_MIXER_READ_DEVMASK returns a bitmask which tells + * the devices supported by the particular mixer. + */ + +#define SOUND_MIXER_NRDEVICES 17 +#define SOUND_MIXER_VOLUME 0 +#define SOUND_MIXER_BASS 1 +#define SOUND_MIXER_TREBLE 2 +#define SOUND_MIXER_SYNTH 3 +#define SOUND_MIXER_PCM 4 +#define SOUND_MIXER_SPEAKER 5 +#define SOUND_MIXER_LINE 6 +#define SOUND_MIXER_MIC 7 +#define SOUND_MIXER_CD 8 +#define SOUND_MIXER_IMIX 9 /* Recording monitor */ +#define SOUND_MIXER_ALTPCM 10 +#define SOUND_MIXER_RECLEV 11 /* Recording level */ +#define SOUND_MIXER_IGAIN 12 /* Input gain */ +#define SOUND_MIXER_OGAIN 13 /* Output gain */ +/* + * The AD1848 codec and compatibles have three line level inputs + * (line, aux1 and aux2). Since each card manufacturer have assigned + * different meanings to these inputs, it's inpractical to assign + * specific meanings (line, cd, synth etc.) to them. + */ +#define SOUND_MIXER_LINE1 14 /* Input source 1 (aux1) */ +#define SOUND_MIXER_LINE2 15 /* Input source 2 (aux2) */ +#define SOUND_MIXER_LINE3 16 /* Input source 3 (line) */ + +/* + * Some on/off settings (SOUND_SPECIAL_MIN - SOUND_SPECIAL_MAX) + * Not counted to SOUND_MIXER_NRDEVICES, but use the same number space + */ +#define SOUND_ONOFF_MIN 28 +#define SOUND_ONOFF_MAX 30 +#define SOUND_MIXER_MUTE 28 /* 0 or 1 */ +#define SOUND_MIXER_ENHANCE 29 /* Enhanced stereo (0, 40, 60 or 80) */ +#define SOUND_MIXER_LOUD 30 /* 0 or 1 */ + +/* Note! Number 31 cannot be used since the sign bit is reserved */ + +#define SOUND_DEVICE_LABELS { \ + "Vol ", "Bass ", "Trebl", "Synth", "Pcm ", "Spkr ", "Line ", \ + "Mic ", "CD ", "Mix ", "Pcm2 ", "Rec ", "IGain", "OGain", \ + "Line1", "Line2", "Line3"} + +#define SOUND_DEVICE_NAMES { \ + "vol", "bass", "treble", "synth", "pcm", "speaker", "line", \ + "mic", "cd", "mix", "pcm2", "rec", "igain", "ogain", \ + "line1", "line2", "line3"} + +/* Device bitmask identifiers */ + +#define SOUND_MIXER_RECSRC 0xff /* 1 bit per recording source */ +#define SOUND_MIXER_DEVMASK 0xfe /* 1 bit per supported device */ +#define SOUND_MIXER_RECMASK 0xfd /* 1 bit per supp. recording source */ +#define SOUND_MIXER_CAPS 0xfc +#define SOUND_CAP_EXCL_INPUT 0x00000001 /* Only 1 rec. src at a time */ +#define SOUND_MIXER_STEREODEVS 0xfb /* Mixer channels supporting stereo */ + +/* Device mask bits */ + +#define SOUND_MASK_VOLUME (1 << SOUND_MIXER_VOLUME) +#define SOUND_MASK_BASS (1 << SOUND_MIXER_BASS) +#define SOUND_MASK_TREBLE (1 << SOUND_MIXER_TREBLE) +#define SOUND_MASK_SYNTH (1 << SOUND_MIXER_SYNTH) +#define SOUND_MASK_PCM (1 << SOUND_MIXER_PCM) +#define SOUND_MASK_SPEAKER (1 << SOUND_MIXER_SPEAKER) +#define SOUND_MASK_LINE (1 << SOUND_MIXER_LINE) +#define SOUND_MASK_MIC (1 << SOUND_MIXER_MIC) +#define SOUND_MASK_CD (1 << SOUND_MIXER_CD) +#define SOUND_MASK_IMIX (1 << SOUND_MIXER_IMIX) +#define SOUND_MASK_ALTPCM (1 << SOUND_MIXER_ALTPCM) +#define SOUND_MASK_RECLEV (1 << SOUND_MIXER_RECLEV) +#define SOUND_MASK_IGAIN (1 << SOUND_MIXER_IGAIN) +#define SOUND_MASK_OGAIN (1 << SOUND_MIXER_OGAIN) +#define SOUND_MASK_LINE1 (1 << SOUND_MIXER_LINE1) +#define SOUND_MASK_LINE2 (1 << SOUND_MIXER_LINE2) +#define SOUND_MASK_LINE3 (1 << SOUND_MIXER_LINE3) + +#define SOUND_MASK_MUTE (1 << SOUND_MIXER_MUTE) +#define SOUND_MASK_ENHANCE (1 << SOUND_MIXER_ENHANCE) +#define SOUND_MASK_LOUD (1 << SOUND_MIXER_LOUD) + +#define MIXER_READ(dev) _IOR('M', dev, int) +#define SOUND_MIXER_READ_VOLUME MIXER_READ(SOUND_MIXER_VOLUME) +#define SOUND_MIXER_READ_BASS MIXER_READ(SOUND_MIXER_BASS) +#define SOUND_MIXER_READ_TREBLE MIXER_READ(SOUND_MIXER_TREBLE) +#define SOUND_MIXER_READ_SYNTH MIXER_READ(SOUND_MIXER_SYNTH) +#define SOUND_MIXER_READ_PCM MIXER_READ(SOUND_MIXER_PCM) +#define SOUND_MIXER_READ_SPEAKER MIXER_READ(SOUND_MIXER_SPEAKER) +#define SOUND_MIXER_READ_LINE MIXER_READ(SOUND_MIXER_LINE) +#define SOUND_MIXER_READ_MIC MIXER_READ(SOUND_MIXER_MIC) +#define SOUND_MIXER_READ_CD MIXER_READ(SOUND_MIXER_CD) +#define SOUND_MIXER_READ_IMIX MIXER_READ(SOUND_MIXER_IMIX) +#define SOUND_MIXER_READ_ALTPCM MIXER_READ(SOUND_MIXER_ALTPCM) +#define SOUND_MIXER_READ_RECLEV MIXER_READ(SOUND_MIXER_RECLEV) +#define SOUND_MIXER_READ_IGAIN MIXER_READ(SOUND_MIXER_IGAIN) +#define SOUND_MIXER_READ_OGAIN MIXER_READ(SOUND_MIXER_OGAIN) +#define SOUND_MIXER_READ_LINE1 MIXER_READ(SOUND_MIXER_LINE1) +#define SOUND_MIXER_READ_LINE2 MIXER_READ(SOUND_MIXER_LINE2) +#define SOUND_MIXER_READ_LINE3 MIXER_READ(SOUND_MIXER_LINE3) +#define SOUND_MIXER_READ_MUTE MIXER_READ(SOUND_MIXER_MUTE) +#define SOUND_MIXER_READ_ENHANCE MIXER_READ(SOUND_MIXER_ENHANCE) +#define SOUND_MIXER_READ_LOUD MIXER_READ(SOUND_MIXER_LOUD) + +#define SOUND_MIXER_READ_RECSRC MIXER_READ(SOUND_MIXER_RECSRC) +#define SOUND_MIXER_READ_DEVMASK MIXER_READ(SOUND_MIXER_DEVMASK) +#define SOUND_MIXER_READ_RECMASK MIXER_READ(SOUND_MIXER_RECMASK) +#define SOUND_MIXER_READ_STEREODEVS MIXER_READ(SOUND_MIXER_STEREODEVS) +#define SOUND_MIXER_READ_CAPS MIXER_READ(SOUND_MIXER_CAPS) + +#define MIXER_WRITE(dev) _IOWR('M', dev, int) +#define SOUND_MIXER_WRITE_VOLUME MIXER_WRITE(SOUND_MIXER_VOLUME) +#define SOUND_MIXER_WRITE_BASS MIXER_WRITE(SOUND_MIXER_BASS) +#define SOUND_MIXER_WRITE_TREBLE MIXER_WRITE(SOUND_MIXER_TREBLE) +#define SOUND_MIXER_WRITE_SYNTH MIXER_WRITE(SOUND_MIXER_SYNTH) +#define SOUND_MIXER_WRITE_PCM MIXER_WRITE(SOUND_MIXER_PCM) +#define SOUND_MIXER_WRITE_SPEAKER MIXER_WRITE(SOUND_MIXER_SPEAKER) +#define SOUND_MIXER_WRITE_LINE MIXER_WRITE(SOUND_MIXER_LINE) +#define SOUND_MIXER_WRITE_MIC MIXER_WRITE(SOUND_MIXER_MIC) +#define SOUND_MIXER_WRITE_CD MIXER_WRITE(SOUND_MIXER_CD) +#define SOUND_MIXER_WRITE_IMIX MIXER_WRITE(SOUND_MIXER_IMIX) +#define SOUND_MIXER_WRITE_ALTPCM MIXER_WRITE(SOUND_MIXER_ALTPCM) +#define SOUND_MIXER_WRITE_RECLEV MIXER_WRITE(SOUND_MIXER_RECLEV) +#define SOUND_MIXER_WRITE_IGAIN MIXER_WRITE(SOUND_MIXER_IGAIN) +#define SOUND_MIXER_WRITE_OGAIN MIXER_WRITE(SOUND_MIXER_OGAIN) +#define SOUND_MIXER_WRITE_LINE1 MIXER_WRITE(SOUND_MIXER_LINE1) +#define SOUND_MIXER_WRITE_LINE2 MIXER_WRITE(SOUND_MIXER_LINE2) +#define SOUND_MIXER_WRITE_LINE3 MIXER_WRITE(SOUND_MIXER_LINE3) +#define SOUND_MIXER_WRITE_MUTE MIXER_WRITE(SOUND_MIXER_MUTE) +#define SOUND_MIXER_WRITE_ENHANCE MIXER_WRITE(SOUND_MIXER_ENHANCE) +#define SOUND_MIXER_WRITE_LOUD MIXER_WRITE(SOUND_MIXER_LOUD) + +#define SOUND_MIXER_WRITE_RECSRC MIXER_WRITE(SOUND_MIXER_RECSRC) + +#define LEFT_CHN 0 +#define RIGHT_CHN 1 + +/* + * Level 2 event types for /dev/sequencer + */ + +/* + * The 4 most significant bits of byte 0 specify the class of + * the event: + * + * 0x8X = system level events, + * 0x9X = device/port specific events, event[1] = device/port, + * The last 4 bits give the subtype: + * 0x02 = Channel event (event[3] = chn). + * 0x01 = note event (event[4] = note). + * (0x01 is not used alone but always with bit 0x02). + * event[2] = MIDI message code (0x80=note off etc.) + * + */ + +#define EV_SEQ_LOCAL 0x80 +#define EV_TIMING 0x81 +#define EV_CHN_COMMON 0x92 +#define EV_CHN_VOICE 0x93 +#define EV_SYSEX 0x94 +/* + * Event types 200 to 220 are reserved for application use. + * These numbers will not be used by the driver. + */ + +/* + * Events for event type EV_CHN_VOICE + */ + +#define MIDI_NOTEOFF 0x80 +#define MIDI_NOTEON 0x90 +#define MIDI_KEY_PRESSURE 0xA0 + +/* + * Events for event type EV_CHN_COMMON + */ + +#define MIDI_CTL_CHANGE 0xB0 +#define MIDI_PGM_CHANGE 0xC0 +#define MIDI_CHN_PRESSURE 0xD0 +#define MIDI_PITCH_BEND 0xE0 + +#define MIDI_SYSTEM_PREFIX 0xF0 + +/* + * Timer event types + */ +#define TMR_WAIT_REL 1 /* Time relative to the prev time */ +#define TMR_WAIT_ABS 2 /* Absolute time since TMR_START */ +#define TMR_STOP 3 +#define TMR_START 4 +#define TMR_CONTINUE 5 +#define TMR_TEMPO 6 +#define TMR_ECHO 8 +#define TMR_CLOCK 9 /* MIDI clock */ +#define TMR_SPP 10 /* Song position pointer */ +#define TMR_TIMESIG 11 /* Time signature */ + +/* + * Local event types + */ +#define LOCL_STARTAUDIO 1 + +#if (!defined(__KERNEL__) && !defined(KERNEL) && !defined(INKERNEL) && !defined(_KERNEL)) || defined(USE_SEQ_MACROS) +/* + * Some convenience macros to simplify programming of the + * /dev/sequencer interface + * + * These macros define the API which should be used when possible. + */ + +#ifndef USE_SIMPLE_MACROS +void seqbuf_dump(void); /* This function must be provided by programs */ + +/* Sample seqbuf_dump() implementation: + * + * SEQ_DEFINEBUF (2048); -- Defines a buffer for 2048 bytes + * + * int seqfd; -- The file descriptor for /dev/sequencer. + * + * void + * seqbuf_dump () + * { + * if (_seqbufptr) + * if (write (seqfd, _seqbuf, _seqbufptr) == -1) + * { + * perror ("write /dev/sequencer"); + * exit (-1); + * } + * _seqbufptr = 0; + * } + */ + +#define SEQ_DEFINEBUF(len) \ + u_char _seqbuf[len]; int _seqbuflen = len;int _seqbufptr = 0 +#define SEQ_USE_EXTBUF() \ + extern u_char _seqbuf[]; \ + extern int _seqbuflen;extern int _seqbufptr +#define SEQ_DECLAREBUF() SEQ_USE_EXTBUF() +#define SEQ_PM_DEFINES struct patmgr_info _pm_info +#define _SEQ_NEEDBUF(len) \ + if ((_seqbufptr+(len)) > _seqbuflen) \ + seqbuf_dump() +#define _SEQ_ADVBUF(len) _seqbufptr += len +#define SEQ_DUMPBUF seqbuf_dump +#else +/* + * This variation of the sequencer macros is used just to format one event + * using fixed buffer. + * + * The program using the macro library must define the following macros before + * using this library. + * + * #define _seqbuf name of the buffer (u_char[]) + * #define _SEQ_ADVBUF(len) If the applic needs to know the exact + * size of the event, this macro can be used. + * Otherwise this must be defined as empty. + * #define _seqbufptr Define the name of index variable or 0 if + * not required. + */ +#define _SEQ_NEEDBUF(len) /* empty */ +#endif + +#define PM_LOAD_PATCH(dev, bank, pgm) \ + (SEQ_DUMPBUF(), _pm_info.command = _PM_LOAD_PATCH, \ + _pm_info.device=dev, _pm_info.data.data8[0]=pgm, \ + _pm_info.parm1 = bank, _pm_info.parm2 = 1, \ + ioctl(seqfd, SNDCTL_PMGR_ACCESS, &_pm_info)) +#define PM_LOAD_PATCHES(dev, bank, pgm) \ + (SEQ_DUMPBUF(), _pm_info.command = _PM_LOAD_PATCH, \ + _pm_info.device=dev, bcopy( pgm, _pm_info.data.data8, 128), \ + _pm_info.parm1 = bank, _pm_info.parm2 = 128, \ + ioctl(seqfd, SNDCTL_PMGR_ACCESS, &_pm_info)) + +#define SEQ_VOLUME_MODE(dev, mode) { \ + _SEQ_NEEDBUF(8);\ + _seqbuf[_seqbufptr] = SEQ_EXTENDED;\ + _seqbuf[_seqbufptr+1] = SEQ_VOLMODE;\ + _seqbuf[_seqbufptr+2] = (dev);\ + _seqbuf[_seqbufptr+3] = (mode);\ + _seqbuf[_seqbufptr+4] = 0;\ + _seqbuf[_seqbufptr+5] = 0;\ + _seqbuf[_seqbufptr+6] = 0;\ + _seqbuf[_seqbufptr+7] = 0;\ + _SEQ_ADVBUF(8);} + +/* + * Midi voice messages + */ + +#define _CHN_VOICE(dev, event, chn, note, parm) { \ + _SEQ_NEEDBUF(8);\ + _seqbuf[_seqbufptr] = EV_CHN_VOICE;\ + _seqbuf[_seqbufptr+1] = (dev);\ + _seqbuf[_seqbufptr+2] = (event);\ + _seqbuf[_seqbufptr+3] = (chn);\ + _seqbuf[_seqbufptr+4] = (note);\ + _seqbuf[_seqbufptr+5] = (parm);\ + _seqbuf[_seqbufptr+6] = (0);\ + _seqbuf[_seqbufptr+7] = 0;\ + _SEQ_ADVBUF(8);} + +#define SEQ_START_NOTE(dev, chn, note, vol) \ + _CHN_VOICE(dev, MIDI_NOTEON, chn, note, vol) + +#define SEQ_STOP_NOTE(dev, chn, note, vol) \ + _CHN_VOICE(dev, MIDI_NOTEOFF, chn, note, vol) + +#define SEQ_KEY_PRESSURE(dev, chn, note, pressure) \ + _CHN_VOICE(dev, MIDI_KEY_PRESSURE, chn, note, pressure) + +/* + * Midi channel messages + */ + +#define _CHN_COMMON(dev, event, chn, p1, p2, w14) { \ + _SEQ_NEEDBUF(8);\ + _seqbuf[_seqbufptr] = EV_CHN_COMMON;\ + _seqbuf[_seqbufptr+1] = (dev);\ + _seqbuf[_seqbufptr+2] = (event);\ + _seqbuf[_seqbufptr+3] = (chn);\ + _seqbuf[_seqbufptr+4] = (p1);\ + _seqbuf[_seqbufptr+5] = (p2);\ + *(short *)&_seqbuf[_seqbufptr+6] = (w14);\ + _SEQ_ADVBUF(8);} +/* + * SEQ_SYSEX permits sending of sysex messages. (It may look that it permits + * sending any MIDI bytes but it's absolutely not possible. Trying to do + * so _will_ cause problems with MPU401 intelligent mode). + * + * Sysex messages are sent in blocks of 1 to 6 bytes. Longer messages must be + * sent by calling SEQ_SYSEX() several times (there must be no other events + * between them). First sysex fragment must have 0xf0 in the first byte + * and the last byte (buf[len-1] of the last fragment must be 0xf7. No byte + * between these sysex start and end markers cannot be larger than 0x7f. Also + * lengths of each fragments (except the last one) must be 6. + * + * Breaking the above rules may work with some MIDI ports but is likely to + * cause fatal problems with some other devices (such as MPU401). + */ +#define SEQ_SYSEX(dev, buf, len) { \ + int i, l=(len); if (l>6)l=6;\ + _SEQ_NEEDBUF(8);\ + _seqbuf[_seqbufptr] = EV_SYSEX;\ + for(i=0;i<l;i++)_seqbuf[_seqbufptr+i+1] = (buf)[i];\ + for(i=l;i<6;i++)_seqbuf[_seqbufptr+i+1] = 0xff;\ + _SEQ_ADVBUF(8);} + +#define SEQ_CHN_PRESSURE(dev, chn, pressure) \ + _CHN_COMMON(dev, MIDI_CHN_PRESSURE, chn, pressure, 0, 0) + +#define SEQ_SET_PATCH(dev, chn, patch) \ + _CHN_COMMON(dev, MIDI_PGM_CHANGE, chn, patch, 0, 0) + +#define SEQ_CONTROL(dev, chn, controller, value) \ + _CHN_COMMON(dev, MIDI_CTL_CHANGE, chn, controller, 0, value) + +#define SEQ_BENDER(dev, chn, value) \ + _CHN_COMMON(dev, MIDI_PITCH_BEND, chn, 0, 0, value) + + +#define SEQ_V2_X_CONTROL(dev, voice, controller, value) { \ + _SEQ_NEEDBUF(8);\ + _seqbuf[_seqbufptr] = SEQ_EXTENDED;\ + _seqbuf[_seqbufptr+1] = SEQ_CONTROLLER;\ + _seqbuf[_seqbufptr+2] = (dev);\ + _seqbuf[_seqbufptr+3] = (voice);\ + _seqbuf[_seqbufptr+4] = (controller);\ + *(short *)&_seqbuf[_seqbufptr+5] = (value);\ + _seqbuf[_seqbufptr+7] = 0;\ + _SEQ_ADVBUF(8);} + +/* + * The following 5 macros are incorrectly implemented and obsolete. + * Use SEQ_BENDER and SEQ_CONTROL (with proper controller) instead. + */ + +#define SEQ_PITCHBEND(dev, voice, value) \ + SEQ_V2_X_CONTROL(dev, voice, CTRL_PITCH_BENDER, value) +#define SEQ_BENDER_RANGE(dev, voice, value) \ + SEQ_V2_X_CONTROL(dev, voice, CTRL_PITCH_BENDER_RANGE, value) +#define SEQ_EXPRESSION(dev, voice, value) \ + SEQ_CONTROL(dev, voice, CTL_EXPRESSION, value*128) +#define SEQ_MAIN_VOLUME(dev, voice, value) \ + SEQ_CONTROL(dev, voice, CTL_MAIN_VOLUME, (value*16383)/100) +#define SEQ_PANNING(dev, voice, pos) \ + SEQ_CONTROL(dev, voice, CTL_PAN, (pos+128) / 2) + +/* + * Timing and syncronization macros + */ + +#define _TIMER_EVENT(ev, parm) { \ + _SEQ_NEEDBUF(8);\ + _seqbuf[_seqbufptr+0] = EV_TIMING; \ + _seqbuf[_seqbufptr+1] = (ev); \ + _seqbuf[_seqbufptr+2] = 0;\ + _seqbuf[_seqbufptr+3] = 0;\ + *(u_int *)&_seqbuf[_seqbufptr+4] = (parm); \ + _SEQ_ADVBUF(8); \ + } + +#define SEQ_START_TIMER() _TIMER_EVENT(TMR_START, 0) +#define SEQ_STOP_TIMER() _TIMER_EVENT(TMR_STOP, 0) +#define SEQ_CONTINUE_TIMER() _TIMER_EVENT(TMR_CONTINUE, 0) +#define SEQ_WAIT_TIME(ticks) _TIMER_EVENT(TMR_WAIT_ABS, ticks) +#define SEQ_DELTA_TIME(ticks) _TIMER_EVENT(TMR_WAIT_REL, ticks) +#define SEQ_ECHO_BACK(key) _TIMER_EVENT(TMR_ECHO, key) +#define SEQ_SET_TEMPO(value) _TIMER_EVENT(TMR_TEMPO, value) +#define SEQ_SONGPOS(pos) _TIMER_EVENT(TMR_SPP, pos) +#define SEQ_TIME_SIGNATURE(sig) _TIMER_EVENT(TMR_TIMESIG, sig) + +/* + * Local control events + */ + +#define _LOCAL_EVENT(ev, parm) { \ + _SEQ_NEEDBUF(8);\ + _seqbuf[_seqbufptr+0] = EV_SEQ_LOCAL; \ + _seqbuf[_seqbufptr+1] = (ev); \ + _seqbuf[_seqbufptr+2] = 0;\ + _seqbuf[_seqbufptr+3] = 0;\ + *(u_int *)&_seqbuf[_seqbufptr+4] = (parm); \ + _SEQ_ADVBUF(8); \ + } + +#define SEQ_PLAYAUDIO(devmask) _LOCAL_EVENT(LOCL_STARTAUDIO, devmask) +/* + * Events for the level 1 interface only + */ + +#define SEQ_MIDIOUT(device, byte) { \ + _SEQ_NEEDBUF(4);\ + _seqbuf[_seqbufptr] = SEQ_MIDIPUTC;\ + _seqbuf[_seqbufptr+1] = (byte);\ + _seqbuf[_seqbufptr+2] = (device);\ + _seqbuf[_seqbufptr+3] = 0;\ + _SEQ_ADVBUF(4);} + +/* + * Patch loading. + */ +#define SEQ_WRPATCH(patchx, len) { \ + if (_seqbufptr) seqbuf_dump(); \ + if (write(seqfd, (char*)(patchx), len)==-1) \ + perror("Write patch: /dev/sequencer"); \ + } + +#define SEQ_WRPATCH2(patchx, len) \ + ( seqbuf_dump(), write(seqfd, (char*)(patchx), len) ) + +#endif + +/* + * Here I have moved all the aliases for ioctl names. + */ + +#define SNDCTL_DSP_SAMPLESIZE SNDCTL_DSP_SETFMT +#define SOUND_PCM_WRITE_BITS SNDCTL_DSP_SETFMT +#define SOUND_PCM_SETFMT SNDCTL_DSP_SETFMT + +#define SOUND_PCM_WRITE_RATE SNDCTL_DSP_SPEED +#define SOUND_PCM_POST SNDCTL_DSP_POST +#define SOUND_PCM_RESET SNDCTL_DSP_RESET +#define SOUND_PCM_SYNC SNDCTL_DSP_SYNC +#define SOUND_PCM_SUBDIVIDE SNDCTL_DSP_SUBDIVIDE +#define SOUND_PCM_SETFRAGMENT SNDCTL_DSP_SETFRAGMENT +#define SOUND_PCM_GETFMTS SNDCTL_DSP_GETFMTS +#define SOUND_PCM_GETOSPACE SNDCTL_DSP_GETOSPACE +#define SOUND_PCM_GETISPACE SNDCTL_DSP_GETISPACE +#define SOUND_PCM_NONBLOCK SNDCTL_DSP_NONBLOCK +#define SOUND_PCM_GETCAPS SNDCTL_DSP_GETCAPS +#define SOUND_PCM_GETTRIGGER SNDCTL_DSP_GETTRIGGER +#define SOUND_PCM_SETTRIGGER SNDCTL_DSP_SETTRIGGER +#define SOUND_PCM_SETSYNCRO SNDCTL_DSP_SETSYNCRO +#define SOUND_PCM_GETIPTR SNDCTL_DSP_GETIPTR +#define SOUND_PCM_GETOPTR SNDCTL_DSP_GETOPTR +#define SOUND_PCM_MAPINBUF SNDCTL_DSP_MAPINBUF +#define SOUND_PCM_MAPOUTBUF SNDCTL_DSP_MAPOUTBUF + +#endif /* SOUNDCARD_H */ diff --git a/sys/i386/isa/snd/ulaw.h b/sys/i386/isa/snd/ulaw.h new file mode 100644 index 0000000..8971f63 --- /dev/null +++ b/sys/i386/isa/snd/ulaw.h @@ -0,0 +1,93 @@ +/* + * on entry: ulaw, on exit: unsigned 8 bit. + */ +static unsigned char ulaw_dsp[] = { + 3, 7, 11, 15, 19, 23, 27, 31, + 35, 39, 43, 47, 51, 55, 59, 63, + 66, 68, 70, 72, 74, 76, 78, 80, + 82, 84, 86, 88, 90, 92, 94, 96, + + 98, 99, 100, 101, 102, 103, 104, 105, + 106, 107, 108, 109, 110, 111, 112, 113, + 113, 114, 114, 115, 115, 116, 116, 117, + 117, 118, 118, 119, 119, 120, 120, 121, + + 121, 121, 122, 122, 122, 122, 123, 123, + 123, 123, 124, 124, 124, 124, 125, 125, + 125, 125, 125, 125, 126, 126, 126, 126, + 126, 126, 126, 126, 127, 127, 127, 127, + + 127, 127, 127, 127, 127, 127, 127, 127, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + + + 253, 249, 245, 241, 237, 233, 229, 225, + 221, 217, 213, 209, 205, 201, 197, 193, + 190, 188, 186, 184, 182, 180, 178, 176, + 174, 172, 170, 168, 166, 164, 162, 160, + + 158, 157, 156, 155, 154, 153, 152, 151, + 150, 149, 148, 147, 146, 145, 144, 143, + 143, 142, 142, 141, 141, 140, 140, 139, + 139, 138, 138, 137, 137, 136, 136, 135, + + 135, 135, 134, 134, 134, 134, 133, 133, + 133, 133, 132, 132, 132, 132, 131, 131, + 131, 131, 131, 131, 130, 130, 130, 130, + 130, 130, 130, 130, 129, 129, 129, 129, + + 129, 129, 129, 129, 129, 129, 129, 129, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, +}; + +#ifndef DSP_ULAW_NOT_WANTED +/* + * on entry: unsigned 8-bit, on exit: ulaw. + */ +static unsigned char dsp_ulaw[] = { + 0, 0, 0, 0, 0, 1, 1, 1, + 1, 2, 2, 2, 2, 3, 3, 3, + 3, 4, 4, 4, 4, 5, 5, 5, + 5, 6, 6, 6, 6, 7, 7, 7, + + 7, 8, 8, 8, 8, 9, 9, 9, + 9, 10, 10, 10, 10, 11, 11, 11, + 11, 12, 12, 12, 12, 13, 13, 13, + 13, 14, 14, 14, 14, 15, 15, 15, + + 15, 16, 16, 17, 17, 18, 18, 19, + 19, 20, 20, 21, 21, 22, 22, 23, + 23, 24, 24, 25, 25, 26, 26, 27, + 27, 28, 28, 29, 29, 30, 30, 31, + + 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, + 47, 49, 51, 53, 55, 57, 59, 61, + 63, 66, 70, 74, 78, 84, 92, 104, + + + 254, 231, 219, 211, 205, 201, 197, 193, + 190, 188, 186, 184, 182, 180, 178, 176, + 175, 174, 173, 172, 171, 170, 169, 168, + 167, 166, 165, 164, 163, 162, 161, 160, + + 159, 159, 158, 158, 157, 157, 156, 156, + 155, 155, 154, 154, 153, 153, 152, 152, + 151, 151, 150, 150, 149, 149, 148, 148, + 147, 147, 146, 146, 145, 145, 144, 144, + + 143, 143, 143, 143, 142, 142, 142, 142, + 141, 141, 141, 141, 140, 140, 140, 140, + 139, 139, 139, 139, 138, 138, 138, 138, + 137, 137, 137, 137, 136, 136, 136, 136, + + 135, 135, 135, 135, 134, 134, 134, 134, + 133, 133, 133, 133, 132, 132, 132, 132, + 131, 131, 131, 131, 130, 130, 130, 130, + 129, 129, 129, 129, 128, 128, 128, 128, +}; +#endif /* !DSP_ULAW_NOT_WANTED */ |