diff options
author | cg <cg@FreeBSD.org> | 1999-09-01 04:08:39 +0000 |
---|---|---|
committer | cg <cg@FreeBSD.org> | 1999-09-01 04:08:39 +0000 |
commit | 73a7a67d13cba01eb39397ff54125ce8d3e6f8cc (patch) | |
tree | 45f897f70e6c72dbb1af7c4e6103dfa70ae3542b /sys/dev/sound/pcm | |
parent | feefbc8c0eaa6a6e695c1e9c3514be8d22e881ea (diff) | |
download | FreeBSD-src-73a7a67d13cba01eb39397ff54125ce8d3e6f8cc.zip FreeBSD-src-73a7a67d13cba01eb39397ff54125ce8d3e6f8cc.tar.gz |
say hello to newpcm. it is not yet enabled, requiring new pnp code from dfr
to compile successfully. further details will be provided in the commit
enabling newpcm.
Diffstat (limited to 'sys/dev/sound/pcm')
-rw-r--r-- | sys/dev/sound/pcm/ac97.c | 335 | ||||
-rw-r--r-- | sys/dev/sound/pcm/ac97.h | 35 | ||||
-rw-r--r-- | sys/dev/sound/pcm/channel.c | 737 | ||||
-rw-r--r-- | sys/dev/sound/pcm/channel.h | 80 | ||||
-rw-r--r-- | sys/dev/sound/pcm/datatypes.h | 156 | ||||
-rw-r--r-- | sys/dev/sound/pcm/dsp.c | 543 | ||||
-rw-r--r-- | sys/dev/sound/pcm/dsp.h | 37 | ||||
-rw-r--r-- | sys/dev/sound/pcm/fake.c | 115 | ||||
-rw-r--r-- | sys/dev/sound/pcm/feeder.c | 403 | ||||
-rw-r--r-- | sys/dev/sound/pcm/mixer.c | 202 | ||||
-rw-r--r-- | sys/dev/sound/pcm/mixer.h | 42 | ||||
-rw-r--r-- | sys/dev/sound/pcm/sound.c | 438 | ||||
-rw-r--r-- | sys/dev/sound/pcm/sound.h | 181 |
13 files changed, 3304 insertions, 0 deletions
diff --git a/sys/dev/sound/pcm/ac97.c b/sys/dev/sound/pcm/ac97.c new file mode 100644 index 0000000..b3da9cb --- /dev/null +++ b/sys/dev/sound/pcm/ac97.c @@ -0,0 +1,335 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * All rights reserved. + * + * 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. + * + * $Id$ + */ + +#include <dev/pcm/sound.h> +#include <dev/pcm/ac97.h> + +#define AC97_MUTE 0x8000 + +#define AC97_REG_RESET 0x00 +#define AC97_MIX_MASTER 0x02 +#define AC97_MIX_PHONES 0x04 +#define AC97_MIX_MONO 0x06 +#define AC97_MIX_TONE 0x08 +#define AC97_MIX_BEEP 0x0a +#define AC97_MIX_PHONE 0x0c +#define AC97_MIX_MIC 0x0e +#define AC97_MIX_LINE 0x10 +#define AC97_MIX_CD 0x12 +#define AC97_MIX_VIDEO 0x14 +#define AC97_MIX_AUX 0x16 +#define AC97_MIX_PCM 0x18 +#define AC97_REG_RECSEL 0x1a +#define AC97_MIX_RGAIN 0x1c +#define AC97_MIX_MGAIN 0x1e +#define AC97_REG_GEN 0x20 +#define AC97_REG_3D 0x22 +#define AC97_REG_POWER 0x26 +#define AC97_REG_ID1 0x7c +#define AC97_REG_ID2 0x7e + +struct ac97mixtable_entry { + int reg:8; + unsigned bits:4; + unsigned ofs:4; + unsigned stereo:1; + unsigned mute:1; + unsigned recidx:4; + unsigned mask:1; +}; + +struct ac97_info { + ac97_read *read; + ac97_write *write; + void *devinfo; + char id[4]; + char rev; + unsigned caps, se; + struct ac97mixtable_entry mix[32]; +}; + +struct ac97_codecid { + u_int32_t id; + char *name; +}; + +static const struct ac97mixtable_entry ac97mixtable_default[32] = { + [SOUND_MIXER_VOLUME] = { AC97_MIX_MASTER, 5, 0, 1, 1, 6, 0 }, + [SOUND_MIXER_BASS] = { AC97_MIX_TONE, 4, 8, 0, 0, 0, 1 }, + [SOUND_MIXER_TREBLE] = { AC97_MIX_TONE, 4, 0, 0, 0, 0, 1 }, + [SOUND_MIXER_PCM] = { AC97_MIX_PCM, 5, 0, 1, 1, 0, 0 }, + [SOUND_MIXER_SPEAKER] = { AC97_MIX_BEEP, 4, 1, 0, 1, 0, 0 }, + [SOUND_MIXER_LINE] = { AC97_MIX_LINE, 5, 0, 1, 1, 5, 0 }, + [SOUND_MIXER_MIC] = { AC97_MIX_MIC, 5, 0, 0, 1, 1, 0 }, + [SOUND_MIXER_CD] = { AC97_MIX_CD, 5, 0, 1, 1, 2, 0 }, + [SOUND_MIXER_LINE1] = { AC97_MIX_AUX, 5, 0, 1, 1, 4, 0 }, + [SOUND_MIXER_VIDEO] = { AC97_MIX_VIDEO, 5, 0, 1, 1, 3, 0 }, + [SOUND_MIXER_RECLEV] = { -AC97_MIX_RGAIN, 4, 0, 1, 1, 0, 0 } +}; + +static const unsigned ac97mixdevs = + SOUND_MASK_VOLUME | + SOUND_MASK_PCM | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | + SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_LINE1 | + SOUND_MASK_VIDEO | SOUND_MASK_RECLEV; + +static const unsigned ac97recdevs = + SOUND_MASK_VOLUME | SOUND_MASK_LINE | SOUND_MASK_MIC | + SOUND_MASK_CD | SOUND_MASK_LINE1 | SOUND_MASK_VIDEO; + +static struct ac97_codecid ac97codecid[] = { + { 0x414B4D00, "Asahi Kasei AK4540" }, + { 0x43525900, "Cirrus Logic CS4297" }, + { 0x83847600, "SigmaTel STAC????" }, + { 0x83847604, "SigmaTel STAC9701/3/4/5" }, + { 0x83847605, "SigmaTel STAC9704" }, + { 0x83847608, "SigmaTel STAC9708" }, + { 0x83847609, "SigmaTel STAC9721" }, + { 0, NULL } +}; + +static char *ac97enhancement[] = { + "", + "Analog Devices Phat Stereo", + "Creative Stereo Enhancement", + "National Semi 3D Stereo Enhancement", + "Yamaha Ymersion", + "BBE 3D Stereo Enhancement", + "Crystal Semi 3D Stereo Enhancement", + "Qsound QXpander", + "Spatializer 3D Stereo Enhancement", + "SRS 3D Stereo Enhancement", + "Platform Tech 3D Stereo Enhancement", + "AKM 3D Audio", + "Aureal Stereo Enhancement", + "Aztech 3D Enhancement", + "Binaura 3D Audio Enhancement", + "ESS Technology Stereo Enhancement", + "Harman International VMAx", + "Nvidea 3D Stereo Enhancement", + "Philips Incredible Sound", + "Texas Instruments 3D Stereo Enhancement", + "VLSI Technology 3D Stereo Enhancement", + "TriTech 3D Stereo Enhancement", + "Realtek 3D Stereo Enhancement", + "Samsung 3D Stereo Enhancement", + "Wolfson Microelectronics 3D Enhancement", + "Delta Integration 3D Enhancement", + "SigmaTel 3D Enhancement", + "Reserved 27", + "Rockwell 3D Stereo Enhancement", + "Reserved 29", + "Reserved 30", + "Reserved 31" +}; + +static char *ac97feature[] = { + "mic channel", + "reserved", + "tone", + "simulated stereo", + "headphone", + "bass boost", + "18 bit DAC", + "20 bit DAC", + "18 bit ADC", + "20 bit ADC" +}; + +static int +ac97_setrecsrc(struct ac97_info *codec, int channel) +{ + struct ac97mixtable_entry *e = &codec->mix[channel]; + if (e->recidx > 0) { + int val = e->recidx - 1; + val |= val << 8; + codec->write(codec->devinfo, AC97_REG_RECSEL, val); + return 0; + } else return -1; +} + +static int +ac97_setmixer(struct ac97_info *codec, unsigned channel, unsigned left, unsigned right) +{ + struct ac97mixtable_entry *e = &codec->mix[channel]; + if (e->reg != 0) { + int max, val; + + if (!e->stereo) right = left; + if (e->reg > 0) { + left = 100 - left; + right = 100 - right; + } + + max = (1 << e->bits) - 1; + left = (left * max) / 100; + right = (right * max) / 100; + + val = (left << 8) | right; + + left = (left * 100) / max; + right = (right * 100) / max; + + if (e->reg > 0) { + left = 100 - left; + right = 100 - right; + } + + if (!e->stereo) { + val &= max; + val <<= e->ofs; + if (e->mask) { + int cur = codec->read(codec->devinfo, e->reg); + val |= cur & ~(max << e->ofs); + } + } + if (left == 0 && right == 0 && e->mute == 1) val = AC97_MUTE; + codec->write(codec->devinfo, abs(e->reg), val); + return left | (right << 8); + } else return -1; +} + +#if 0 +static int +ac97_getmixer(struct ac97_info *codec, int channel) +{ + struct ac97mixtable_entry *e = &codec->mix[channel]; + if (channel < SOUND_MIXER_NRDEVICES && e->reg != 0) { + int max, val, volume; + + max = (1 << e->bits) - 1; + val = codec->read(codec->devinfo, e->reg); + if (val == AC97_MUTE && e->mute == 1) volume = 0; + else { + if (e->stereo == 0) val >>= e->ofs; + val &= max; + volume = (val * 100) / max; + if (e->reg > 0) volume = 100 - volume; + } + return volume; + } else return -1; +} +#endif + +static unsigned +ac97_init(struct ac97_info *codec) +{ + unsigned i, j; + u_int32_t id; + + for (i = 0; i < 32; i++) codec->mix[i] = ac97mixtable_default[i]; + + codec->write(codec->devinfo, AC97_REG_POWER, 0); + codec->write(codec->devinfo, AC97_REG_RESET, 0); + DELAY(10000); + + i = codec->read(codec->devinfo, AC97_REG_RESET); + codec->caps = i & 0x03ff; + codec->se = (i & 0x7c00) >> 10; + + id = (codec->read(codec->devinfo, AC97_REG_ID1) << 16) | + codec->read(codec->devinfo, AC97_REG_ID2); + codec->rev = id & 0x000000ff; + + codec->write(codec->devinfo, AC97_MIX_MASTER, 0x20); + if ((codec->read(codec->devinfo, AC97_MIX_MASTER) & 0x20) == 0x20) + codec->mix[SOUND_MIXER_VOLUME].bits++; + codec->write(codec->devinfo, AC97_MIX_MASTER, 0x00); + + if (bootverbose) { + printf("ac97: codec id 0x%8x", id); + for (i = 0; ac97codecid[i].id; i++) { + if (ac97codecid[i].id == id) printf(" (%s)", ac97codecid[i].name); + } + printf("\nac97: codec features "); + for (i = j = 0; i < 10; i++) { + if (codec->caps & (1 << i)) { + printf("%s%s", j? ", " : "", ac97feature[i]); + j++; + } + } + printf("%s%d bit master volume", j? ", " : "", codec->mix[SOUND_MIXER_VOLUME].bits); + printf("%s%s\n", j? ", " : "", ac97enhancement[codec->se]); + } + + if ((codec->read(codec->devinfo, AC97_REG_POWER) & 2) == 0) + printf("ac97: dac not ready\n"); + return 0; +} + +struct ac97_info * +ac97_create(void *devinfo, ac97_read *rd, ac97_write *wr) +{ + struct ac97_info *codec; + + codec = (struct ac97_info *)malloc(sizeof *codec, M_DEVBUF, M_NOWAIT); + if (codec != NULL) { + codec->read = rd; + codec->write = wr; + codec->devinfo = devinfo; + } + return codec; +} + +static int +ac97mix_init(snd_mixer *m) +{ + struct ac97_info *codec = mix_getdevinfo(m); + if (codec == NULL) return -1; + ac97_init(codec); + mix_setdevs(m, ac97mixdevs | ((codec->caps & 4)? SOUND_MASK_BASS | SOUND_MASK_TREBLE : 0)); + mix_setrecdevs(m, ac97recdevs); + return 0; +} + +static int +ac97mix_set(snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + struct ac97_info *codec = mix_getdevinfo(m); + if (codec == NULL) return -1; + return ac97_setmixer(codec, dev, left, right); +} + +static int +ac97mix_setrecsrc(snd_mixer *m, u_int32_t src) +{ + int i; + struct ac97_info *codec = mix_getdevinfo(m); + if (codec == NULL) return -1; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if ((src & (1 << i)) != 0) break; + return (ac97_setrecsrc(codec, i) == 0)? 1 << i : -1; +} + +snd_mixer ac97_mixer = { + "AC97 mixer", + ac97mix_init, + ac97mix_set, + ac97mix_setrecsrc, +}; + diff --git a/sys/dev/sound/pcm/ac97.h b/sys/dev/sound/pcm/ac97.h new file mode 100644 index 0000000..c3460a2 --- /dev/null +++ b/sys/dev/sound/pcm/ac97.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * All rights reserved. + * + * 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. + * + * $Id$ + */ + +typedef u_int32_t (ac97_read)(void *devinfo, int regno); +typedef void (ac97_write)(void *devinfo, int regno, u_int32_t data); + +extern snd_mixer ac97_mixer; +struct ac97_info; + +struct ac97_info *ac97_create(void *devinfo, ac97_read *rd, ac97_write *wr); diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c new file mode 100644 index 0000000..dd383e5 --- /dev/null +++ b/sys/dev/sound/pcm/channel.c @@ -0,0 +1,737 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * Portions Copyright by Luigi Rizzo - 1997-99 + * All rights reserved. + * + * 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. + * + * $Id$ + */ + +#include <dev/pcm/sound.h> + +#define MIN_CHUNK_SIZE 256 /* for uiomove etc. */ +#define DMA_ALIGN_THRESHOLD 4 +#define DMA_ALIGN_MASK (~(DMA_ALIGN_THRESHOLD - 1)) + +#define ISA_DMA(b) (((b)->chan >= 0 && (b)->chan != 4 && (b)->chan < 8)) +#define CANCHANGE(c) (!(c)->buffer.dl) + +static int chn_reinit(pcm_channel *c); +static void chn_stintr(pcm_channel *c); +/* + * 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 length (rl,fl): + + 0 rp,rl fp,fl bufsize + |__________>____________>________| + FREE d READY w FREE + + READY: data written from the process and ready to be sent to the DAC; + FREE: free part of the buffer. + +Both regions can wrap around the end of the buffer. At initialization, +READY is empty, FREE takes all the available space, and dma is +idle. dl contains the length of the current DMA transfer, dl=0 +means that the dma is idle. + +The two boundaries (rp,fp) in the buffers are advanced by DMA [d] +and write() [w] operations. The first portion of the READY region +is used for DMA transfers. The transfer is started at rp and with +chunks of length dl. During DMA operations, dsp_wr_dmaupdate() +updates rp, rl and fl tracking the ISA DMA engine as the transfer +makes progress. +When a new block is written, fp advances and rl,fl are updated +accordingly. + +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. When a +DMA transfer is complete, an interrupt causes dsp_wrintr() to be +called which 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 dsp_wr_dmaupdate() which changes rp, rl and fl. + +The sistem tries to make all DMA transfers use the same size, +play_blocksize or rec_blocksize. The size is either selected by +the user, or computed by the system to correspond to about .25s of +audio. The blocksize must be within a range which is currently: + + min(5ms, 40 bytes) ... 1/2 buffer size. + +When there aren't enough data (write) or space (read), a transfer +is started with a reduced size. + +To reduce problems in case of overruns, the routine which fills up +the buffer should initialize (e.g. by repeating the last value) a +reasonably long area after the last block so that no noise is +produced on overruns. + + * + */ + + +/* XXX this is broken: in the event a bounce buffer is used, data never + * gets copied in or out of the real buffer. fix requires mods to isa_dma.c + * and possibly fixes to other autodma mode clients + */ +static void +chn_isadmabounce(pcm_channel *c) +{ + if (ISA_DMA(&c->buffer)) { + /* tell isa_dma to bounce data in/out */ + } else panic("chn_isadmabounce called on invalid channel"); +} + +static int +chn_polltrigger(pcm_channel *c) +{ + snd_dbuf *b = &c->buffer; + unsigned lim = (c->flags & CHN_F_HAS_SIZE)? c->blocksize : 1; + int trig = 0; + + if (c->flags & CHN_F_MAPPED) + trig = ((b->int_count > b->prev_int_count) || b->first_poll); + else trig = (((c->direction == PCMDIR_PLAY)? b->fl : b->rl) >= lim); + return trig; +} + +static int +chn_pollreset(pcm_channel *c) +{ + snd_dbuf *b = &c->buffer; + + if (c->flags & CHN_F_MAPPED) b->prev_int_count = b->int_count; + b->first_poll = 0; + return 1; +} + +/* + * chn_dmadone() updates pointers and wakes up any process sleeping + * or waiting on a select(). + * Must be called at spltty(). + */ +static void +chn_dmadone(pcm_channel *c) +{ + snd_dbuf *b = &c->buffer; + + chn_dmaupdate(c); + if (ISA_DMA(b)) chn_isadmabounce(c); /* sync bounce buffer */ + wakeup(b); + b->int_count++; + if (b->sel.si_pid && chn_polltrigger(c)) selwakeup(&b->sel); +} + +/* + * chn_dmaupdate() tracks the status of a dma transfer, + * updating pointers. It must be called at spltty(). + * + * NOTE: when we are using auto dma in the device, rl might become + * negative. + */ +void +chn_dmaupdate(pcm_channel *c) +{ + snd_dbuf *b = &c->buffer; + int delta, hwptr = chn_getptr(c); + + if (c->direction == PCMDIR_PLAY) { + delta = (b->bufsize + hwptr - b->rp) % b->bufsize; + b->rp = hwptr; + b->rl -= delta; + b->fl += delta; + } else { + delta = (b->bufsize + hwptr - b->fp) % b->bufsize; + b->fp = hwptr; + b->rl += delta; + b->fl -= delta; + } + b->total += delta; +} + +/* + * Write interrupt routine. Can be called from other places (e.g. + * to start a paused transfer), but with interrupts disabled. + */ +static void +chn_wrintr(pcm_channel *c) +{ + snd_dbuf *b = &c->buffer; + int start; + + if (b->dl) chn_dmadone(c); + + /* + * start another dma operation only if have ready data in the buffer, + * there is no pending abort, have a full-duplex device, or have a + * half duplex device and there is no pending op on the other side. + * + * Force transfers to be aligned to a boundary of 4, which is + * needed when doing stereo and 16-bit. + */ + if (c->flags & CHN_F_MAPPED) start = c->flags & CHN_F_TRIGGERED; + else start = (b->rl >= DMA_ALIGN_THRESHOLD && !(c->flags & CHN_F_ABORTING)); + if (start) { + int l; + chn_dmaupdate(c); + l = min(b->rl, c->blocksize) & DMA_ALIGN_MASK; + if (c->flags & CHN_F_MAPPED) l = c->blocksize; + /* + * check if we need to reprogram the DMA on the sound card. + * This happens if the size has changed _and_ the new size + * is smaller, or it matches the blocksize. + * + * 0 <= l <= blocksize + * 0 <= dl <= blocksize + * reprog if (dl == 0 || l != dl) + * was: + * l != b->dl && (b->dl == 0 || l < b->dl || l == c->blocksize) + */ + if (b->dl == 0 || l != b->dl) { + /* size has changed. Stop and restart */ + DEB(printf("wrintr: bsz %d -> %d, rp %d rl %d\n", + b->dl, l, b->rp, b->rl)); + if (b->dl) chn_trigger(c, PCMTRIG_STOP); + b->dl = l; /* record new transfer size */ + chn_trigger(c, PCMTRIG_START); + } + } else { + /* cannot start a new dma transfer */ + DEB(printf("cannot start wr-dma flags 0x%08x rp %d rl %d\n", + c->flags, b->rp, b->rl)); + if (b->dl) { /* was active */ + b->dl = 0; + chn_trigger(c, PCMTRIG_STOP); +#if 0 + if (c->flags & CHN_F_WRITING) + DEB(printf("got wrint while reloading\n")); + else if (b->rl <= 0) /* XXX added 980110 lr */ + chn_resetbuf(c); +#endif + } + } +} + +/* + * 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 +chn_write(pcm_channel *c, struct uio *buf) +{ + int l, w, timeout, ret = 0; + long s; + snd_dbuf *b = &c->buffer; + + if (c->flags & CHN_F_WRITING) { + /* This shouldn't happen and is actually silly + * - will never wake up, just timeout; why not sleep on b? + */ + tsleep(&s, PZERO, "pcmwrW", hz); + return EBUSY; + } + c->flags |= CHN_F_WRITING; + while (buf->uio_resid >= DMA_ALIGN_THRESHOLD) { + s = spltty(); + chn_dmaupdate(c); + splx(s); + if (b->fl < DMA_ALIGN_THRESHOLD) { + if (c->flags & CHN_F_NBIO) break; + timeout = (buf->uio_resid >= b->dl)? hz : 1; + ret = tsleep(b, PRIBIO | PCATCH, "pcmwr", timeout); + if (ret == EINTR) chn_abort(c); + if (ret == EINTR || ret == ERESTART) break; + ret = 0; + continue; + } + /* ensure we always have a whole number of samples */ + l = min(b->fl, b->bufsize - b->fp) & DMA_ALIGN_MASK; + w = c->feeder->feed(c->feeder, b->buf + b->fp, l, buf); + s = spltty(); + b->rl += w; + b->fl -= w; + b->fp = (b->fp + w) % b->bufsize; + splx(s); + if (b->rl && !b->dl) chn_stintr(c); + } + c->flags &= ~CHN_F_WRITING; + return ret; +} + +/* + * SOUND INPUT + * + +The input part is similar to the output one, with a circular buffer +split in two regions, and boundaries advancing because of read() calls +[r] or dma operation [d]. At initialization, as for the write +routine, READY is empty, and FREE takes all the space. + + 0 rp,rl fp,fl bufsize + |__________>____________>________| + FREE r READY d FREE + +Operation is as follows: upon user read (dsp_read_body()) a DMA read +is started if not already active (marked by b->dl > 0), +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, with a preferred constant transfer size equal to +rec_blocksize, and fallback to smaller sizes if no space is available. + + */ + +/* read interrupt routine. Must be called with interrupts blocked. */ +static void +chn_rdintr(pcm_channel *c) +{ + snd_dbuf *b = &c->buffer; + int start; + + if (b->dl) chn_dmadone(c); + + DEB(printf("rdintr: start dl %d, rp:rl %d:%d, fp:fl %d:%d\n", + b->dl, b->rp, b->rl, b->fp, b->fl)); + /* Restart if have enough free space to absorb overruns */ + if (c->flags & CHN_F_MAPPED) start = c->flags & CHN_F_TRIGGERED; + else start = (b->fl > 0x200 && !(c->flags & CHN_F_ABORTING)); + if (start) { + int l = min(b->fl - 0x100, c->blocksize); + if (c->flags & CHN_F_MAPPED) l = c->blocksize; + l &= DMA_ALIGN_MASK ; /* realign sizes */ + + DEB(printf("rdintr: dl %d -> %d\n", b->dl, l);) + if (l != b->dl) { + /* size has changed. Stop and restart */ + if (b->dl) { + chn_trigger(c, PCMTRIG_STOP); + chn_dmaupdate(c); + l = min(b->fl - 0x100, c->blocksize); + l &= DMA_ALIGN_MASK ; /* realign sizes */ + } + b->dl = l; + chn_trigger(c, PCMTRIG_START); + } + } else { + if (b->dl) { /* was active */ + b->dl = 0; + chn_dmaupdate(c); + chn_trigger(c, PCMTRIG_STOP); + } + } +} + +/* + * 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. + * + * 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 +chn_read(pcm_channel *c, struct uio *buf) +{ + int w, l, timeout, limit, ret = 0; + long s; + snd_dbuf *b = &c->buffer; + + if (c->flags & CHN_F_READING) { + /* This shouldn't happen and is actually silly */ + tsleep(&s, PZERO, "pcmrdR", hz); + return (EBUSY); + } + + if (!b->rl & !b->dl) chn_stintr(c); + c->flags |= CHN_F_READING; + limit = buf->uio_resid - c->blocksize; + if (limit < 0) limit = 0; + while (buf->uio_resid > limit) { + s = spltty(); + chn_dmaupdate(c); + splx(s); + if (b->rl < DMA_ALIGN_THRESHOLD) { + if (c->flags & CHN_F_NBIO) break; + timeout = (buf->uio_resid - limit >= b->dl)? hz : 1; + ret = tsleep(b, PRIBIO | PCATCH, "pcmrd", timeout); + if (ret == EINTR) chn_abort(c); + if (ret == EINTR || ret == ERESTART) break; + ret = 0; + continue; + } + /* ensure we always have a whole number of samples */ + l = min(b->rl, b->bufsize - b->rp) & DMA_ALIGN_MASK; + w = c->feeder->feed(c->feeder, b->buf + b->rp, l, buf); + s = spltty(); + b->rl -= w; + b->fl += w; + b->rp = (b->rp + w) % b->bufsize; + splx(s); + } + c->flags &= ~CHN_F_READING; + return ret; +} + +void +chn_intr(pcm_channel *c) +{ + if (!c->buffer.dl) chn_reinit(c); + if (c->direction == PCMDIR_PLAY) chn_wrintr(c); else chn_rdintr(c); +} + +static void +chn_stintr(pcm_channel *c) +{ + u_long s; + s = spltty(); + chn_intr(c); + splx(s); +} + +static void +chn_dma_setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + snd_dbuf *b = (snd_dbuf *)arg; + + if (bootverbose) { + printf("pcm: setmap %lx, %lx; ", (unsigned long)segs->ds_addr, + (unsigned long)segs->ds_len); + printf("%p -> %lx\n", b->buf, (unsigned long)vtophys(b->buf)); + } +} + +int +chn_allocbuf(snd_dbuf *b, bus_dma_tag_t parent_dmat) +{ + if (bus_dmamem_alloc(parent_dmat, (void **)&b->buf, + BUS_DMA_NOWAIT, &b->dmamap)) return -1; + if (bus_dmamap_load(parent_dmat, b->dmamap, b->buf, + b->bufsize, chn_dma_setmap, b, 0)) return -1; + return 0; +} + +void +chn_resetbuf(pcm_channel *c) +{ + snd_dbuf *b = &c->buffer; + u_int16_t data, *p; + u_int32_t i; + + c->buffer.sample_size = 1; + c->buffer.sample_size <<= (c->hwfmt & AFMT_STEREO)? 1 : 0; + c->buffer.sample_size <<= (c->hwfmt & AFMT_16BIT)? 1 : 0; + /* rely on bufsize & 3 == 0 */ + if (c->hwfmt & AFMT_SIGNED) data = 0x00; else data = 0x80; + if (c->hwfmt & AFMT_16BIT) data <<= 8; else data |= data << 8; + if (c->hwfmt & AFMT_BIGENDIAN) + data = ((data >> 8) & 0x00ff) | ((data << 8) & 0xff00); + for (i = 0, p = (u_int16_t *)b->buf; i < b->bufsize; i += 2) + *p++ = data; + b->rp = b->fp = 0; + b->dl = b->rl = 0; + b->prev_total = b->total = 0; + b->prev_int_count = b->int_count = 0; + b->first_poll = 1; + b->fl = b->bufsize; +} + +void +buf_isadma(snd_dbuf *b, int go) +{ + if (ISA_DMA(b)) { + if (go == PCMTRIG_START) isa_dmastart(b->dir | B_RAW, b->buf, + b->bufsize, b->chan); + else { + isa_dmastop(b->chan); + isa_dmadone(b->dir | B_RAW, b->buf, b->bufsize, + b->chan); + } + } else panic("buf_isadma called on invalid channel"); +} + +int +buf_isadmaptr(snd_dbuf *b) +{ + if (ISA_DMA(b)) { + int i = b->dl? isa_dmastatus(b->chan) : b->bufsize; + if (i < 0) i = 0; + return b->bufsize - i; + } else panic("buf_isadmaptr called on invalid channel"); + return -1; +} + +/* + * snd_sync waits until the space in the given channel goes above + * a threshold. The threshold is checked against fl or rl respectively. + * Assume that the condition can become true, do not check here... + */ +int +chn_sync(pcm_channel *c, int threshold) +{ + u_long s, rdy; + int ret; + snd_dbuf *b = &c->buffer; + + for (;;) { + s = spltty(); + chn_dmaupdate(c); + rdy = (c->direction == PCMDIR_PLAY)? b->fl : b->rl; + if (rdy <= threshold) { + ret = tsleep((caddr_t)b, PRIBIO | PCATCH, "pcmsyn", 1); + splx(s); + if (ret == ERESTART || ret == EINTR) { + printf("tsleep returns %d\n", ret); + return -1; + } + } else break; + } + splx(s); + return 0; +} + +int +chn_poll(pcm_channel *c, int ev, struct proc *p) +{ + snd_dbuf *b = &c->buffer; + u_long s = spltty(); + if (b->dl) chn_dmaupdate(c); + splx(s); + if (chn_polltrigger(c) && chn_pollreset(c)) return ev; + else { + selrecord(p, &b->sel); + return 0; + } +} + +/* + * chn_abort is a non-blocking function which aborts a pending + * DMA transfer and flushes the buffers. + * It returns the number of bytes that have not been transferred. + */ +int +chn_abort(pcm_channel *c) +{ + long s; + int missing = 0; + snd_dbuf *b = &c->buffer; + + s = spltty(); + if (b->dl) { + b->dl = 0; + c->flags &= ~((c->direction == PCMDIR_PLAY)? CHN_F_WRITING : CHN_F_READING); + chn_trigger(c, PCMTRIG_ABORT); + chn_dmadone(c); + } + missing = b->rl; + splx(s); + return missing; +} + +/* + * this routine tries to flush the dma transfer. It is called + * on a close. We immediately abort any read DMA + * operation, and then wait for the play buffer to drain. + */ + +int +chn_flush(pcm_channel *c) +{ + int ret, count = 10; + snd_dbuf *b = &c->buffer; + + DEB(printf("snd_flush c->flags 0x%08x\n", c->flags)); + c->flags |= CHN_F_CLOSING; + if (c->direction != PCMDIR_PLAY) chn_abort(c); + else while (b->dl) { + /* still pending output data. */ + ret = tsleep((caddr_t)b, PRIBIO | PCATCH, "pcmflu", hz); + chn_dmaupdate(c); + DEB(printf("snd_sync: now rl : fl %d : %d\n", b->rl, b->fl)); + if (ret == EINTR) { + printf("tsleep returns %d\n", ret); + return -1; + } + if (ret && --count == 0) { + printf("timeout flushing dbuf_out, cnt 0x%x flags 0x%x\n", + b->rl, c->flags); + break; + } + } + c->flags &= ~CHN_F_CLOSING; + if (c->direction == PCMDIR_PLAY) chn_abort(c); + return 0; +} + +int +chn_reset(pcm_channel *c) +{ + chn_abort(c); + c->flags &= CHN_F_RESET; + chn_resetbuf(c); + c->flags |= CHN_F_INIT; + return 0; +} + +static int +chn_reinit(pcm_channel *c) +{ + if ((c->flags & CHN_F_INIT) && CANCHANGE(c)) { + chn_setformat(c, c->format); + chn_setspeed(c, c->speed); + chn_setblocksize(c, c->blocksize); + chn_setvolume(c, (c->volume >> 8) & 0xff, c->volume & 0xff); + c->flags &= ~CHN_F_INIT; + return 1; + } + return 0; +} + +int +chn_init(pcm_channel *c, void *devinfo, int dir) +{ + c->flags = 0; + c->feeder = &feeder_root; + c->buffer.chan = -1; + c->devinfo = c->init(devinfo, &c->buffer, c, dir); + chn_setdir(c, dir); + return 0; +} + +int +chn_setdir(pcm_channel *c, int dir) +{ + c->direction = dir; + if (ISA_DMA(&c->buffer)) + c->buffer.dir = (dir == PCMDIR_PLAY)? B_WRITE : B_READ; + return c->setdir(c->devinfo, c->direction); +} + +int +chn_setvolume(pcm_channel *c, int left, int right) +{ + /* could add a feeder for volume changing if channel returns -1 */ + if (CANCHANGE(c)) { + return -1; + } + c->volume = (left << 8) | right; + c->flags |= CHN_F_INIT; + return 0; +} + +int +chn_setspeed(pcm_channel *c, int speed) +{ + /* could add a feeder for rate conversion */ + if (CANCHANGE(c)) { + c->speed = c->setspeed(c->devinfo, speed); + return c->speed; + } + c->speed = speed; + c->flags |= CHN_F_INIT; + return 0; +} + +int +chn_setformat(pcm_channel *c, u_int32_t fmt) +{ + if (CANCHANGE(c)) { + c->hwfmt = c->format = fmt; + c->hwfmt = chn_feedchain(c); + chn_resetbuf(c); + c->setformat(c->devinfo, c->hwfmt); + return fmt; + } + c->format = fmt; + c->flags |= CHN_F_INIT; + return 0; +} + +int +chn_setblocksize(pcm_channel *c, int blksz) +{ + if (CANCHANGE(c)) { + c->flags &= ~CHN_F_HAS_SIZE; + if (blksz >= 2) c->flags |= CHN_F_HAS_SIZE; + blksz = abs(blksz); + if (blksz < 2) blksz = (c->buffer.sample_size * c->speed) >> 2; + RANGE(blksz, 1024, c->buffer.bufsize / 4); + blksz &= ~3; + c->blocksize = c->setblocksize(c->devinfo, blksz); + return c->blocksize; + } + c->blocksize = blksz; + c->flags |= CHN_F_INIT; + return 0; +} + +int +chn_trigger(pcm_channel *c, int go) +{ + return c->trigger(c->devinfo, go); +} + +int +chn_getptr(pcm_channel *c) +{ + return c->getptr(c->devinfo); +} + +pcmchan_caps * +chn_getcaps(pcm_channel *c) +{ + return c->getcaps(c->devinfo); +} diff --git a/sys/dev/sound/pcm/channel.h b/sys/dev/sound/pcm/channel.h new file mode 100644 index 0000000..8667a9f --- /dev/null +++ b/sys/dev/sound/pcm/channel.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * All rights reserved. + * + * 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. + * + * $Id$ + */ + +int chn_write(pcm_channel *c, struct uio *buf); +int chn_read(pcm_channel *c, struct uio *buf); +int chn_sync(pcm_channel *c, int threshold); +int chn_flush(pcm_channel *c); +int chn_poll(pcm_channel *c, int ev, struct proc *p); + +int chn_init(pcm_channel *c, void *devinfo, int dir); +int chn_setdir(pcm_channel *c, int dir); +int chn_reset(pcm_channel *c); +int chn_setvolume(pcm_channel *c, int left, int right); +int chn_setspeed(pcm_channel *c, int speed); +int chn_setformat(pcm_channel *c, u_int32_t fmt); +int chn_setblocksize(pcm_channel *c, int blksz); +int chn_trigger(pcm_channel *c, int go); +int chn_getptr(pcm_channel *c); +pcmchan_caps *chn_getcaps(pcm_channel *c); + +int chn_allocbuf(snd_dbuf *b, bus_dma_tag_t parent_dmat); +void chn_resetbuf(pcm_channel *c); +void chn_intr(pcm_channel *c); +void chn_dmaupdate(pcm_channel *c); +int chn_abort(pcm_channel *c); + +void buf_isadma(snd_dbuf *b, int go); +int buf_isadmaptr(snd_dbuf *b); +int chn_feedchain(pcm_channel *c); + +extern pcm_feeder feeder_root; + +#define PCMDIR_PLAY 1 +#define PCMDIR_REC -1 + +#define PCMTRIG_START 1 +#define PCMTRIG_STOP 0 +#define PCMTRIG_ABORT -1 + +#define CHN_F_READING 0x00000001 /* have a pending read */ +#define CHN_F_WRITING 0x00000002 /* have a pending write */ +#define CHN_F_CLOSING 0x00000004 /* a pending close */ +#define CHN_F_ABORTING 0x00000008 /* a pending abort */ +#define CHN_F_PENDING_IO (CHN_F_READING | CHN_F_WRITING) +#define CHN_F_RUNNING 0x00000010 /* dma is running */ +#define CHN_F_TRIGGERED 0x00000020 + +#define CHN_F_BUSY 0x00001000 /* has been opened */ +#define CHN_F_HAS_SIZE 0x00002000 /* user set block size */ +#define CHN_F_NBIO 0x00004000 /* do non-blocking i/o */ +#define CHN_F_INIT 0x00008000 /* changed parameters. need init */ +#define CHN_F_MAPPED 0x00010000 /* has been mmap()ed */ + + +#define CHN_F_RESET (CHN_F_BUSY) diff --git a/sys/dev/sound/pcm/datatypes.h b/sys/dev/sound/pcm/datatypes.h new file mode 100644 index 0000000..50928f6 --- /dev/null +++ b/sys/dev/sound/pcm/datatypes.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * All rights reserved. + * + * 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. + * + * $Id$ + */ + +typedef struct _snd_mixer snd_mixer; +typedef struct _snd_dbuf snd_dbuf; +typedef struct _snddev_info snddev_info; +typedef struct _pcmchan_caps pcmchan_caps; +typedef struct _pcm_feeder pcm_feeder; +typedef struct _pcm_channel pcm_channel; + +typedef int (mix_set_t)(snd_mixer *m, unsigned dev, unsigned left, unsigned right); +typedef int (mix_recsrc_t)(snd_mixer *m, u_int32_t src); +typedef int (mix_init_t)(snd_mixer *m); + +struct _snd_mixer { + char name[64]; + mix_init_t *init; + mix_set_t *set; + mix_recsrc_t *setrecsrc; + + void *devinfo; + u_int32_t devs; + u_int32_t recdevs; + u_int32_t recsrc; + u_int16_t level[32]; +}; + +/* + * descriptor of a dma buffer. See dmabuf.c for documentation. + * (rp,rl) and (fp,fl) identify the READY and FREE regions of the + * buffer. dl contains the length used for dma transfer, dl>0 also + * means that the channel is busy and there is a DMA transfer in progress. + */ + +struct _snd_dbuf { + char *buf; + int bufsize; + volatile int rp, fp; /* pointers to the ready and free area */ + volatile int dl; /* transfer size */ + volatile int rl, fl; /* lenght of ready and free areas. */ + volatile u_int32_t int_count, prev_int_count; + int chan, dir; /* dma channel */ + int sample_size; /* 1, 2, 4 */ + struct selinfo sel; + u_long total; /* total bytes processed */ + u_long prev_total; /* copy of the above when GETxPTR called */ + int first_poll; + bus_dmamap_t dmamap; +}; + +typedef int (pcmfeed_init_t)(pcm_feeder *feeder); +typedef int (pcmfeed_free_t)(pcm_feeder *feeder); +typedef int (pcmfeed_feed_t)(pcm_feeder *feeder, u_int8_t *buffer, u_int32_t count, struct uio *stream); + +struct _pcm_feeder { + char name[16]; + pcmfeed_init_t *init; + pcmfeed_free_t *free; + pcmfeed_feed_t *feed; + void *data; + pcm_feeder *source; +}; + +struct _pcmchan_caps { + u_int32_t minspeed, maxspeed; + u_int32_t formats, bestfmt; +}; + +typedef void *(pcmchan_init_t)(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); +typedef int (pcmchan_setdir_t)(void *data, int dir); +typedef int (pcmchan_setformat_t)(void *data, u_int32_t format); +typedef int (pcmchan_setspeed_t)(void *data, u_int32_t speed); +typedef int (pcmchan_setblocksize_t)(void *data, u_int32_t blocksize); +typedef int (pcmchan_trigger_t)(void *data, int go); +typedef int (pcmchan_getptr_t)(void *data); +typedef pcmchan_caps *(pcmchan_getcaps_t)(void *data); + +struct _pcm_channel { + pcmchan_init_t *init; + pcmchan_setdir_t *setdir; + pcmchan_setformat_t *setformat; + pcmchan_setspeed_t *setspeed; + pcmchan_setblocksize_t *setblocksize; + pcmchan_trigger_t *trigger; + pcmchan_getptr_t *getptr; + pcmchan_getcaps_t *getcaps; + pcm_feeder *feeder; + + int volume; + u_int32_t speed; + u_int32_t flags; + u_int32_t format, hwfmt; + u_int32_t blocksize; + + int direction; + snd_dbuf buffer; + void *devinfo; +}; + +#define SND_STATUSLEN 64 +/* descriptor of audio device */ +struct _snddev_info { + pcm_channel *play, *rec, **aplay, **arec, fakechan; + unsigned playcount, reccount, chancount; + snd_mixer mixer; + u_long magic; + unsigned flags; + void *devinfo; + char status[SND_STATUSLEN]; +}; + +/* mixer description structure and macros - these should go away, + * only sb.[ch] and mss.[ch] use them + */ +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 MIX_NONE(name) MIX_ENT(name, 0,0,0,0, 0,0,0,0) + diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c new file mode 100644 index 0000000..a868e92 --- /dev/null +++ b/sys/dev/sound/pcm/dsp.c @@ -0,0 +1,543 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * All rights reserved. + * + * 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. + * + * $Id$ + */ + +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/kernel.h> + +#include <dev/pcm/sound.h> + +static int getchns(snddev_info *d, int chan, pcm_channel **rdch, pcm_channel **wrch); + +static pcm_channel * +allocchn(snddev_info *d, int direction) +{ + pcm_channel *chns = (direction == PCMDIR_PLAY)? d->play : d->rec; + int i, cnt = (direction == PCMDIR_PLAY)? d->playcount : d->reccount; + for (i = 0; i < cnt; i++) { + if (!(chns[i].flags & CHN_F_BUSY)) { + chns[i].flags |= CHN_F_BUSY; + return &chns[i]; + } + } + return NULL; +} + +static int +getchns(snddev_info *d, int chan, pcm_channel **rdch, pcm_channel **wrch) +{ + if ((d->flags & SD_F_PRIO_SET) == SD_F_PRIO_SET) + panic("read and write both prioritised"); + if (d->flags & SD_F_SIMPLEX) { + *rdch = (d->flags & SD_F_PRIO_RD)? d->arec[chan] : &d->fakechan; + *wrch = (d->flags & SD_F_PRIO_WR)? d->aplay[chan] : &d->fakechan; + } else { + *rdch = d->arec[chan]; + *wrch = d->aplay[chan]; + } + return 0; +} + +static void +setchns(snddev_info *d, int chan) +{ + if ((d->flags & SD_F_PRIO_SET) == SD_F_PRIO_SET) + panic("read and write both prioritised"); + d->flags |= SD_F_DIR_SET; + if (d->flags & SD_F_EVILSB16) { + if ((d->flags & SD_F_PRIO_RD) && (d->aplay[chan])) { + pcm_channel *tmp; + tmp = d->arec[chan]; + d->arec[chan] = d->aplay[chan]; + d->aplay[chan] = tmp; + } + if (d->aplay[chan]) chn_setdir(d->aplay[chan], PCMDIR_PLAY); + if (d->arec[chan]) chn_setdir(d->arec[chan], PCMDIR_REC); + } +} + +int +dsp_open(snddev_info *d, int chan, int oflags, int devtype) +{ + pcm_channel *rdch = NULL, *wrch = NULL; + u_int32_t fmt; + + if (chan >= d->chancount) return ENODEV; + if (d->aplay[chan] || d->arec[chan]) return EBUSY; + if (oflags & FREAD) { + rdch = allocchn(d, PCMDIR_REC); + if (!rdch) return EBUSY; + } + if (oflags & FWRITE) { + wrch = allocchn(d, PCMDIR_PLAY); + if (!wrch) { + if (rdch) rdch->flags &= ~CHN_F_BUSY; + return EBUSY; + } + } + d->aplay[chan] = wrch; + d->arec[chan] = rdch; + switch (devtype) { + case SND_DEV_DSP16: + fmt = AFMT_S16_LE; + break; + + case SND_DEV_DSP: + fmt = AFMT_U8; + break; + + case SND_DEV_AUDIO: + fmt = AFMT_MU_LAW; + break; + + default: + return ENXIO; + } + + if (rdch) { + chn_reset(rdch); + if (oflags & O_NONBLOCK) rdch->flags |= CHN_F_NBIO; + rdch->volume = (100 << 8) | 100; + rdch->format = fmt; + rdch->speed = DSP_DEFAULT_SPEED; + rdch->blocksize = 2048; + } + if (wrch) { + chn_reset(wrch); + if (oflags & O_NONBLOCK) wrch->flags |= CHN_F_NBIO; + wrch->volume = (100 << 8) | 100; + wrch->format = fmt; + wrch->speed = DSP_DEFAULT_SPEED; + wrch->blocksize = 2048; + } + return 0; +} + +int +dsp_close(snddev_info *d, int chan, int devtype) +{ + pcm_channel *rdch, *wrch; + + d->flags &= ~SD_F_TRANSIENT; + getchns(d, chan, &rdch, &wrch); + + if (rdch) { + chn_abort(rdch); + rdch->flags &= ~(CHN_F_BUSY | CHN_F_RUNNING | CHN_F_MAPPED); + } + if (wrch) wrch->flags &= ~(CHN_F_BUSY | CHN_F_RUNNING | CHN_F_MAPPED); + d->aplay[chan] = NULL; + d->arec[chan] = NULL; + return 0; +} + +int +dsp_read(snddev_info *d, int chan, struct uio *buf, int flag) +{ + pcm_channel *rdch, *wrch; + + if (!(d->flags & SD_F_PRIO_SET)) d->flags |= SD_F_PRIO_RD; + if (!(d->flags & SD_F_DIR_SET)) setchns(d, chan); + getchns(d, chan, &rdch, &wrch); + if (!rdch || !(rdch->flags & CHN_F_BUSY)) + panic("dsp_read: non%s channel", rdch? "busy" : "existant"); + if (rdch->flags & CHN_F_MAPPED) return EINVAL; + if (!(rdch->flags & CHN_F_RUNNING)) rdch->flags |= CHN_F_RUNNING; + return chn_read(rdch, buf); +} + +int +dsp_write(snddev_info *d, int chan, struct uio *buf, int flag) +{ + pcm_channel *rdch, *wrch; + + if (!(d->flags & SD_F_PRIO_SET)) d->flags |= SD_F_PRIO_WR; + if (!(d->flags & SD_F_DIR_SET)) setchns(d, chan); + getchns(d, chan, &rdch, &wrch); + if (!wrch || !(wrch->flags & CHN_F_BUSY)) + panic("dsp_write: non%s channel", wrch? "busy" : "existant"); + if (wrch->flags & CHN_F_MAPPED) return EINVAL; + if (!(wrch->flags & CHN_F_RUNNING)) wrch->flags |= CHN_F_RUNNING; + return chn_write(wrch, buf); +} + +int +dsp_ioctl(snddev_info *d, int chan, u_long cmd, caddr_t arg) +{ + int ret = 0, *arg_i = (int *)arg; + u_long s; + pcm_channel *wrch = NULL, *rdch = NULL; + + getchns(d, chan, &rdch, &wrch); + + /* + * 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 (wrch && wrch->buffer.dl) chn_dmaupdate(wrch); + *arg_i = wrch? wrch->buffer.fl : 0; + break; + + case AIOSSIZE: /* set the current blocksize */ + { + struct snd_size *p = (struct snd_size *)arg; + splx(s); + if (wrch) chn_setblocksize(wrch, p->play_size); + if (rdch) chn_setblocksize(rdch, p->rec_size); + } + /* FALLTHROUGH */ + case AIOGSIZE: /* get the current blocksize */ + { + struct snd_size *p = (struct snd_size *)arg; + if (wrch) p->play_size = wrch->blocksize; + if (rdch) p->rec_size = rdch->blocksize; + } + break; + + case AIOSFMT: + { + snd_chan_param *p = (snd_chan_param *)arg; + splx(s); + if (wrch) { + chn_setformat(wrch, p->play_format); + chn_setspeed(wrch, p->play_rate); + } + if (rdch) { + chn_setformat(rdch, p->rec_format); + chn_setspeed(rdch, p->rec_rate); + } + } + /* FALLTHROUGH */ + + case AIOGFMT: + { + snd_chan_param *p = (snd_chan_param *)arg; + p->play_rate = wrch? wrch->speed : 0; + p->rec_rate = rdch? rdch->speed : 0; + p->play_format = wrch? wrch->format : 0; + p->rec_format = rdch? rdch->format : 0; + } + break; + + case AIOGCAP: /* get capabilities */ + { + snd_capabilities *p = (snd_capabilities *)arg; + pcmchan_caps *pcaps = NULL, *rcaps = NULL; + if (rdch) rcaps = chn_getcaps(rdch); + if (wrch) pcaps = chn_getcaps(wrch); + p->rate_min = max(rcaps? rcaps->minspeed : 0, + pcaps? pcaps->minspeed : 0); + p->rate_max = min(rcaps? rcaps->maxspeed : 1000000, + pcaps? pcaps->maxspeed : 1000000); + p->bufsize = min(rdch? rdch->buffer.bufsize : 1000000, + wrch? wrch->buffer.bufsize : 1000000); + /* XXX bad on sb16 */ + p->formats = (rcaps? rcaps->formats : 0xffffffff) & + (pcaps? pcaps->formats : 0xffffffff); + p->mixers = 1; /* default: one mixer */ + p->inputs = d->mixer.devs; + p->left = p->right = 100; + } + break; + + case AIOSTOP: + if (*arg_i == AIOSYNC_PLAY && wrch) *arg_i = chn_abort(wrch); + else if (*arg_i == AIOSYNC_CAPTURE && rdch) *arg_i = chn_abort(rdch); + else { + splx(s); + printf("AIOSTOP: bad channel 0x%x\n", *arg_i); + *arg_i = 0; + } + break; + + case AIOSYNC: + printf("AIOSYNC chan 0x%03lx pos %lu 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 (rdch && rdch->buffer.dl) chn_dmaupdate(rdch); + *arg_i = rdch? rdch->buffer.rl : 0; + break; + + case FIOASYNC: /*set/clear async i/o */ + DEB( printf("FIOASYNC\n") ; ) + break; + + case SNDCTL_DSP_NONBLOCK: + case FIONBIO: /* set/clear non-blocking i/o */ + if (rdch) rdch->flags &= ~CHN_F_NBIO; + if (wrch) wrch->flags &= ~CHN_F_NBIO; + if (*arg_i) { + if (rdch) rdch->flags |= CHN_F_NBIO; + if (wrch) wrch->flags |= CHN_F_NBIO; + } + break; + + /* + * Finally, here is the linux-compatible ioctl interface + */ + #define THE_REAL_SNDCTL_DSP_GETBLKSIZE _IOWR('P', 4, int) + case THE_REAL_SNDCTL_DSP_GETBLKSIZE: + case SNDCTL_DSP_GETBLKSIZE: + *arg_i = wrch? wrch->blocksize : 0; /* XXX rdch? */ + break ; + + case SNDCTL_DSP_SETBLKSIZE: + splx(s); + if (wrch) chn_setblocksize(wrch, *arg_i); + if (rdch) chn_setblocksize(rdch, *arg_i); + break; + + case SNDCTL_DSP_RESET: + DEB(printf("dsp reset\n")); + if (wrch) chn_abort(wrch); + if (rdch) chn_abort(rdch); + break; + + case SNDCTL_DSP_SYNC: + printf("dsp sync\n"); + splx(s); + if (wrch) chn_sync(wrch, wrch->buffer.bufsize - 4); + break; + + case SNDCTL_DSP_SPEED: + splx(s); + if (wrch) chn_setspeed(wrch, *arg_i); + if (rdch) chn_setspeed(rdch, *arg_i); + /* fallthru */ + + case SOUND_PCM_READ_RATE: + *arg_i = wrch? wrch->speed : rdch->speed; + break; + + case SNDCTL_DSP_STEREO: + splx(s); + if (wrch) chn_setformat(wrch, (wrch->format & ~AFMT_STEREO) | + ((*arg_i)? AFMT_STEREO : 0)); + if (rdch) chn_setformat(rdch, (rdch->format & ~AFMT_STEREO) | + ((*arg_i)? AFMT_STEREO : 0)); + *arg_i = ((wrch? wrch->format : rdch->format) & AFMT_STEREO)? 1 : 0; + break; + + case SOUND_PCM_WRITE_CHANNELS: + splx(s); + if (wrch) chn_setformat(wrch, (wrch->format & ~AFMT_STEREO) | + ((*arg_i == 2)? AFMT_STEREO : 0)); + if (rdch) chn_setformat(rdch, (rdch->format & ~AFMT_STEREO) | + ((*arg_i == 2)? AFMT_STEREO : 0)); + /* fallthru */ + + case SOUND_PCM_READ_CHANNELS: + *arg_i = ((wrch? wrch->format : rdch->format) & AFMT_STEREO)? 2 : 1; + break; + + case SNDCTL_DSP_GETFMTS: /* returns a mask of supported fmts */ + *arg_i = wrch? chn_getcaps(wrch)->formats : chn_getcaps(rdch)->formats; + break ; + + case SNDCTL_DSP_SETFMT: /* sets _one_ format */ + splx(s); + if (wrch) chn_setformat(wrch, *arg_i); + if (rdch) chn_setformat(rdch, *arg_i); + *arg_i = wrch? wrch->format : rdch->format; + break; + + case SNDCTL_DSP_SUBDIVIDE: + /* XXX watch out, this is RW! */ + DEB(printf("SNDCTL_DSP_SUBDIVIDE unimplemented\n");) + break; + + case SNDCTL_DSP_SETFRAGMENT: + /* XXX watch out, this is RW! */ + DEB(printf("SNDCTL_DSP_SETFRAGMENT 0x%08x\n", *(int *)arg)); + { + int bytes = 1 << min(*arg_i & 0xffff, 16); + int count = (*arg_i >> 16) & 0xffff; + pcm_channel *c = wrch? wrch : rdch; + splx(s); + if (rdch) chn_setblocksize(rdch, bytes); + if (wrch) chn_setblocksize(wrch, bytes); + + /* eg: 4dwave can only interrupt at buffer midpoint, so + * it will force blocksize == bufsize/2 + */ + count = c->buffer.bufsize / c->blocksize; + bytes = ffs(c->blocksize) - 1; + *arg_i = (count << 16) | bytes; + } + break; + + case SNDCTL_DSP_GETISPACE: + /* return space available in the input queue */ + { + audio_buf_info *a = (audio_buf_info *)arg; + if (rdch) { + snd_dbuf *b = &rdch->buffer; + if (b->dl) chn_dmaupdate(rdch); + a->bytes = b->fl; + a->fragments = 1; + a->fragstotal = b->bufsize / rdch->blocksize; + a->fragsize = rdch->blocksize; + } + } + break; + + case SNDCTL_DSP_GETOSPACE: + /* return space available in the output queue */ + { + audio_buf_info *a = (audio_buf_info *)arg; + if (wrch) { + snd_dbuf *b = &wrch->buffer; + if (b->dl) chn_dmaupdate(wrch); + a->bytes = b->fl; + a->fragments = 1; + a->fragstotal = b->bufsize / wrch->blocksize; + a->fragsize = wrch->blocksize; + } + } + break; + + case SNDCTL_DSP_GETIPTR: + { + count_info *a = (count_info *)arg; + if (rdch) { + snd_dbuf *b = &rdch->buffer; + if (b->dl) chn_dmaupdate(rdch); + a->bytes = b->total; + a->blocks = (b->total - b->prev_total) / rdch->blocksize; + a->ptr = b->fp; + b->prev_total += a->blocks * rdch->blocksize; + } else ret = EINVAL; + } + break; + + case SNDCTL_DSP_GETOPTR: + { + count_info *a = (count_info *)arg; + if (wrch) { + snd_dbuf *b = &wrch->buffer; + if (b->dl) chn_dmaupdate(wrch); + a->bytes = b->total; + a->blocks = (b->total - b->prev_total) / wrch->blocksize; + a->ptr = b->rp; + b->prev_total += a->blocks * wrch->blocksize; + } else ret = EINVAL; + } + break; + + case SNDCTL_DSP_GETCAPS: + *arg_i = DSP_CAP_REALTIME | DSP_CAP_MMAP | DSP_CAP_TRIGGER; + if (rdch && wrch && !(d->flags & SD_F_SIMPLEX)) + *arg_i |= DSP_CAP_DUPLEX; + break; + + case SOUND_PCM_READ_BITS: + *arg_i = ((wrch? wrch->format : rdch->format) & AFMT_16BIT)? 16 : 8; + break; + + case SNDCTL_DSP_SETTRIGGER: + if (rdch) { + rdch->flags &= ~CHN_F_TRIGGERED; + if (*arg_i & PCM_ENABLE_INPUT) + rdch->flags |= CHN_F_TRIGGERED; + chn_intr(rdch); + } + if (wrch) { + wrch->flags &= ~CHN_F_TRIGGERED; + if (*arg_i & PCM_ENABLE_OUTPUT) + wrch->flags |= CHN_F_TRIGGERED; + chn_intr(wrch); + } + break; + + case SNDCTL_DSP_GETTRIGGER: + *arg_i = 0; + if (wrch && wrch->flags & CHN_F_TRIGGERED) + *arg_i |= PCM_ENABLE_OUTPUT; + if (rdch && rdch->flags & CHN_F_TRIGGERED) + *arg_i |= PCM_ENABLE_INPUT; + break; + + case SNDCTL_DSP_MAPINBUF: + case SNDCTL_DSP_MAPOUTBUF: + case SNDCTL_DSP_SETSYNCRO: + /* undocumented */ + + case SNDCTL_DSP_POST: + case SOUND_PCM_WRITE_FILTER: + case SOUND_PCM_READ_FILTER: + /* dunno what these do, don't sound important */ + default: + DEB(printf("default ioctl snd%d fn 0x%08x fail\n", unit, cmd)); + ret = EINVAL; + break; + } + splx(s); + return ret; +} + +int +dsp_poll(snddev_info *d, int chan, int events, struct proc *p) +{ + int ret = 0, e; + pcm_channel *wrch = NULL, *rdch = NULL; + + getchns(d, chan, &rdch, &wrch); + e = events & (POLLOUT | POLLWRNORM); + if (wrch && e) ret |= chn_poll(wrch, e, p); + e = events & (POLLIN | POLLRDNORM); + if (rdch && e) ret |= chn_poll(rdch, e, p); + return ret; +} + +int +dsp_mmap(snddev_info *d, int chan, vm_offset_t offset, int nprot) +{ + pcm_channel *wrch = NULL, *rdch = NULL, *c = NULL; + + getchns(d, chan, &rdch, &wrch); + /* XXX this is broken by line 204 of vm/device_pager.c, so force write buffer */ + if (1 || (wrch && (nprot & PROT_WRITE))) c = wrch; + else if (rdch && (nprot & PROT_READ)) c = rdch; + if (c) { + c->flags |= CHN_F_MAPPED; + return atop(vtophys(c->buffer.buf + offset)); + } + return -1; +} + diff --git a/sys/dev/sound/pcm/dsp.h b/sys/dev/sound/pcm/dsp.h new file mode 100644 index 0000000..27b7e3d --- /dev/null +++ b/sys/dev/sound/pcm/dsp.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * All rights reserved. + * + * 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. + * + * $Id$ + */ + +int dsp_open(snddev_info *d, int chan, int oflags, int devtype); +int dsp_close(snddev_info *d, int chan, int devtype); +int dsp_read(snddev_info *d, int chan, struct uio *buf, int flag); +int dsp_write(snddev_info *d, int chan, struct uio *buf, int flag); +int dsp_ioctl(snddev_info *d, int chan, u_long cmd, caddr_t arg); +int dsp_poll(snddev_info *d, int chan, int events, struct proc *p); +int dsp_mmap(snddev_info *d, int chan, vm_offset_t offset, int nprot); + + diff --git a/sys/dev/sound/pcm/fake.c b/sys/dev/sound/pcm/fake.c new file mode 100644 index 0000000..d050eef --- /dev/null +++ b/sys/dev/sound/pcm/fake.c @@ -0,0 +1,115 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * All rights reserved. + * + * 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. + * + * $Id$ + */ + +#include "pcm.h" + +#include <dev/pcm/sound.h> + +/* channel interface */ +static void *fkchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); +static int fkchan_setdir(void *data, int dir); +static int fkchan_setformat(void *data, u_int32_t format); +static int fkchan_setspeed(void *data, u_int32_t speed); +static int fkchan_setblocksize(void *data, u_int32_t blocksize); +static int fkchan_trigger(void *data, int go); +static int fkchan_getptr(void *data); +static pcmchan_caps *fkchan_getcaps(void *data); + +static pcmchan_caps fk_caps = { + 4000, 48000, + AFMT_STEREO | AFMT_U8 | AFMT_S8 | AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE +}; + +static pcm_channel fk_chantemplate = { + fkchan_init, + fkchan_setdir, + fkchan_setformat, + fkchan_setspeed, + fkchan_setblocksize, + fkchan_trigger, + fkchan_getptr, + fkchan_getcaps, +}; + +/* channel interface */ +static void * +fkchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) +{ + b->bufsize = 16384; + b->buf = malloc(b->bufsize, M_DEVBUF, M_NOWAIT); + return (void *)0xbabef00d; +} + +static int +fkchan_setdir(void *data, int dir) +{ + return 0; +} + +static int +fkchan_setformat(void *data, u_int32_t format) +{ + return 0; +} + +static int +fkchan_setspeed(void *data, u_int32_t speed) +{ + return speed; +} + +static int +fkchan_setblocksize(void *data, u_int32_t blocksize) +{ + return blocksize; +} + +static int +fkchan_trigger(void *data, int go) +{ + return 0; +} + +static int +fkchan_getptr(void *data) +{ + return 0; +} + +static pcmchan_caps * +fkchan_getcaps(void *data) +{ + return &fk_caps; +} + +int +fkchan_setup(pcm_channel *c) +{ + *c = fk_chantemplate; + return 0; +} diff --git a/sys/dev/sound/pcm/feeder.c b/sys/dev/sound/pcm/feeder.c new file mode 100644 index 0000000..3e51d74 --- /dev/null +++ b/sys/dev/sound/pcm/feeder.c @@ -0,0 +1,403 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * All rights reserved. + * + * 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. + * + * $Id$ + */ + +#include <dev/pcm/sound.h> + +static int chn_addfeeder(pcm_channel *c, pcm_feeder *f); +static int chn_removefeeder(pcm_channel *c); + +#define FEEDBUFSZ 8192 + +static unsigned char ulaw_to_u8[] = { + 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, +}; + +static unsigned char u8_to_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, +}; + +/*****************************************************************************/ + +static int +feed_root(pcm_feeder *feeder, u_int8_t *buffer, u_int32_t count, struct uio *stream) +{ + int ret, tmp; + if (!count) panic("feed_root: count == 0"); + tmp = stream->uio_resid; + ret = uiomove(buffer, count, stream); + if (ret) panic("feed_root: uiomove failed"); + tmp -= stream->uio_resid; + if (!tmp) panic("feed_root: uiomove didn't"); + return tmp; +} +pcm_feeder feeder_root = { "root", NULL, NULL, feed_root }; + +/*****************************************************************************/ + +static int +feed_8to16(pcm_feeder *f, u_int8_t *b, u_int32_t count, struct uio *stream) +{ + int i, j, k; + k = f->source->feed(f->source, b, count / 2, stream); + j = k - 1; + i = j * 2 + 1; + while (i > 0 && j >= 0) { + b[i--] = b[j--]; + b[i--] = 0; + } + return k * 2; +} +static pcm_feeder feeder_8to16 = { "8to16", NULL, NULL, feed_8to16 }; + +/*****************************************************************************/ + +static int +feed_16to8_init(pcm_feeder *f) +{ + f->data = malloc(FEEDBUFSZ, M_DEVBUF, M_NOWAIT); + return (f->data == NULL); +} + +static int +feed_16to8_free(pcm_feeder *f) +{ + if (f->data) free(f->data, M_DEVBUF); + f->data = NULL; + return 0; +} + +static int +feed_16to8le(pcm_feeder *f, u_int8_t *b, u_int32_t count, struct uio *stream) +{ + u_int32_t i = 0, toget = count * 2; + int j = 1, k; + k = f->source->feed(f->source, f->data, min(toget, FEEDBUFSZ), stream); + while (j < k) { + b[i++] = ((u_int8_t *)f->data)[j]; + j += 2; + } + return i; +} +static pcm_feeder feeder_16to8le = + { "16to8le", feed_16to8_init, feed_16to8_free, feed_16to8le }; + +/*****************************************************************************/ + +static int +feed_monotostereo8(pcm_feeder *f, u_int8_t *b, u_int32_t count, struct uio *stream) +{ + int i, j, k = f->source->feed(f->source, b, count / 2, stream); + j = k - 1; + i = j * 2 + 1; + while (i > 0 && j >= 0) { + b[i--] = b[j]; + b[i--] = b[j]; + j--; + } + return k * 2; +} +static pcm_feeder feeder_monotostereo8 = + { "monotostereo8", NULL, NULL, feed_monotostereo8 }; + +/*****************************************************************************/ + +static int +feed_stereotomono8_init(pcm_feeder *f) +{ + f->data = malloc(FEEDBUFSZ, M_DEVBUF, M_NOWAIT); + return (f->data == NULL); +} + +static int +feed_stereotomono8_free(pcm_feeder *f) +{ + if (f->data) free(f->data, M_DEVBUF); + f->data = NULL; + return 0; +} + +static int +feed_stereotomono8(pcm_feeder *f, u_int8_t *b, u_int32_t count, struct uio *stream) +{ + u_int32_t i = 0, toget = count * 2; + int j = 0, k; + k = f->source->feed(f->source, f->data, min(toget, FEEDBUFSZ), stream); + while (j < k) { + b[i++] = ((u_int8_t *)f->data)[j]; + j += 2; + } + return i; +} +static pcm_feeder feeder_stereotomono8 = + { "stereotomono8", feed_stereotomono8_init, feed_stereotomono8_free, + feed_stereotomono8 }; + +/*****************************************************************************/ + +static int +feed_endian(pcm_feeder *f, u_int8_t *b, u_int32_t count, struct uio *stream) +{ + u_int8_t t; + int i = 0, j = f->source->feed(f->source, b, count, stream); + while (i < j) { + t = b[i]; + b[i] = b[i + 1]; + b[i + 1] = t; + i += 2; + } + return count; +} +static pcm_feeder feeder_endian = { "endian", NULL, NULL, feed_endian }; + +/*****************************************************************************/ + +static int +feed_sign(pcm_feeder *f, u_int8_t *b, u_int32_t count, struct uio *stream) +{ + int i = 0, j = f->source->feed(f->source, b, count, stream); + int ssz = (int)f->data, ofs = ssz - 1; + while (i < j) { + b[i + ofs] ^= 0x80; + i += ssz; + } + return i; +} +static pcm_feeder feeder_sign8 = + { "sign8", NULL, NULL, feed_sign, (void *)1 }; +static pcm_feeder feeder_sign16 = + { "sign16", NULL, NULL, feed_sign, (void *)2 }; + +/*****************************************************************************/ + +static int +feed_table(pcm_feeder *f, u_int8_t *b, u_int32_t count, struct uio *stream) +{ + int i = 0, j = f->source->feed(f->source, b, count, stream); + while (i < j) { + b[i] = ((u_int8_t *)f->data)[b[i]]; + i++; + } + return i; +} +static pcm_feeder feeder_ulawtou8 = + { "ulawtou8", NULL, NULL, feed_table, ulaw_to_u8 }; +static pcm_feeder feeder_u8toulaw = + { "u8toulaw", NULL, NULL, feed_table, u8_to_ulaw }; + +/*****************************************************************************/ + +struct fmtspec { + int stereo; + int sign; + int bit16; + int bigendian; + int ulaw; + int bad; +}; + +struct fmtcvt { + pcm_feeder *f; + struct fmtspec ispec, ospec; +}; + +struct fmtcvt cvttab[] = { + {&feeder_ulawtou8, {-1, 0, 0, 0, 1}, {-1, 0, 0, 0, 0}}, + {&feeder_u8toulaw, {-1, 0, 0, 0, 0}, {-1, 0, 0, 0, 1}}, + {&feeder_sign8, {-1, 0, 0, 0, 0}, {-1, 1, 0, 0, 0}}, + {&feeder_sign8, {-1, 1, 0, 0, 0}, {-1, 0, 0, 0, 0}}, + {&feeder_monotostereo8, { 0, -1, 0, 0, -1}, { 1, -1, 0, 0, -1}}, + {&feeder_stereotomono8, { 1, -1, 0, 0, -1}, { 0, -1, 0, 0, -1}}, + {&feeder_sign16, {-1, 0, 1, 0, 0}, {-1, 1, 1, 0, 0}}, + {&feeder_sign16, {-1, 1, 1, 0, 0}, {-1, 0, 1, 0, 0}}, + {&feeder_8to16, {-1, -1, 0, 0, 0}, {-1, -1, 1, 0, 0}}, + {&feeder_16to8le, {-1, -1, 1, 0, 0}, {-1, -1, 0, 0, 0}}, + {&feeder_endian, {-1, -1, 1, 0, 0}, {-1, -1, 1, 1, 0}}, + {&feeder_endian, {-1, -1, 1, 1, 0}, {-1, -1, 1, 0, 0}}, +}; +#define FEEDERTABSZ (sizeof(cvttab) / sizeof(struct fmtcvt)) + +static int +getspec(u_int32_t fmt, struct fmtspec *spec) +{ + spec->stereo = (fmt & AFMT_STEREO)? 1 : 0; + spec->sign = (fmt & AFMT_SIGNED)? 1 : 0; + spec->bit16 = (fmt & AFMT_16BIT)? 1 : 0; + spec->bigendian = (fmt & AFMT_BIGENDIAN)? 1 : 0; + spec->ulaw = (fmt & AFMT_MU_LAW)? 1 : 0; + spec->bad = (fmt & (AFMT_A_LAW | AFMT_MPEG))? 1 : 0; + return 0; +} + +static int +cmp(int x, int y) +{ + return (x == -1 || x == y || y == -1)? 1 : 0; +} + +static int +cmpspec(struct fmtspec *x, struct fmtspec *y) +{ + int i = 0; + if (cmp(x->stereo, y->stereo)) i |= 0x01; + if (cmp(x->sign, y->sign)) i |= 0x02; + if (cmp(x->bit16, y->bit16)) i |= 0x04; + if (cmp(x->bigendian, y->bigendian)) i |= 0x08; + if (cmp(x->ulaw, y->ulaw)) i |= 0x10; + return i; +} + +static int +cvtapply(pcm_channel *c, struct fmtcvt *cvt, struct fmtspec *s) +{ + int i = cmpspec(s, &cvt->ospec); + chn_addfeeder(c, cvt->f); + if (cvt->ospec.stereo != -1) s->stereo = cvt->ospec.stereo; + if (cvt->ospec.sign != -1) s->sign = cvt->ospec.sign; + if (cvt->ospec.bit16 != -1) s->bit16 = cvt->ospec.bit16; + if (cvt->ospec.bigendian != -1) s->bigendian = cvt->ospec.bigendian; + if (cvt->ospec.ulaw != -1) s->ulaw = cvt->ospec.ulaw; + return i; +} + +int +chn_feedchain(pcm_channel *c) +{ + int i, chosen, iter; + u_int32_t mask; + struct fmtspec s, t; + struct fmtcvt *e; + + while (chn_removefeeder(c) != -1); + if ((c->format & chn_getcaps(c)->formats) == c->format) + return c->format; + getspec(c->format, &s); + if (s.bad) return -1; + getspec(chn_getcaps(c)->bestfmt, &t); + mask = (~cmpspec(&s, &t)) & 0x1f; + iter = 0; + do { + if (mask == 0 || iter >= 8) break; + chosen = -1; + for (i = 0; i < FEEDERTABSZ && chosen == -1; i++) { + e = &cvttab[i]; + if ((cmpspec(&s, &e->ispec) == 0x1f) && + ((~cmpspec(&e->ispec, &e->ospec)) & mask)) + chosen = i; + } + if (chosen != -1) mask &= cvtapply(c, &cvttab[chosen], &s); + iter++; + } while (chosen != -1); + return (iter < 8)? chn_getcaps(c)->bestfmt : -1; +} + +static int +chn_addfeeder(pcm_channel *c, pcm_feeder *f) +{ + pcm_feeder *n; + n = malloc(sizeof(pcm_feeder), M_DEVBUF, M_NOWAIT); + *n = *f; + n->source = c->feeder; + c->feeder = n; + if (n->init) n->init(n); + return 0; +} + +static int +chn_removefeeder(pcm_channel *c) +{ + pcm_feeder *f; + if (c->feeder == &feeder_root) return -1; + f = c->feeder->source; + if (c->feeder->free) c->feeder->free(c->feeder); + free(c->feeder, M_DEVBUF); + c->feeder = f; + return 0; +} + diff --git a/sys/dev/sound/pcm/mixer.c b/sys/dev/sound/pcm/mixer.c new file mode 100644 index 0000000..0a7c918 --- /dev/null +++ b/sys/dev/sound/pcm/mixer.c @@ -0,0 +1,202 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * All rights reserved. + * + * 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. + * + * $Id$ + */ + +#include <dev/pcm/sound.h> + +static u_int16_t snd_mixerdefaults[SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_VOLUME] = 75, + [SOUND_MIXER_BASS] = 50, + [SOUND_MIXER_TREBLE] = 50, + [SOUND_MIXER_PCM] = 75, + [SOUND_MIXER_SPEAKER] = 75, + [SOUND_MIXER_LINE] = 75, + [SOUND_MIXER_MIC] = 0, + [SOUND_MIXER_CD] = 75, + [SOUND_MIXER_LINE1] = 75, + [SOUND_MIXER_VIDEO] = 75, + [SOUND_MIXER_RECLEV] = 0, +}; + +int +mixer_init(snddev_info *d, snd_mixer *m, void *devinfo) +{ + if (d == NULL) return -1; + d->mixer = *m; + d->mixer.devinfo = devinfo; + bzero(&d->mixer.level, sizeof d->mixer.level); + if (d->mixer.init != NULL && d->mixer.init(&d->mixer) == 0) { + int i; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + u_int16_t v = snd_mixerdefaults[i]; + mixer_set(d, i, v | (v << 8)); + } + mixer_setrecsrc(d, SOUND_MASK_MIC); + return 0; + } else return -1; +} + +int +mixer_set(snddev_info *d, unsigned dev, unsigned lev) +{ + if (d == NULL || d->mixer.set == NULL) return -1; + if ((dev < SOUND_MIXER_NRDEVICES) && (d->mixer.devs & (1 << dev))) { + unsigned l = min((lev & 0x00ff), 100); + unsigned r = min(((lev & 0xff00) >> 8), 100); + int v = d->mixer.set(&d->mixer, dev, l, r); + if (v >= 0) d->mixer.level[dev] = v; + return 0; + } else return -1; +} + +int +mixer_get(snddev_info *d, int dev) +{ + if (d == NULL) return -1; + if (dev < SOUND_MIXER_NRDEVICES && (d->mixer.devs & (1 << dev))) + return d->mixer.level[dev]; + else return -1; +} + +int +mixer_setrecsrc(snddev_info *d, u_int32_t src) +{ + if (d == NULL || d->mixer.setrecsrc == NULL) return -1; + src &= d->mixer.recdevs; + if (src == 0) src = SOUND_MASK_MIC; + d->mixer.recsrc = d->mixer.setrecsrc(&d->mixer, src); + return 0; +} + +int +mixer_getrecsrc(snddev_info *d) +{ + if (d == NULL) return -1; + return d->mixer.recsrc; +} + +int +mixer_ioctl(snddev_info *d, u_long cmd, caddr_t arg) +{ + int ret, *arg_i = (int *)arg; + + if ((cmd & MIXER_WRITE(0)) == MIXER_WRITE(0)) { + int j = cmd & 0xff; + + if (j == SOUND_MIXER_RECSRC) ret = mixer_setrecsrc(d, *arg_i); + else ret = mixer_set(d, j, *arg_i); + return (ret == 0)? 0 : ENXIO; + } + + if ((cmd & MIXER_READ(0)) == MIXER_READ(0)) { + int v = -1, j = cmd & 0xff; + + switch (j) { + case SOUND_MIXER_DEVMASK: + case SOUND_MIXER_CAPS: + case SOUND_MIXER_STEREODEVS: + v = d->mixer.devs; + break; + + case SOUND_MIXER_RECMASK: + v = d->mixer.recdevs; + break; + + case SOUND_MIXER_RECSRC: + v = mixer_getrecsrc(d); + break; + + default: + v = mixer_get(d, j); + } + *arg_i = v; + return (v != -1)? 0 : ENXIO; + } + return ENXIO; +} + +void +mix_setdevs(snd_mixer *m, u_int32_t v) +{ + m->devs = v; +} + +void +mix_setrecdevs(snd_mixer *m, u_int32_t v) +{ + m->recdevs = v; +} + +u_int32_t +mix_getdevs(snd_mixer *m) +{ + return m->devs; +} + +u_int32_t +mix_getrecdevs(snd_mixer *m) +{ + return m->recdevs; +} + +void * +mix_getdevinfo(snd_mixer *m) +{ + return m->devinfo; +} + +/* + * 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 */ +} + diff --git a/sys/dev/sound/pcm/mixer.h b/sys/dev/sound/pcm/mixer.h new file mode 100644 index 0000000..82a8f26 --- /dev/null +++ b/sys/dev/sound/pcm/mixer.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * All rights reserved. + * + * 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. + * + * $Id$ + */ + +extern int mixer_init(snddev_info *d, snd_mixer *m, void *devinfo); +extern int mixer_set(snddev_info *d, unsigned dev, unsigned lev); +extern int mixer_get(snddev_info *d, int dev); +extern int mixer_setrecsrc(snddev_info *d, u_int32_t src); +extern int mixer_getrecsrc(snddev_info *d); +extern int mixer_ioctl(snddev_info *d, u_long cmd, caddr_t arg); + +extern void change_bits(mixer_tab *t, u_char *regval, int dev, int chn, int newval); + +void mix_setdevs(snd_mixer *m, u_int32_t v); +void mix_setrecdevs(snd_mixer *m, u_int32_t v); +u_int32_t mix_getdevs(snd_mixer *m); +u_int32_t mix_getrecdevs(snd_mixer *m); +void *mix_getdevinfo(snd_mixer *m); diff --git a/sys/dev/sound/pcm/sound.c b/sys/dev/sound/pcm/sound.c new file mode 100644 index 0000000..102b87a --- /dev/null +++ b/sys/dev/sound/pcm/sound.c @@ -0,0 +1,438 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it) + * All rights reserved. + * + * 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. + * + * $Id$ + */ + +#include "opt_devfs.h" + +#include <dev/pcm/sound.h> +#ifdef DEVFS +#include <sys/devfsext.h> +#endif /* DEVFS */ + +#if NPCM > 0 /* from "pcm.h" via disgusting #include in snd/sound.h */ + +extern struct isa_driver pcmdriver; + +static int status_isopen = 0; +static int status_init(char *buf, int size); +static int status_read(struct uio *buf); + +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; +static d_poll_t sndpoll; + +#define CDEV_MAJOR 30 +static struct cdevsw snd_cdevsw = { + /* open */ sndopen, + /* close */ sndclose, + /* read */ sndread, + /* write */ sndwrite, + /* ioctl */ sndioctl, + /* stop */ nostop, + /* reset */ noreset, + /* devtotty */ nodevtotty, + /* poll */ sndpoll, + /* mmap */ sndmmap, + /* strategy */ nostrategy, + /* name */ "snd", + /* parms */ noparms, + /* maj */ CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, + /* maxio */ 0, + /* bmaj */ -1 +}; + +/* PROPOSAL: +each unit needs: +status, mixer, dsp, dspW, audio, sequencer, midi-in, seq2, sndproc = 9 devices +dspW and audio are deprecated. +dsp needs min 64 channels, will give it 256 + +minor = (unit << 12) + (dev << 8) + channel +currently minor = (channel << 8) + (unit << 4) + dev + +nomenclature: + /dev/pcmX/dsp.(0..255) + /dev/pcmX/dspW + /dev/pcmX/audio + /dev/pcmX/status + /dev/pcmX/mixer + [etc.] +*/ + +#define PCMMINOR(x) (minor(x)) +#define PCMCHAN(x) ((PCMMINOR(x) & 0x0000ff00) >> 8) +#define PCMUNIT(x) ((PCMMINOR(x) & 0x000000f0) >> 4) +#define PCMDEV(x) (PCMMINOR(x) & 0x0000000f) + +static devclass_t pcm_devclass; + +static snddev_info * +gsd(int unit) +{ + return devclass_get_softc(pcm_devclass, unit); +} + +int +pcm_addchan(device_t dev, int dir, pcm_channel *templ, void *devinfo) +{ + snddev_info *d = device_get_softc(dev); + pcm_channel *ch; + + ch = (dir == PCMDIR_PLAY)? &d->play[d->playcount++] : &d->rec[d->reccount++]; + *ch = *templ; + chn_init(ch, devinfo, dir); + d->chancount++; + return 0; +} + +int +pcm_setstatus(device_t dev, char *str) +{ + snddev_info *d = device_get_softc(dev); + strncpy(d->status, str, SND_STATUSLEN); + return 0; +} + +u_int32_t +pcm_getflags(device_t dev) +{ + snddev_info *d = device_get_softc(dev); + return d->flags; +} + +void +pcm_setflags(device_t dev, u_int32_t val) +{ + snddev_info *d = device_get_softc(dev); + d->flags = val; +} + +/* This is the generic init routine */ +int +pcm_register(device_t dev, void *devinfo, int numplay, int numrec) +{ + int sz, unit = device_get_unit(dev); + snddev_info *d = device_get_softc(dev); + + if (!pcm_devclass) { + pcm_devclass = device_get_devclass(dev); + cdevsw_add(&snd_cdevsw); + } + d->devinfo = devinfo; + d->chancount = d->playcount = d->reccount = 0; + sz = (numplay + numrec) * sizeof(pcm_channel *); + d->aplay = (pcm_channel **)malloc(sz, M_DEVBUF, M_NOWAIT); + if (!d->aplay) goto no; + d->arec = (pcm_channel **)malloc(sz, M_DEVBUF, M_NOWAIT); + if (!d->arec) goto no; + bzero(d->aplay, sz); + bzero(d->arec, sz); + + d->play = (pcm_channel *)malloc(numplay * sizeof(pcm_channel), + M_DEVBUF, M_NOWAIT); + if (!d->play) goto no; + d->rec = (pcm_channel *)malloc(numrec * sizeof(pcm_channel), + M_DEVBUF, M_NOWAIT); + if (!d->rec) goto no; + bzero(d->play, numplay * sizeof(pcm_channel)); + bzero(d->rec, numrec * sizeof(pcm_channel)); + + fkchan_setup(&d->fakechan); + chn_init(&d->fakechan, NULL, 0); + d->magic = MAGIC(unit); /* debugging... */ + + return 0; +no: + if (d->aplay) free(d->aplay, M_DEVBUF); + if (d->play) free(d->play, M_DEVBUF); + if (d->arec) free(d->arec, M_DEVBUF); + if (d->rec) free(d->rec, M_DEVBUF); + return ENXIO; +} + +/* + * 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 i_dev, int *unit, int *dev, int *chan) +{ + int u, d, c; + + u = PCMUNIT(i_dev); + d = PCMDEV(i_dev); + c = PCMCHAN(i_dev); + if (u > devclass_get_maxunit(pcm_devclass)) u = -1; + if (unit) *unit = u; + if (dev) *dev = d; + if (chan) *chan = c; + if (u < 0) return NULL; + + switch(d) { + case SND_DEV_CTL: /* /dev/mixer handled by pcm */ + case SND_DEV_STATUS: /* /dev/sndstat handled by pcm */ + case SND_DEV_DSP: + case SND_DEV_DSP16: + case SND_DEV_AUDIO: + return gsd(u); + + case SND_DEV_SEQ: /* XXX when enabled... */ + case SND_DEV_SEQ2: + case SND_DEV_MIDIN: + case SND_DEV_SNDPROC: /* /dev/sndproc handled by pcm */ + default: + printf("unsupported subdevice %d\n", d); + return NULL; + } +} + +static int +sndopen(dev_t i_dev, int flags, int mode, struct proc *p) +{ + int dev, unit, chan; + snddev_info *d = get_snddev_info(i_dev, &unit, &dev, &chan); + + DEB(printf("open snd%d subdev %d flags 0x%08x mode 0x%08x\n", + unit, dev, flags, mode)); + + switch(dev) { + case SND_DEV_STATUS: + if (status_isopen) return EBUSY; + status_isopen = 1; + return 0; + + case SND_DEV_CTL: + return d? 0 : ENXIO; + + case SND_DEV_AUDIO: + case SND_DEV_DSP: + case SND_DEV_DSP16: + return d? dsp_open(d, chan, flags, dev) : ENXIO; + + default: + return ENXIO; + } +} + +static int +sndclose(dev_t i_dev, int flags, int mode, struct proc *p) +{ + int dev, unit, chan; + snddev_info *d = get_snddev_info(i_dev, &unit, &dev, &chan); + + DEB(printf("close snd%d subdev %d\n", unit, dev)); + + switch(dev) { /* only those for which close makes sense */ + case SND_DEV_STATUS: + if (!status_isopen) return EBADF; + status_isopen = 0; + return 0; + + case SND_DEV_CTL: + return d? 0 : ENXIO; + + case SND_DEV_AUDIO: + case SND_DEV_DSP: + case SND_DEV_DSP16: + return d? dsp_close(d, chan, dev) : ENXIO; + + default: + return ENXIO; + } +} + +static int +sndread(dev_t i_dev, struct uio *buf, int flag) +{ + int dev, unit, chan; + snddev_info *d = get_snddev_info(i_dev, &unit, &dev, &chan); + DEB(printf("read snd%d subdev %d flag 0x%08x\n", unit, dev, flag)); + + switch(dev) { + case SND_DEV_STATUS: + return status_isopen? status_read(buf) : EBADF; + + case SND_DEV_AUDIO: + case SND_DEV_DSP: + case SND_DEV_DSP16: + return d? dsp_read(d, chan, buf, flag) : EBADF; + + default: + return ENXIO; + } +} + +static int +sndwrite(dev_t i_dev, struct uio *buf, int flag) +{ + int dev, unit, chan; + snddev_info *d = get_snddev_info(i_dev, &unit, &dev, &chan); + + DEB(printf("write snd%d subdev %d flag 0x%08x\n", unit, dev & 0xf, flag)); + + switch(dev) { /* only writeable devices */ + case SND_DEV_DSP: + case SND_DEV_DSP16: + case SND_DEV_AUDIO: + return d? dsp_write(d, chan, buf, flag) : EBADF; + + default: + return EPERM; /* for non-writeable devices ; */ + } +} + +static int +sndioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct proc * p) +{ + int dev, chan; + snddev_info *d = get_snddev_info(i_dev, NULL, &dev, &chan); + + if (d == NULL) return ENXIO; + + switch(dev) { + case SND_DEV_CTL: + return mixer_ioctl(d, cmd, arg); + + case SND_DEV_AUDIO: + case SND_DEV_DSP: + case SND_DEV_DSP16: + return dsp_ioctl(d, chan, cmd, arg); + + default: + return ENXIO; + } +} + +static int +sndpoll(dev_t i_dev, int events, struct proc *p) +{ + int dev, chan; + snddev_info *d = get_snddev_info(i_dev, NULL, &dev, &chan); + + DEB(printf("sndpoll dev 0x%04x events 0x%08x\n", i_dev, events)); + + if (d == NULL) return ENXIO; + + switch(dev) { + case SND_DEV_AUDIO: + case SND_DEV_DSP: + case SND_DEV_DSP16: + return dsp_poll(d, chan, events, p); + + default: + return (events & + (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM)) | POLLHUP; + } +} + +/* + * 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. + */ +static int +sndmmap(dev_t i_dev, vm_offset_t offset, int nprot) +{ + int unit, dev, chan; + snddev_info *d = get_snddev_info(i_dev, &unit, &dev, &chan); + + DEB(printf("sndmmap d 0x%p dev 0x%04x ofs 0x%08x nprot 0x%08x\n", + d, dev, offset, nprot)); + + if (d == NULL || nprot & PROT_EXEC) return -1; /* forbidden */ + + switch(dev) { + case SND_DEV_AUDIO: + case SND_DEV_DSP: + case SND_DEV_DSP16: + return dsp_mmap(d, chan, offset, nprot); + + default: + return -1; + } +} + +static int +status_init(char *buf, int size) +{ + int i; + device_t dev; + snddev_info *d; + + snprintf(buf, size, "FreeBSD Audio Driver (newpcm) %s %s\n" + "Installed devices:\n", __DATE__, __TIME__); + + for (i = 0; i <= devclass_get_maxunit(pcm_devclass); i++) { + d = gsd(i); + if (!d) continue; + dev = devclass_get_device(pcm_devclass, i); + if (1) snprintf(buf + strlen(buf), size - strlen(buf), + "pcm%d: <%s> %s (%d/%d channels%s)\n", + i, device_get_desc(dev), d->status, + d->playcount, d->reccount, + (!(d->flags & SD_F_SIMPLEX))? " duplex" : ""); + } + return strlen(buf); +} + +static int +status_read(struct uio *buf) +{ + static char status_buf[4096]; + static int bufptr = 0, buflen = 0; + int l; + + if (status_isopen == 1) { + status_isopen++; + bufptr = 0; + buflen = status_init(status_buf, sizeof status_buf); + } + + l = min(buf->uio_resid, buflen - bufptr); + bufptr += l; + return (l > 0)? uiomove(status_buf + bufptr - l, l, buf) : 0; +} + +#endif /* NPCM > 0 */ diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h new file mode 100644 index 0000000..5d17624 --- /dev/null +++ b/sys/dev/sound/pcm/sound.h @@ -0,0 +1,181 @@ +/* + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * Copyright by Hannu Savolainen 1995 + * All rights reserved. + * + * 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. + * + * $Id$ + */ + +#ifdef KERNEL +#include "pcm.h" +#else +#error why? +#define NPCM 1 +#endif +#if NPCM > 0 + +/* + * first, include kernel header files. + */ + +#ifndef _OS_H_ +#define _OS_H_ + +#ifdef KERNEL +#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/module.h> +#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/bus.h> +#include <sys/buf.h> +#include <machine/clock.h> /* for DELAY */ +#include <machine/resource.h> +#include <machine/bus_memio.h> +#include <machine/bus_pio.h> +#include <machine/bus.h> +#include <sys/rman.h> +#include <sys/mman.h> +#include <sys/poll.h> +#include <sys/soundcard.h> +#include <isa/isavar.h> +#include <vm/vm.h> +#include <vm/pmap.h> + +#else +struct isa_device { int dummy; }; +#define d_open_t void +#define d_close_t void +#define d_read_t void +#define d_write_t void +#define d_ioctl_t void +#define d_select_t void +#endif /* KERNEL */ + +#endif /* _OS_H_ */ + +#include <dev/pcm/datatypes.h> +#include <dev/pcm/channel.h> +#include <dev/pcm/mixer.h> +#include <dev/pcm/dsp.h> + +#define MAGIC(unit) (0xa4d10de0 + unit) + +#define SD_F_SIMPLEX 0x00000001 +#define SD_F_EVILSB16 0x00000002 +#define SD_F_PRIO_RD 0x10000000 +#define SD_F_PRIO_WR 0x20000000 +#define SD_F_PRIO_SET (SD_F_PRIO_RD | SD_F_PRIO_WR) +#define SD_F_DIR_SET 0x40000000 +#define SD_F_TRANSIENT 0xf0000000 + +/* 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)) + +#define DSP_BUFFSIZE (65536 - 256) /* XXX */ +/* the last 256 bytes are room for buggy soundcard to overflow. */ + +/* make figuring out what a format is easier. got AFMT_STEREO already */ +#define AFMT_16BIT (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE) +#define AFMT_SIGNED (AFMT_S16_LE | AFMT_S16_BE | AFMT_S8) +#define AFMT_BIGENDIAN (AFMT_S16_BE | AFMT_U16_BE) + +int fkchan_setup(pcm_channel *c); + +#ifdef KERNEL +#include "pnp.h" +#endif /* KERNEL */ + +/* + * 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 /dev/sequencer */ +#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. */ +#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 + +#ifdef KERNEL + +/* + * some macros for debugging purposes + * DDB/DEB to enable/disable debugging stuff + * BVDDB to enable debugging when bootverbose + */ +#define DDB(x) x /* XXX */ +#define BVDDB(x) if (bootverbose) x + +#ifndef DEB +#define DEB(x) +#endif + +int pcm_addchan(device_t dev, int dir, pcm_channel *templ, void *devinfo); +int pcm_register(device_t dev, void *devinfo, int numplay, int numrec); +int pcm_setstatus(device_t dev, char *str); +u_int32_t pcm_getflags(device_t dev); +void pcm_setflags(device_t dev, u_int32_t val); + +#endif /* KERNEL */ + +/* 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 */ + +/* ought to be made obsolete */ +#define DV_F_DEV_MASK 0x0000ff00 /* force device type/class */ +#define DV_F_DEV_SHIFT 8 /* force device type/class */ + +#endif |