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 | |
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.
40 files changed, 11805 insertions, 0 deletions
diff --git a/sys/dev/pcm/ac97.c b/sys/dev/pcm/ac97.c new file mode 100644 index 0000000..b3da9cb --- /dev/null +++ b/sys/dev/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/pcm/ac97.h b/sys/dev/pcm/ac97.h new file mode 100644 index 0000000..c3460a2 --- /dev/null +++ b/sys/dev/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/pcm/channel.c b/sys/dev/pcm/channel.c new file mode 100644 index 0000000..dd383e5 --- /dev/null +++ b/sys/dev/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/pcm/channel.h b/sys/dev/pcm/channel.h new file mode 100644 index 0000000..8667a9f --- /dev/null +++ b/sys/dev/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/pcm/datatypes.h b/sys/dev/pcm/datatypes.h new file mode 100644 index 0000000..50928f6 --- /dev/null +++ b/sys/dev/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/pcm/dsp.c b/sys/dev/pcm/dsp.c new file mode 100644 index 0000000..a868e92 --- /dev/null +++ b/sys/dev/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/pcm/dsp.h b/sys/dev/pcm/dsp.h new file mode 100644 index 0000000..27b7e3d --- /dev/null +++ b/sys/dev/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/pcm/fake.c b/sys/dev/pcm/fake.c new file mode 100644 index 0000000..d050eef --- /dev/null +++ b/sys/dev/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/pcm/feeder.c b/sys/dev/pcm/feeder.c new file mode 100644 index 0000000..3e51d74 --- /dev/null +++ b/sys/dev/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/pcm/mixer.c b/sys/dev/pcm/mixer.c new file mode 100644 index 0000000..0a7c918 --- /dev/null +++ b/sys/dev/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/pcm/mixer.h b/sys/dev/pcm/mixer.h new file mode 100644 index 0000000..82a8f26 --- /dev/null +++ b/sys/dev/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/pcm/pci/aureal.c b/sys/dev/pcm/pci/aureal.c new file mode 100644 index 0000000..49900e6 --- /dev/null +++ b/sys/dev/pcm/pci/aureal.c @@ -0,0 +1,693 @@ +/* + * 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 "pci.h" +#include "pcm.h" + +#include <dev/pcm/sound.h> +#include <dev/pcm/ac97.h> +#include <dev/pcm/pci/aureal.h> + +#include <pci/pcireg.h> +#include <pci/pcivar.h> + +#if NPCI != 0 + +/* PCI IDs of supported chips */ +#define AU8820_PCI_ID 0x000112eb + +/* channel interface */ +static void *auchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); +static int auchan_setdir(void *data, int dir); +static int auchan_setformat(void *data, u_int32_t format); +static int auchan_setspeed(void *data, u_int32_t speed); +static int auchan_setblocksize(void *data, u_int32_t blocksize); +static int auchan_trigger(void *data, int go); +static int auchan_getptr(void *data); +static pcmchan_caps *auchan_getcaps(void *data); + +static pcmchan_caps au_playcaps = { + 4000, 48000, + AFMT_STEREO | AFMT_U8 | AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE +}; + +static pcmchan_caps au_reccaps = { + 4000, 48000, + AFMT_STEREO | AFMT_U8 | AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE +}; + +static pcm_channel au_chantemplate = { + auchan_init, + auchan_setdir, + auchan_setformat, + auchan_setspeed, + auchan_setblocksize, + auchan_trigger, + auchan_getptr, + auchan_getcaps, +}; + +/* -------------------------------------------------------------------- */ + +static u_int32_t au_rdcd(void *arg, int regno); +static void au_wrcd(void *arg, int regno, u_int32_t data); + +struct au_info; + +struct au_chinfo { + struct au_info *parent; + pcm_channel *channel; + snd_dbuf *buffer; + int dir; +}; + +struct au_info { + int unit; + + bus_space_tag_t st[3]; + bus_space_handle_t sh[3]; + + bus_dma_tag_t parent_dmat; + + u_int32_t x[32], y[128]; + char z[128]; + u_int32_t routes[4], interrupts; + struct au_chinfo pch; +}; + +static int au_init(device_t dev, struct au_info *au); +static void au_intr(void *); + +/* -------------------------------------------------------------------- */ + +static u_int32_t +au_rd(struct au_info *au, int mapno, int regno, int size) +{ + switch(size) { + case 1: + return bus_space_read_1(au->st[mapno], au->sh[mapno], regno); + case 2: + return bus_space_read_2(au->st[mapno], au->sh[mapno], regno); + case 4: + return bus_space_read_4(au->st[mapno], au->sh[mapno], regno); + default: + return 0xffffffff; + } +} + +static void +au_wr(struct au_info *au, int mapno, int regno, u_int32_t data, int size) +{ + switch(size) { + case 1: + bus_space_write_1(au->st[mapno], au->sh[mapno], regno, data); + break; + case 2: + bus_space_write_2(au->st[mapno], au->sh[mapno], regno, data); + break; + case 4: + bus_space_write_4(au->st[mapno], au->sh[mapno], regno, data); + break; + } +} + +static u_int32_t +au_rdcd(void *arg, int regno) +{ + struct au_info *au = (struct au_info *)arg; + int i=0, j=0; + + regno<<=16; + au_wr(au, 0, AU_REG_CODECIO, regno, 4); + while (j<50) { + i=au_rd(au, 0, AU_REG_CODECIO, 4); + if ((i & 0x00ff0000) == (regno | 0x00800000)) break; + DELAY(j * 200 + 2000); + j++; + } + if (j==50) printf("pcm%d: codec timeout reading register %x (%x)\n", + au->unit, (regno & AU_CDC_REGMASK)>>16, i); + return i & AU_CDC_DATAMASK; +} + +static void +au_wrcd(void *arg, int regno, u_int32_t data) +{ + struct au_info *au = (struct au_info *)arg; + int i, j, tries; + i=j=tries=0; + do { + while (j<50 && (i & AU_CDC_WROK) == 0) { + i=au_rd(au, 0, AU_REG_CODECST, 4); + DELAY(2000); + j++; + } + if (j==50) printf("codec timeout during write of register %x, data %x\n", + regno, data); + au_wr(au, 0, AU_REG_CODECIO, (regno<<16) | AU_CDC_REGSET | data, 4); +/* DELAY(20000); + i=au_rdcd(au, regno); +*/ tries++; + } while (0); /* (i != data && tries < 3); */ + /* + if (tries == 3) printf("giving up writing 0x%4x to codec reg %2x\n", data, regno); + */ +} + +static void +au_setbit(u_int32_t *p, char bit, u_int32_t value) +{ + p += bit >> 5; + bit &= 0x1f; + *p &= ~ (1 << bit); + *p |= (value << bit); +} + +static void +au_addroute(struct au_info *au, int a, int b, int route) +{ + int j = 0x1099c+(a<<2); + if (au->x[a] != a+0x67) j = AU_REG_RTBASE+(au->x[a]<<2); + + au_wr(au, 0, AU_REG_RTBASE+(route<<2), 0xffffffff, 4); + au_wr(au, 0, j, route | (b<<7), 4); + au->y[route]=au->x[a]; + au->x[a]=route; + au->z[route]=a & 0x000000ff; + au_setbit(au->routes, route, 1); +} + +static void +au_delroute(struct au_info *au, int route) +{ + int i; + int j=au->z[route]; + + au_setbit(au->routes, route, 0); + au->z[route]=0x1f; + i=au_rd(au, 0, AU_REG_RTBASE+(route<<2), 4); + au_wr(au, 0, AU_REG_RTBASE+(au->y[route]<<2), i, 4); + au->y[i & 0x7f]=au->y[route]; + au_wr(au, 0, AU_REG_RTBASE+(route<<2), 0xfffffffe, 4); + if (au->x[j] == route) au->x[j]=au->y[route]; + au->y[route]=0x7f; +} + +static void +au_encodec(struct au_info *au, char channel) +{ + au_wr(au, 0, AU_REG_CODECEN, + au_rd(au, 0, AU_REG_CODECEN, 4) | (1 << (channel + 8)), 4); +} + +static void +au_clrfifo(struct au_info *au, u_int32_t c) +{ + u_int32_t i; + + for (i=0; i<32; i++) au_wr(au, 0, AU_REG_FIFOBASE+(c<<7)+(i<<2), 0, 4); +} + +static void +au_setadb(struct au_info *au, u_int32_t c, u_int32_t enable) +{ + int x; + + x = au_rd(au, 0, AU_REG_ADB, 4); + x &= ~(1 << c); + x |= (enable << c); + au_wr(au, 0, AU_REG_ADB, x, 4); +} + +static void +au_prepareoutput(struct au_chinfo *ch, u_int32_t format) +{ + struct au_info *au = ch->parent; + int i, stereo = (format & AFMT_STEREO)? 1 : 0; + u_int32_t baseaddr = vtophys(ch->buffer->buf); + + au_wr(au, 0, 0x1061c, 0, 4); + au_wr(au, 0, 0x10620, 0, 4); + au_wr(au, 0, 0x10624, 0, 4); + switch(format & ~AFMT_STEREO) { + case 1: + i=0xb000; + break; + case 2: + i=0xf000; + break; + case 8: + i=0x7000; + break; + case 16: + i=0x23000; + break; + default: + i=0x3000; + } + au_wr(au, 0, 0x10200, baseaddr, 4); + au_wr(au, 0, 0x10204, baseaddr+0x1000, 4); + au_wr(au, 0, 0x10208, baseaddr+0x2000, 4); + au_wr(au, 0, 0x1020c, baseaddr+0x3000, 4); + + au_wr(au, 0, 0x10400, 0xdeffffff, 4); + au_wr(au, 0, 0x10404, 0xfcffffff, 4); + + au_wr(au, 0, 0x10580, i, 4); + + au_wr(au, 0, 0x10210, baseaddr, 4); + au_wr(au, 0, 0x10214, baseaddr+0x1000, 4); + au_wr(au, 0, 0x10218, baseaddr+0x2000, 4); + au_wr(au, 0, 0x1021c, baseaddr+0x3000, 4); + + au_wr(au, 0, 0x10408, 0x00fff000 | 0x56000000 | 0x00000fff, 4); + au_wr(au, 0, 0x1040c, 0x00fff000 | 0x74000000 | 0x00000fff, 4); + + au_wr(au, 0, 0x10584, i, 4); + + au_wr(au, 0, 0x0f800, stereo? 0x00030032 : 0x00030030, 4); + au_wr(au, 0, 0x0f804, stereo? 0x00030032 : 0x00030030, 4); + + au_addroute(au, 0x11, 0, 0x58); + au_addroute(au, 0x11, stereo? 0 : 1, 0x59); +} + +/* channel interface */ +static void * +auchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) +{ + struct au_info *au = devinfo; + struct au_chinfo *ch = (dir == PCMDIR_PLAY)? &au->pch : NULL; + + ch->parent = au; + ch->channel = c; + ch->buffer = b; + ch->buffer->bufsize = AU_BUFFSIZE; + if (chn_allocbuf(ch->buffer, au->parent_dmat) == -1) return NULL; + return ch; +} + +static int +auchan_setdir(void *data, int dir) +{ + struct au_chinfo *ch = data; + if (dir == PCMDIR_PLAY) { + } else { + } + ch->dir = dir; + return 0; +} + +static int +auchan_setformat(void *data, u_int32_t format) +{ + struct au_chinfo *ch = data; + + if (ch->dir == PCMDIR_PLAY) au_prepareoutput(ch, format); + return 0; +} + +static int +auchan_setspeed(void *data, u_int32_t speed) +{ + struct au_chinfo *ch = data; + if (ch->dir == PCMDIR_PLAY) { + } else { + } + return speed; +} + +static int +auchan_setblocksize(void *data, u_int32_t blocksize) +{ + return blocksize; +} + +static int +auchan_trigger(void *data, int go) +{ + struct au_chinfo *ch = data; + struct au_info *au = ch->parent; + if (ch->dir == PCMDIR_PLAY) { + au_setadb(au, 0x11, (go)? 1 : 0); + if (!go) { + au_wr(au, 0, 0xf800, 0, 4); + au_wr(au, 0, 0xf804, 0, 4); + au_delroute(au, 0x58); + au_delroute(au, 0x59); + } + } else { + } + return 0; +} + +static int +auchan_getptr(void *data) +{ + struct au_chinfo *ch = data; + struct au_info *au = ch->parent; + if (ch->dir == PCMDIR_PLAY) { + return au_rd(au, 0, AU_REG_UNK2, 4) & (AU_BUFFSIZE-1); + } else { + return 0; + } +} + +static pcmchan_caps * +auchan_getcaps(void *data) +{ + struct au_chinfo *ch = data; + return (ch->dir == PCMDIR_PLAY)? &au_playcaps : &au_reccaps; +} + +/* The interrupt handler */ +static void +au_intr (void *p) +{ + struct au_info *au = p; + u_int32_t intsrc, i; + + au->interrupts++; + intsrc=au_rd(au, 0, AU_REG_IRQSRC, 4); + printf("pcm%d: interrupt with src %x\n", au->unit, intsrc); + if (intsrc & AU_IRQ_FATAL) printf("pcm%d: fatal error irq\n", au->unit); + if (intsrc & AU_IRQ_PARITY) printf("pcm%d: parity error irq\n", au->unit); + if (intsrc & AU_IRQ_UNKNOWN) { + (void)au_rd(au, 0, AU_REG_UNK1, 4); + au_wr(au, 0, AU_REG_UNK1, 0, 4); + au_wr(au, 0, AU_REG_UNK1, 0x10000, 4); + } + if (intsrc & AU_IRQ_PCMOUT) { + i=au_rd(au, 0, AU_REG_UNK2, 4) & (AU_BUFFSIZE-1); + chn_intr(au->pch.channel); + (void)au_rd(au, 0, AU_REG_UNK3, 4); + (void)au_rd(au, 0, AU_REG_UNK4, 4); + (void)au_rd(au, 0, AU_REG_UNK5, 4); + } +/* don't support midi + if (intsrc & AU_IRQ_MIDI) { + i=au_rd(au, 0, 0x11004, 4); + j=10; + while (i & 0xff) { + if (j-- <= 0) break; + i=au_rd(au, 0, 0x11000, 4); + if ((au->midi_stat & 1) && (au->midi_out)) + au->midi_out(au->midi_devno, i); + i=au_rd(au, 0, 0x11004); + } + } +*/ + au_wr(au, 0, AU_REG_IRQSRC, intsrc & 0x7ff, 4); + au_rd(au, 0, AU_REG_IRQSRC, 4); +} + + +/* -------------------------------------------------------------------- */ + +/* Probe and attach the card */ + +static int +au_init(device_t dev, struct au_info *au) +{ + u_int32_t i, j; + + au_wr(au, 0, AU_REG_IRQGLOB, 0xffffffff, 4); + DELAY(100000); + + /* init codec */ + /* cold reset */ + for (i=0; i<32; i++) { + au_wr(au, 0, AU_REG_CODECCHN+(i<<2), 0, 4); + DELAY(10000); + } + if (1) { + au_wr(au, 0, AU_REG_CODECST, 0x8068, 4); + DELAY(10000); + au_wr(au, 0, AU_REG_CODECST, 0x00e8, 4); + DELAY(10000); + } else { + au_wr(au, 0, AU_REG_CODECST, 0x00a8, 4); + DELAY(100000); + au_wr(au, 0, AU_REG_CODECST, 0x80a8, 4); + DELAY(100000); + au_wr(au, 0, AU_REG_CODECST, 0x80e8, 4); + DELAY(100000); + au_wr(au, 0, AU_REG_CODECST, 0x80a8, 4); + DELAY(100000); + au_wr(au, 0, AU_REG_CODECST, 0x00a8, 4); + DELAY(100000); + au_wr(au, 0, AU_REG_CODECST, 0x00e8, 4); + DELAY(100000); + } + + /* init */ + for (i=0; i<32; i++) { + au_wr(au, 0, AU_REG_CODECCHN+(i<<2), 0, 4); + DELAY(10000); + } + au_wr(au, 0, AU_REG_CODECST, 0xe8, 4); + DELAY(10000); + au_wr(au, 0, AU_REG_CODECEN, 0, 4); + + /* setup codec */ + i=j=0; + while (j<100 && (i & AU_CDC_READY)==0) { + i=au_rd(au, 0, AU_REG_CODECST, 4); + DELAY(1000); + j++; + } + if (j==100) device_printf(dev, "codec not ready, status 0x%x\n", i); + + /* init adb */ + /*au->x5c=0;*/ + for (i=0; i<32; i++) au->x[i]=i+0x67; + for (i=0; i<128; i++) au->y[i]=0x7f; + for (i=0; i<128; i++) au->z[i]=0x1f; + au_wr(au, 0, AU_REG_ADB, 0, 4); + for (i=0; i<124; i++) au_wr(au, 0, AU_REG_RTBASE+(i<<2), 0xffffffff, 4); + + /* test */ + i=au_rd(au, 0, 0x107c0, 4); + if (i!=0xdeadbeef) device_printf(dev, "dma check failed: 0x%x\n", i); + + /* install mixer */ + au_wr(au, 0, AU_REG_IRQGLOB, + au_rd(au, 0, AU_REG_IRQGLOB, 4) | AU_IRQ_ENABLE, 4); + /* braindead but it's what the oss/linux driver does + * for (i=0; i<0x80000000; i++) au_wr(au, 0, i<<2, 0, 4); + */ + au->routes[0]=au->routes[1]=au->routes[2]=au->routes[3]=0; + /*au->x1e4=0;*/ + + /* attach channel */ + au_addroute(au, 0x11, 0x48, 0x02); + au_addroute(au, 0x11, 0x49, 0x03); + au_encodec(au, 0); + au_encodec(au, 1); + + for (i=0; i<48; i++) au_wr(au, 0, 0xf800+(i<<2), 0x20, 4); + for (i=2; i<6; i++) au_wr(au, 0, 0xf800+(i<<2), 0, 4); + au_wr(au, 0, 0xf8c0, 0x0843, 4); + for (i=0; i<4; i++) au_clrfifo(au, i); + + return (0); +} + +static int +au_testirq(struct au_info *au) +{ + au_wr(au, 0, AU_REG_UNK1, 0x80001000, 4); + au_wr(au, 0, AU_REG_IRQEN, 0x00001030, 4); + au_wr(au, 0, AU_REG_IRQSRC, 0x000007ff, 4); + DELAY(1000000); + if (au->interrupts==0) printf("pcm%d: irq test failed\n", au->unit); + /* this apparently generates an irq */ + return 0; +} + +static int +au_pci_probe(device_t dev) +{ + if (pci_get_devid(dev) == AU8820_PCI_ID) { + device_set_desc(dev, "Aureal Vortex 8820"); + return 0; + } + + return ENXIO; +} + +static int +au_pci_attach(device_t dev) +{ + snddev_info *d; + u_int32_t data; + struct au_info *au; + int type[10]; + int regid[10]; + struct resource *reg[10]; + int i, j, mapped = 0; + int irqid; + struct resource *irq = 0; + void *ih = 0; + struct ac97_info *codec; + char status[SND_STATUSLEN]; + + d = device_get_softc(dev); + if ((au = malloc(sizeof(*au), M_DEVBUF, M_NOWAIT)) == NULL) { + device_printf(dev, "cannot allocate softc\n"); + return ENXIO; + } + + bzero(au, sizeof(*au)); + au->unit = device_get_unit(dev); + + data = pci_read_config(dev, PCIR_COMMAND, 2); + data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN); + pci_write_config(dev, PCIR_COMMAND, data, 2); + data = pci_read_config(dev, PCIR_COMMAND, 2); + + j=0; + /* XXX dfr: is this strictly necessary? */ + for (i=0; i<PCI_MAXMAPS_0; i++) { +#if 0 + /* Slapped wrist: config_id and map are private structures */ + if (bootverbose) { + printf("pcm%d: map %d - allocating ", unit, i+1); + printf("0x%x bytes of ", 1<<config_id->map[i].ln2size); + printf("%s space ", (config_id->map[i].type & PCI_MAPPORT)? + "io" : "memory"); + printf("at 0x%x...", config_id->map[i].base); + } +#endif + regid[j] = PCIR_MAPS + i*4; + type[j] = SYS_RES_MEMORY; + reg[j] = bus_alloc_resource(dev, type[j], ®id[j], + 0, ~0, 1, RF_ACTIVE); + if (!reg[j]) { + type[j] = SYS_RES_IOPORT; + reg[j] = bus_alloc_resource(dev, type[j], ®id[j], + 0, ~0, 1, RF_ACTIVE); + } + if (reg[j]) { + au->st[i] = rman_get_bustag(reg[j]); + au->sh[i] = rman_get_bushandle(reg[j]); + mapped++; + } +#if 0 + if (bootverbose) printf("%s\n", mapped? "ok" : "failed"); +#endif + if (mapped) j++; + if (j == 10) { + /* XXX */ + device_printf(dev, "too many resources"); + goto bad; + } + } + +#if 0 + if (j < config_id->nummaps) { + printf("pcm%d: unable to map a required resource\n", unit); + free(au, M_DEVBUF); + return; + } +#endif + + au_wr(au, 0, AU_REG_IRQEN, 0, 4); + + irqid = 0; + irq = bus_alloc_resource(dev, SYS_RES_IRQ, &irqid, + 0, ~0, 1, RF_ACTIVE); + if (!irq + || bus_setup_intr(dev, irq, INTR_TYPE_TTY, au_intr, au, &ih)) { + device_printf(dev, "unable to map interrupt\n"); + goto bad; + } + + if (au_testirq(au)) device_printf(dev, "irq test failed\n"); + + if (au_init(dev, au) == -1) { + device_printf(dev, "unable to initialize the card\n"); + goto bad; + } + + codec = ac97_create(au, au_rdcd, au_wrcd); + if (codec == NULL) goto bad; + mixer_init(d, &ac97_mixer, codec); + + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/AU_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, + /*flags*/0, &au->parent_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto bad; + } + + snprintf(status, SND_STATUSLEN, "at %s 0x%lx irq %ld", + (type[0] == SYS_RES_IOPORT)? "io" : "memory", + rman_get_start(reg[0]), rman_get_start(irq)); + + if (pcm_register(dev, au, 1, 1)) goto bad; + /* pcm_addchan(dev, PCMDIR_REC, &au_chantemplate, au); */ + pcm_addchan(dev, PCMDIR_PLAY, &au_chantemplate, au); + pcm_setstatus(dev, status); + + return 0; + + bad: + if (au) free(au, M_DEVBUF); + for (i = 0; i < j; i++) + bus_release_resource(dev, type[i], regid[i], reg[i]); + if (ih) bus_teardown_intr(dev, irq, ih); + if (irq) bus_release_resource(dev, SYS_RES_IRQ, irqid, irq); + return ENXIO; +} + +static device_method_t au_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, au_pci_probe), + DEVMETHOD(device_attach, au_pci_attach), + + { 0, 0 } +}; + +static driver_t au_driver = { + "pcm", + au_methods, + sizeof(snddev_info), +}; + +static devclass_t pcm_devclass; + +DRIVER_MODULE(au, pci, au_driver, pcm_devclass, 0, 0); + +#endif /* NPCI != 0 */ + diff --git a/sys/dev/pcm/pci/aureal.h b/sys/dev/pcm/pci/aureal.h new file mode 100644 index 0000000..c5bcb29 --- /dev/null +++ b/sys/dev/pcm/pci/aureal.h @@ -0,0 +1,99 @@ +/* + * 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$ + */ + +#ifndef _AU8820_REG_H +#define _AU8820_REG_H + +#define AU_BUFFSIZE 0x4000 + +#define AU_REG_FIFOBASE 0x0e000 + +#define AU_REG_UNK2 0x105c0 +#define AU_REG_UNK3 0x10600 +#define AU_REG_UNK4 0x10604 +#define AU_REG_UNK5 0x10608 + +#define AU_REG_RTBASE 0x10800 + +#define AU_REG_ADB 0x10a00 + +#define AU_REG_CODECCHN 0x11880 + +#define AU_REG_CODECST 0x11984 +#define AU_CDC_RUN 0x00000040 +#define AU_CDC_WROK 0x00000100 +#define AU_CDC_RESET 0x00008000 + +#define AU_REG_CODECIO 0x11988 +#define AU_CDC_DATAMASK 0x0000ffff +#define AU_CDC_REGMASK 0x007f0000 +#define AU_CDC_REGSET 0x00800000 +#define AU_CDC_READY 0x04000000 + +#define AU_REG_CODECEN 0x11990 +#define AU_CDC_CHAN1EN 0x00000100 +#define AU_CDC_CHAN2EN 0x00000200 + +#define AU_REG_UNK1 0x1199c + +#define AU_REG_IRQSRC 0x12800 +#define AU_IRQ_FATAL 0x0001 +#define AU_IRQ_PARITY 0x0002 +#define AU_IRQ_PCMOUT 0x0020 +#define AU_IRQ_UNKNOWN 0x1000 +#define AU_IRQ_MIDI 0x2000 +#define AU_REG_IRQEN 0x12804 + +#define AU_REG_IRQGLOB 0x1280c +#define AU_IRQ_ENABLE 0x4000 + +#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 + + +#endif diff --git a/sys/dev/pcm/pci/es1370.c b/sys/dev/pcm/pci/es1370.c new file mode 100644 index 0000000..f0f1413 --- /dev/null +++ b/sys/dev/pcm/pci/es1370.c @@ -0,0 +1,543 @@ +/* + * Support the ENSONIQ AudioPCI board based on the ES1370 and Codec + * AK4531. + * + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * Copyright (c) 1998 by Joachim Kuebart. 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. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgement: + * This product includes software developed by Joachim Kuebart. + * + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``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 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: es1370.c,v 1.4 1999/05/09 17:06:45 peter Exp $ + */ + +#include "pci.h" +#include "pcm.h" + +#include <dev/pcm/sound.h> +#include <dev/pcm/pci/es1370.h> + +#include <pci/pcireg.h> +#include <pci/pcivar.h> + +#if NPCI != 0 + +#define MEM_MAP_REG 0x14 + +/* PCI IDs of supported chips */ +#define ES1370_PCI_ID 0x50001274 + +/* device private data */ +struct es_info; + +struct es_chinfo { + struct es_info *parent; + pcm_channel *channel; + snd_dbuf *buffer; + int dir; + u_int32_t fmt; +}; + +struct es_info { + bus_space_tag_t st; + bus_space_handle_t sh; + bus_dma_tag_t parent_dmat; + + /* Contents of board's registers */ + u_long ctrl; + u_long sctrl; + struct es_chinfo pch, rch; +}; + +/* -------------------------------------------------------------------- */ + +/* prototypes */ +static int es_init(struct es_info *); +static void es_intr(void *); +static int write_codec(struct es_info *, u_char, u_char); + +/* channel interface */ +static void *eschan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); +static int eschan_setdir(void *data, int dir); +static int eschan_setformat(void *data, u_int32_t format); +static int eschan_setspeed(void *data, u_int32_t speed); +static int eschan_setblocksize(void *data, u_int32_t blocksize); +static int eschan_trigger(void *data, int go); +static int eschan_getptr(void *data); +static pcmchan_caps *eschan_getcaps(void *data); + +static pcmchan_caps es_playcaps = { + 4000, 48000, + AFMT_STEREO | AFMT_U8 | AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE +}; + +static pcmchan_caps es_reccaps = { + 4000, 48000, + AFMT_STEREO | AFMT_U8 | AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE +}; + +static pcm_channel es_chantemplate = { + eschan_init, + eschan_setdir, + eschan_setformat, + eschan_setspeed, + eschan_setblocksize, + eschan_trigger, + eschan_getptr, + eschan_getcaps, +}; + +/* -------------------------------------------------------------------- */ + +/* The mixer interface */ + +static int es_mixinit(snd_mixer *m); +static int es_mixset(snd_mixer *m, unsigned dev, unsigned left, unsigned right); +static int es_mixsetrecsrc(snd_mixer *m, u_int32_t src); + +static snd_mixer es_mixer = { + "Ensoniq AudioPCI 1370 mixer", + es_mixinit, + es_mixset, + es_mixsetrecsrc, +}; + +static const struct { + unsigned volidx:4; + unsigned left:4; + unsigned right:4; + unsigned stereo:1; + unsigned recmask:13; + unsigned avail:1; +} mixtable[SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_VOLUME] = { 0, 0x0, 0x1, 1, 0x0000, 1 }, + [SOUND_MIXER_PCM] = { 1, 0x2, 0x3, 1, 0x0400, 1 }, + [SOUND_MIXER_SYNTH] = { 2, 0x4, 0x5, 1, 0x0060, 1 }, + [SOUND_MIXER_CD] = { 3, 0x6, 0x7, 1, 0x0006, 1 }, + [SOUND_MIXER_LINE] = { 4, 0x8, 0x9, 1, 0x0018, 1 }, + [SOUND_MIXER_LINE1] = { 5, 0xa, 0xb, 1, 0x1800, 1 }, + [SOUND_MIXER_LINE2] = { 6, 0xc, 0x0, 0, 0x0100, 1 }, + [SOUND_MIXER_LINE3] = { 7, 0xd, 0x0, 0, 0x0200, 1 }, + [SOUND_MIXER_MIC] = { 8, 0xe, 0x0, 0, 0x0001, 1 }, + [SOUND_MIXER_OGAIN] = { 9, 0xf, 0x0, 0, 0x0000, 1 } }; + +static int +es_mixinit(snd_mixer *m) +{ + int i; + u_int32_t v; + + v = 0; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].avail) v |= (1 << i); + mix_setdevs(m, v); + v = 0; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].recmask) v |= (1 << i); + mix_setrecdevs(m, v); + return 0; +} + +static int +es_mixset(snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + int l, r, rl, rr; + + if (!mixtable[dev].avail) return -1; + l = left; + r = mixtable[dev].stereo? right : l; + if (mixtable[dev].left == 0xf) { + rl = (l < 2)? 0x80 : 7 - (l - 2) / 14; + } else { + rl = (l < 10)? 0x80 : 15 - (l - 10) / 6; + } + if (mixtable[dev].stereo) { + rr = (r < 10)? 0x80 : 15 - (r - 10) / 6; + write_codec(mix_getdevinfo(m), mixtable[dev].right, rr); + } + write_codec(mix_getdevinfo(m), mixtable[dev].left, rl); + return l | (r << 8); +} + +static int +es_mixsetrecsrc(snd_mixer *m, u_int32_t src) +{ + int i, j = 0; + + if (src == 0) src = 1 << SOUND_MIXER_MIC; + src &= mix_getrecdevs(m); + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if ((src & (1 << i)) != 0) j |= mixtable[i].recmask; + + write_codec(mix_getdevinfo(m), CODEC_LIMIX1, j & 0x55); + write_codec(mix_getdevinfo(m), CODEC_RIMIX1, j & 0xaa); + write_codec(mix_getdevinfo(m), CODEC_LIMIX2, (j >> 8) & 0x17); + write_codec(mix_getdevinfo(m), CODEC_RIMIX2, (j >> 8) & 0x0f); + write_codec(mix_getdevinfo(m), CODEC_OMIX1, 0x7f); + write_codec(mix_getdevinfo(m), CODEC_OMIX2, 0x3f); + return src; +} + +static int +write_codec(struct es_info *es, u_char i, u_char data) +{ + int wait = 100; /* 100 msec timeout */ + + do { + if ((bus_space_read_4(es->st, es->sh, ES1370_REG_STATUS) & + STAT_CSTAT) == 0) { + bus_space_write_2(es->st, es->sh, ES1370_REG_CODEC, + ((u_short)i << CODEC_INDEX_SHIFT) | data); + return 0; + } + DELAY(1000); + } while (--wait); + printf("pcm: write_codec timed out\n"); + return -1; +} + +/* -------------------------------------------------------------------- */ + +/* channel interface */ +static void * +eschan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) +{ + struct es_info *es = devinfo; + struct es_chinfo *ch = (dir == PCMDIR_PLAY)? &es->pch : &es->rch; + + ch->parent = es; + ch->channel = c; + ch->buffer = b; + ch->buffer->bufsize = ES_BUFFSIZE; + if (chn_allocbuf(ch->buffer, es->parent_dmat) == -1) return NULL; + return ch; +} + +static int +eschan_setdir(void *data, int dir) +{ + struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + + if (dir == PCMDIR_PLAY) { + bus_space_write_1(es->st, es->sh, ES1370_REG_MEMPAGE, + ES1370_REG_DAC2_FRAMEADR >> 8); + bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_FRAMEADR & 0xff, + vtophys(ch->buffer->buf)); + bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_FRAMECNT & 0xff, + (ch->buffer->bufsize >> 2) - 1); + } else { + bus_space_write_1(es->st, es->sh, ES1370_REG_MEMPAGE, + ES1370_REG_ADC_FRAMEADR >> 8); + bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_FRAMEADR & 0xff, + vtophys(ch->buffer->buf)); + bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_FRAMECNT & 0xff, + (ch->buffer->bufsize >> 2) - 1); + } + ch->dir = dir; + return 0; +} + +static int +eschan_setformat(void *data, u_int32_t format) +{ + struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + + if (ch->dir == PCMDIR_PLAY) { + es->sctrl &= ~SCTRL_P2FMT; + if (format & AFMT_S16_LE) es->sctrl |= SCTRL_P2SEB; + if (format & AFMT_STEREO) es->sctrl |= SCTRL_P2SMB; + } else { + es->sctrl &= ~SCTRL_R1FMT; + if (format & AFMT_S16_LE) es->sctrl |= SCTRL_R1SEB; + if (format & AFMT_STEREO) es->sctrl |= SCTRL_R1SMB; + } + bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); + ch->fmt = format; + return 0; +} + +static int +eschan_setspeed(void *data, u_int32_t speed) +{ + struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + + es->ctrl &= ~CTRL_PCLKDIV; + es->ctrl |= DAC2_SRTODIV(speed) << CTRL_SH_PCLKDIV; + bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); + /* rec/play speeds locked together - should indicate in flags */ +#if 0 + if (ch->direction == PCMDIR_PLAY) d->rec[0].speed = speed; + else d->play[0].speed = speed; +#endif + return speed; /* XXX calc real speed */ +} + +static int +eschan_setblocksize(void *data, u_int32_t blocksize) +{ + return blocksize; +} + +static int +eschan_trigger(void *data, int go) +{ + struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + unsigned cnt = ch->buffer->dl / ch->buffer->sample_size - 1; + + if (ch->dir == PCMDIR_PLAY) { + if (go == PCMTRIG_START) { + int b = (ch->fmt & AFMT_S16_LE)? 2 : 1; + es->ctrl |= CTRL_DAC2_EN; + es->sctrl &= ~(SCTRL_P2ENDINC | SCTRL_P2STINC | + SCTRL_P2LOOPSEL | SCTRL_P2PAUSE | + SCTRL_P2DACSEN); + es->sctrl |= SCTRL_P2INTEN | (b << SCTRL_SH_P2ENDINC); + bus_space_write_4(es->st, es->sh, + ES1370_REG_DAC2_SCOUNT, cnt); + } else es->ctrl &= ~CTRL_DAC2_EN; + } else { + if (go == PCMTRIG_START) { + es->ctrl |= CTRL_ADC_EN; + es->sctrl &= ~SCTRL_R1LOOPSEL; + es->sctrl |= SCTRL_R1INTEN; + bus_space_write_4(es->st, es->sh, + ES1370_REG_ADC_SCOUNT, cnt); + } else es->ctrl &= ~CTRL_ADC_EN; + } + bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); + bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); + return 0; +} + +static int +eschan_getptr(void *data) +{ + struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + if (ch->dir == PCMDIR_PLAY) { + bus_space_write_4(es->st, es->sh, ES1370_REG_MEMPAGE, + ES1370_REG_DAC2_FRAMECNT >> 8); + return (bus_space_read_4(es->st, es->sh, + ES1370_REG_DAC2_FRAMECNT & 0xff) >> 14) & 0x3fffc; + } else { + bus_space_write_4(es->st, es->sh, ES1370_REG_MEMPAGE, + ES1370_REG_ADC_FRAMECNT >> 8); + return (bus_space_read_4(es->st, es->sh, + ES1370_REG_ADC_FRAMECNT & 0xff) >> 14) & 0x3fffc; + } +} + +static pcmchan_caps * +eschan_getcaps(void *data) +{ + struct es_chinfo *ch = data; + return (ch->dir == PCMDIR_PLAY)? &es_playcaps : &es_reccaps; +} + +/* The interrupt handler */ +static void +es_intr (void *p) +{ + struct es_info *es = p; + unsigned intsrc, sctrl; + + intsrc = bus_space_read_4(es->st, es->sh, ES1370_REG_STATUS); + if ((intsrc & STAT_INTR) == 0) return; + + sctrl = es->sctrl; + if (intsrc & STAT_ADC) sctrl &= ~SCTRL_R1INTEN; + if (intsrc & STAT_DAC1) sctrl &= ~SCTRL_P1INTEN; + if (intsrc & STAT_DAC2) sctrl &= ~SCTRL_P2INTEN; + + bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, sctrl); + bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); + + if (intsrc & STAT_DAC2) chn_intr(es->pch.channel); + if (intsrc & STAT_ADC) chn_intr(es->rch.channel); +} + +/* -------------------------------------------------------------------- */ + +/* + * Probe and attach the card + */ + +static int +es_init(struct es_info *es) +{ + es->ctrl = CTRL_CDC_EN | CTRL_SERR_DIS | + (DAC2_SRTODIV(DSP_DEFAULT_SPEED) << CTRL_SH_PCLKDIV); + bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); + + es->sctrl = 0; + bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); + + write_codec(es, CODEC_RES_PD, 3);/* No RST, PD */ + write_codec(es, CODEC_CSEL, 0); /* CODEC ADC and CODEC DAC use + * {LR,B}CLK2 and run off the LRCLK2 + * PLL; program DAC_SYNC=0! */ + write_codec(es, CODEC_ADSEL, 0);/* Recording source is mixer */ + write_codec(es, CODEC_MGAIN, 0);/* MIC amp is 0db */ + + return 0; +} + +static int +es_pci_probe(device_t dev) +{ + if (pci_get_devid(dev) == ES1370_PCI_ID) { + device_set_desc(dev, "AudioPCI ES1370"); + return 0; + } + return ENXIO; +} + +static int +es_pci_attach(device_t dev) +{ + snddev_info *d; + u_int32_t data; + struct es_info *es = 0; + int type = 0; + int regid; + struct resource *reg = 0; + int mapped; + int irqid; + struct resource *irq = 0; + void *ih = 0; + char status[SND_STATUSLEN]; + + d = device_get_softc(dev); + if ((es = malloc(sizeof *es, M_DEVBUF, M_NOWAIT)) == NULL) { + device_printf(dev, "cannot allocate softc\n"); + return ENXIO; + } + bzero(es, sizeof *es); + + mapped = 0; + data = pci_read_config(dev, PCIR_COMMAND, 2); + if (mapped == 0 && (data & PCIM_CMD_MEMEN)) { + regid = MEM_MAP_REG; + type = SYS_RES_MEMORY; + reg = bus_alloc_resource(dev, type, ®id, + 0, ~0, 1, RF_ACTIVE); + if (reg) { + es->st = rman_get_bustag(reg); + es->sh = rman_get_bushandle(reg); + mapped++; + } + } + if (mapped == 0 && (data & PCIM_CMD_PORTEN)) { + regid = PCI_MAP_REG_START; + type = SYS_RES_IOPORT; + reg = bus_alloc_resource(dev, type, ®id, + 0, ~0, 1, RF_ACTIVE); + if (reg) { + es->st = rman_get_bustag(reg); + es->sh = rman_get_bushandle(reg); + mapped++; + } + } + if (mapped == 0) { + device_printf(dev, "unable to map register space\n"); + goto bad; + } + + if (es_init(es) == -1) { + device_printf(dev, "unable to initialize the card\n"); + goto bad; + } + mixer_init(d, &es_mixer, es); + + irqid = 0; + irq = bus_alloc_resource(dev, SYS_RES_IRQ, &irqid, + 0, ~0, 1, RF_ACTIVE); + if (!irq + || bus_setup_intr(dev, irq, INTR_TYPE_TTY, es_intr, es, &ih)) { + device_printf(dev, "unable to map interrupt\n"); + goto bad; + } + + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/ES_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, + /*flags*/0, &es->parent_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto bad; + } + + snprintf(status, SND_STATUSLEN, "at %s 0x%lx irq %ld", + (type == SYS_RES_IOPORT)? "io" : "memory", + rman_get_start(reg), rman_get_start(irq)); + + if (pcm_register(dev, es, 1, 1)) goto bad; + pcm_addchan(dev, PCMDIR_REC, &es_chantemplate, es); + pcm_addchan(dev, PCMDIR_PLAY, &es_chantemplate, es); + pcm_setstatus(dev, status); + + return 0; + + bad: + if (es) free(es, M_DEVBUF); + if (reg) bus_release_resource(dev, type, regid, reg); + if (ih) bus_teardown_intr(dev, irq, ih); + if (irq) bus_release_resource(dev, SYS_RES_IRQ, irqid, irq); + return ENXIO; +} + +static device_method_t es_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, es_pci_probe), + DEVMETHOD(device_attach, es_pci_attach), + + { 0, 0 } +}; + +static driver_t es_driver = { + "pcm", + es_methods, + sizeof(snddev_info), +}; + +static devclass_t pcm_devclass; + +DRIVER_MODULE(es, pci, es_driver, pcm_devclass, 0, 0); + +#endif /* NPCI != 0 */ diff --git a/sys/dev/pcm/pci/es1370.h b/sys/dev/pcm/pci/es1370.h new file mode 100644 index 0000000..6c981e3 --- /dev/null +++ b/sys/dev/pcm/pci/es1370.h @@ -0,0 +1,134 @@ +/* + * This supports the ENSONIQ AudioPCI board based on the ES1370. + * + * Copyright (c) 1998 Joachim Kuebart <joki@kuebart.stuttgart.netsurf.de> + * 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 immediately at the beginning of the file, without modification, + * 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. + * 3. Absolutely no warranty of function or purpose is made by the author + * Joachim Kuebart. + * 4. Modifications may be freely made to this file if the above conditions + * are met. + * + * $Id: es1370_reg.h,v 1.1 1998/12/31 08:14:27 luigi Exp $ + */ + +#ifndef _ES1370_REG_H +#define _ES1370_REG_H + +#define ES1370_REG_CONTROL 0x00 +#define ES1370_REG_STATUS 0x04 +#define ES1370_REG_UART_DATA 0x08 +#define ES1370_REG_UART_STATUS 0x09 +#define ES1370_REG_UART_CONTROL 0x09 +#define ES1370_REG_UART_TEST 0x0a +#define ES1370_REG_MEMPAGE 0x0c +#define ES1370_REG_CODEC 0x10 +#define CODEC_INDEX_SHIFT 8 +#define ES1370_REG_SERIAL_CONTROL 0x20 +#define ES1370_REG_DAC1_SCOUNT 0x24 +#define ES1370_REG_DAC2_SCOUNT 0x28 +#define ES1370_REG_ADC_SCOUNT 0x2c + +#define ES1370_REG_DAC1_FRAMEADR 0xc30 +#define ES1370_REG_DAC1_FRAMECNT 0xc34 +#define ES1370_REG_DAC2_FRAMEADR 0xc38 +#define ES1370_REG_DAC2_FRAMECNT 0xc3c +#define ES1370_REG_ADC_FRAMEADR 0xd30 +#define ES1370_REG_ADC_FRAMECNT 0xd34 + +#define DAC2_SRTODIV(x) (((1411200 + (x) / 2) / (x) - 2) & 0x1fff) +#define DAC2_DIVTOSR(x) (1411200 / ((x) + 2)) + +#define CTRL_ADC_STOP 0x80000000 /* 1 = ADC stopped */ +#define CTRL_XCTL1 0x40000000 /* SERR pin if enabled */ +#define CTRL_OPEN 0x20000000 /* no function, can be read and + * written */ +#define CTRL_PCLKDIV 0x1fff0000 /* ADC/DAC2 clock divider */ +#define CTRL_SH_PCLKDIV 16 +#define CTRL_MSFMTSEL 0x00008000 /* MPEG serial data fmt: 0 = Sony, 1 + * = I2S */ +#define CTRL_M_SBB 0x00004000 /* DAC2 clock: 0 = PCLKDIV, 1 = MPEG */ +#define CTRL_WTSRSEL 0x00003000 /* DAC1 clock freq: 0=5512, 1=11025, + * 2=22050, 3=44100 */ +#define CTRL_SH_WTSRSEL 12 +#define CTRL_DAC_SYNC 0x00000800 /* 1 = DAC2 runs off DAC1 clock */ +#define CTRL_CCB_INTRM 0x00000400 /* 1 = CCB "voice" ints enabled */ +#define CTRL_M_CB 0x00000200 /* recording source: 0 = ADC, 1 = + * MPEG */ +#define CTRL_XCTL0 0x00000100 /* 0 = Line in, 1 = Line out */ +#define CTRL_BREQ 0x00000080 /* 1 = test mode (internal mem test) */ +#define CTRL_DAC1_EN 0x00000040 /* enable DAC1 */ +#define CTRL_DAC2_EN 0x00000020 /* enable DAC2 */ +#define CTRL_ADC_EN 0x00000010 /* enable ADC */ +#define CTRL_UART_EN 0x00000008 /* enable MIDI uart */ +#define CTRL_JYSTK_EN 0x00000004 /* enable Joystick port (presumably + * at address 0x200) */ +#define CTRL_CDC_EN 0x00000002 /* enable serial (CODEC) interface */ +#define CTRL_SERR_DIS 0x00000001 /* 1 = disable PCI SERR signal */ + +#define SCTRL_P2ENDINC 0x00380000 /* */ +#define SCTRL_SH_P2ENDINC 19 +#define SCTRL_P2STINC 0x00070000 /* */ +#define SCTRL_SH_P2STINC 16 +#define SCTRL_R1LOOPSEL 0x00008000 /* 0 = loop mode */ +#define SCTRL_P2LOOPSEL 0x00004000 /* 0 = loop mode */ +#define SCTRL_P1LOOPSEL 0x00002000 /* 0 = loop mode */ +#define SCTRL_P2PAUSE 0x00001000 /* 1 = pause mode */ +#define SCTRL_P1PAUSE 0x00000800 /* 1 = pause mode */ +#define SCTRL_R1INTEN 0x00000400 /* enable interrupt */ +#define SCTRL_P2INTEN 0x00000200 /* enable interrupt */ +#define SCTRL_P1INTEN 0x00000100 /* enable interrupt */ +#define SCTRL_P1SCTRLD 0x00000080 /* reload sample count register for + * DAC1 */ +#define SCTRL_P2DACSEN 0x00000040 /* 1 = DAC2 play back last sample + * when disabled */ +#define SCTRL_R1SEB 0x00000020 /* 1 = 16bit */ +#define SCTRL_R1SMB 0x00000010 /* 1 = stereo */ +#define SCTRL_R1FMT 0x00000030 /* format mask */ +#define SCTRL_SH_R1FMT 4 +#define SCTRL_P2SEB 0x00000008 /* 1 = 16bit */ +#define SCTRL_P2SMB 0x00000004 /* 1 = stereo */ +#define SCTRL_P2FMT 0x0000000c /* format mask */ +#define SCTRL_SH_P2FMT 2 +#define SCTRL_P1SEB 0x00000002 /* 1 = 16bit */ +#define SCTRL_P1SMB 0x00000001 /* 1 = stereo */ +#define SCTRL_P1FMT 0x00000003 /* format mask */ +#define SCTRL_SH_P1FMT 0 + +#define STAT_INTR 0x80000000 /* wired or of all interrupt bits */ +#define STAT_CSTAT 0x00000400 /* 1 = codec busy or codec write in + * progress */ +#define STAT_CBUSY 0x00000200 /* 1 = codec busy */ +#define STAT_CWRIP 0x00000100 /* 1 = codec write in progress */ +#define STAT_VC 0x00000060 /* CCB int source, 0=DAC1, 1=DAC2, + * 2=ADC, 3=undef */ +#define STAT_SH_VC 5 +#define STAT_MCCB 0x00000010 /* CCB int pending */ +#define STAT_UART 0x00000008 /* UART int pending */ +#define STAT_DAC1 0x00000004 /* DAC1 int pending */ +#define STAT_DAC2 0x00000002 /* DAC2 int pending */ +#define STAT_ADC 0x00000001 /* ADC int pending */ + +#define CODEC_OMIX1 0x10 +#define CODEC_OMIX2 0x11 +#define CODEC_LIMIX1 0x12 +#define CODEC_RIMIX1 0x13 +#define CODEC_LIMIX2 0x14 +#define CODEC_RIMIX2 0x15 +#define CODEC_RES_PD 0x16 +#define CODEC_CSEL 0x17 +#define CODEC_ADSEL 0x18 +#define CODEC_MGAIN 0x19 + +#define ES_BUFFSIZE 0x20000 /* We're PCI! Use a large buffer */ + +#endif diff --git a/sys/dev/pcm/pci/es137x.c b/sys/dev/pcm/pci/es137x.c new file mode 100644 index 0000000..f0f1413 --- /dev/null +++ b/sys/dev/pcm/pci/es137x.c @@ -0,0 +1,543 @@ +/* + * Support the ENSONIQ AudioPCI board based on the ES1370 and Codec + * AK4531. + * + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * Copyright (c) 1998 by Joachim Kuebart. 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. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgement: + * This product includes software developed by Joachim Kuebart. + * + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``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 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: es1370.c,v 1.4 1999/05/09 17:06:45 peter Exp $ + */ + +#include "pci.h" +#include "pcm.h" + +#include <dev/pcm/sound.h> +#include <dev/pcm/pci/es1370.h> + +#include <pci/pcireg.h> +#include <pci/pcivar.h> + +#if NPCI != 0 + +#define MEM_MAP_REG 0x14 + +/* PCI IDs of supported chips */ +#define ES1370_PCI_ID 0x50001274 + +/* device private data */ +struct es_info; + +struct es_chinfo { + struct es_info *parent; + pcm_channel *channel; + snd_dbuf *buffer; + int dir; + u_int32_t fmt; +}; + +struct es_info { + bus_space_tag_t st; + bus_space_handle_t sh; + bus_dma_tag_t parent_dmat; + + /* Contents of board's registers */ + u_long ctrl; + u_long sctrl; + struct es_chinfo pch, rch; +}; + +/* -------------------------------------------------------------------- */ + +/* prototypes */ +static int es_init(struct es_info *); +static void es_intr(void *); +static int write_codec(struct es_info *, u_char, u_char); + +/* channel interface */ +static void *eschan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); +static int eschan_setdir(void *data, int dir); +static int eschan_setformat(void *data, u_int32_t format); +static int eschan_setspeed(void *data, u_int32_t speed); +static int eschan_setblocksize(void *data, u_int32_t blocksize); +static int eschan_trigger(void *data, int go); +static int eschan_getptr(void *data); +static pcmchan_caps *eschan_getcaps(void *data); + +static pcmchan_caps es_playcaps = { + 4000, 48000, + AFMT_STEREO | AFMT_U8 | AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE +}; + +static pcmchan_caps es_reccaps = { + 4000, 48000, + AFMT_STEREO | AFMT_U8 | AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE +}; + +static pcm_channel es_chantemplate = { + eschan_init, + eschan_setdir, + eschan_setformat, + eschan_setspeed, + eschan_setblocksize, + eschan_trigger, + eschan_getptr, + eschan_getcaps, +}; + +/* -------------------------------------------------------------------- */ + +/* The mixer interface */ + +static int es_mixinit(snd_mixer *m); +static int es_mixset(snd_mixer *m, unsigned dev, unsigned left, unsigned right); +static int es_mixsetrecsrc(snd_mixer *m, u_int32_t src); + +static snd_mixer es_mixer = { + "Ensoniq AudioPCI 1370 mixer", + es_mixinit, + es_mixset, + es_mixsetrecsrc, +}; + +static const struct { + unsigned volidx:4; + unsigned left:4; + unsigned right:4; + unsigned stereo:1; + unsigned recmask:13; + unsigned avail:1; +} mixtable[SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_VOLUME] = { 0, 0x0, 0x1, 1, 0x0000, 1 }, + [SOUND_MIXER_PCM] = { 1, 0x2, 0x3, 1, 0x0400, 1 }, + [SOUND_MIXER_SYNTH] = { 2, 0x4, 0x5, 1, 0x0060, 1 }, + [SOUND_MIXER_CD] = { 3, 0x6, 0x7, 1, 0x0006, 1 }, + [SOUND_MIXER_LINE] = { 4, 0x8, 0x9, 1, 0x0018, 1 }, + [SOUND_MIXER_LINE1] = { 5, 0xa, 0xb, 1, 0x1800, 1 }, + [SOUND_MIXER_LINE2] = { 6, 0xc, 0x0, 0, 0x0100, 1 }, + [SOUND_MIXER_LINE3] = { 7, 0xd, 0x0, 0, 0x0200, 1 }, + [SOUND_MIXER_MIC] = { 8, 0xe, 0x0, 0, 0x0001, 1 }, + [SOUND_MIXER_OGAIN] = { 9, 0xf, 0x0, 0, 0x0000, 1 } }; + +static int +es_mixinit(snd_mixer *m) +{ + int i; + u_int32_t v; + + v = 0; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].avail) v |= (1 << i); + mix_setdevs(m, v); + v = 0; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].recmask) v |= (1 << i); + mix_setrecdevs(m, v); + return 0; +} + +static int +es_mixset(snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + int l, r, rl, rr; + + if (!mixtable[dev].avail) return -1; + l = left; + r = mixtable[dev].stereo? right : l; + if (mixtable[dev].left == 0xf) { + rl = (l < 2)? 0x80 : 7 - (l - 2) / 14; + } else { + rl = (l < 10)? 0x80 : 15 - (l - 10) / 6; + } + if (mixtable[dev].stereo) { + rr = (r < 10)? 0x80 : 15 - (r - 10) / 6; + write_codec(mix_getdevinfo(m), mixtable[dev].right, rr); + } + write_codec(mix_getdevinfo(m), mixtable[dev].left, rl); + return l | (r << 8); +} + +static int +es_mixsetrecsrc(snd_mixer *m, u_int32_t src) +{ + int i, j = 0; + + if (src == 0) src = 1 << SOUND_MIXER_MIC; + src &= mix_getrecdevs(m); + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if ((src & (1 << i)) != 0) j |= mixtable[i].recmask; + + write_codec(mix_getdevinfo(m), CODEC_LIMIX1, j & 0x55); + write_codec(mix_getdevinfo(m), CODEC_RIMIX1, j & 0xaa); + write_codec(mix_getdevinfo(m), CODEC_LIMIX2, (j >> 8) & 0x17); + write_codec(mix_getdevinfo(m), CODEC_RIMIX2, (j >> 8) & 0x0f); + write_codec(mix_getdevinfo(m), CODEC_OMIX1, 0x7f); + write_codec(mix_getdevinfo(m), CODEC_OMIX2, 0x3f); + return src; +} + +static int +write_codec(struct es_info *es, u_char i, u_char data) +{ + int wait = 100; /* 100 msec timeout */ + + do { + if ((bus_space_read_4(es->st, es->sh, ES1370_REG_STATUS) & + STAT_CSTAT) == 0) { + bus_space_write_2(es->st, es->sh, ES1370_REG_CODEC, + ((u_short)i << CODEC_INDEX_SHIFT) | data); + return 0; + } + DELAY(1000); + } while (--wait); + printf("pcm: write_codec timed out\n"); + return -1; +} + +/* -------------------------------------------------------------------- */ + +/* channel interface */ +static void * +eschan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) +{ + struct es_info *es = devinfo; + struct es_chinfo *ch = (dir == PCMDIR_PLAY)? &es->pch : &es->rch; + + ch->parent = es; + ch->channel = c; + ch->buffer = b; + ch->buffer->bufsize = ES_BUFFSIZE; + if (chn_allocbuf(ch->buffer, es->parent_dmat) == -1) return NULL; + return ch; +} + +static int +eschan_setdir(void *data, int dir) +{ + struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + + if (dir == PCMDIR_PLAY) { + bus_space_write_1(es->st, es->sh, ES1370_REG_MEMPAGE, + ES1370_REG_DAC2_FRAMEADR >> 8); + bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_FRAMEADR & 0xff, + vtophys(ch->buffer->buf)); + bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_FRAMECNT & 0xff, + (ch->buffer->bufsize >> 2) - 1); + } else { + bus_space_write_1(es->st, es->sh, ES1370_REG_MEMPAGE, + ES1370_REG_ADC_FRAMEADR >> 8); + bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_FRAMEADR & 0xff, + vtophys(ch->buffer->buf)); + bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_FRAMECNT & 0xff, + (ch->buffer->bufsize >> 2) - 1); + } + ch->dir = dir; + return 0; +} + +static int +eschan_setformat(void *data, u_int32_t format) +{ + struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + + if (ch->dir == PCMDIR_PLAY) { + es->sctrl &= ~SCTRL_P2FMT; + if (format & AFMT_S16_LE) es->sctrl |= SCTRL_P2SEB; + if (format & AFMT_STEREO) es->sctrl |= SCTRL_P2SMB; + } else { + es->sctrl &= ~SCTRL_R1FMT; + if (format & AFMT_S16_LE) es->sctrl |= SCTRL_R1SEB; + if (format & AFMT_STEREO) es->sctrl |= SCTRL_R1SMB; + } + bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); + ch->fmt = format; + return 0; +} + +static int +eschan_setspeed(void *data, u_int32_t speed) +{ + struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + + es->ctrl &= ~CTRL_PCLKDIV; + es->ctrl |= DAC2_SRTODIV(speed) << CTRL_SH_PCLKDIV; + bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); + /* rec/play speeds locked together - should indicate in flags */ +#if 0 + if (ch->direction == PCMDIR_PLAY) d->rec[0].speed = speed; + else d->play[0].speed = speed; +#endif + return speed; /* XXX calc real speed */ +} + +static int +eschan_setblocksize(void *data, u_int32_t blocksize) +{ + return blocksize; +} + +static int +eschan_trigger(void *data, int go) +{ + struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + unsigned cnt = ch->buffer->dl / ch->buffer->sample_size - 1; + + if (ch->dir == PCMDIR_PLAY) { + if (go == PCMTRIG_START) { + int b = (ch->fmt & AFMT_S16_LE)? 2 : 1; + es->ctrl |= CTRL_DAC2_EN; + es->sctrl &= ~(SCTRL_P2ENDINC | SCTRL_P2STINC | + SCTRL_P2LOOPSEL | SCTRL_P2PAUSE | + SCTRL_P2DACSEN); + es->sctrl |= SCTRL_P2INTEN | (b << SCTRL_SH_P2ENDINC); + bus_space_write_4(es->st, es->sh, + ES1370_REG_DAC2_SCOUNT, cnt); + } else es->ctrl &= ~CTRL_DAC2_EN; + } else { + if (go == PCMTRIG_START) { + es->ctrl |= CTRL_ADC_EN; + es->sctrl &= ~SCTRL_R1LOOPSEL; + es->sctrl |= SCTRL_R1INTEN; + bus_space_write_4(es->st, es->sh, + ES1370_REG_ADC_SCOUNT, cnt); + } else es->ctrl &= ~CTRL_ADC_EN; + } + bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); + bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); + return 0; +} + +static int +eschan_getptr(void *data) +{ + struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + if (ch->dir == PCMDIR_PLAY) { + bus_space_write_4(es->st, es->sh, ES1370_REG_MEMPAGE, + ES1370_REG_DAC2_FRAMECNT >> 8); + return (bus_space_read_4(es->st, es->sh, + ES1370_REG_DAC2_FRAMECNT & 0xff) >> 14) & 0x3fffc; + } else { + bus_space_write_4(es->st, es->sh, ES1370_REG_MEMPAGE, + ES1370_REG_ADC_FRAMECNT >> 8); + return (bus_space_read_4(es->st, es->sh, + ES1370_REG_ADC_FRAMECNT & 0xff) >> 14) & 0x3fffc; + } +} + +static pcmchan_caps * +eschan_getcaps(void *data) +{ + struct es_chinfo *ch = data; + return (ch->dir == PCMDIR_PLAY)? &es_playcaps : &es_reccaps; +} + +/* The interrupt handler */ +static void +es_intr (void *p) +{ + struct es_info *es = p; + unsigned intsrc, sctrl; + + intsrc = bus_space_read_4(es->st, es->sh, ES1370_REG_STATUS); + if ((intsrc & STAT_INTR) == 0) return; + + sctrl = es->sctrl; + if (intsrc & STAT_ADC) sctrl &= ~SCTRL_R1INTEN; + if (intsrc & STAT_DAC1) sctrl &= ~SCTRL_P1INTEN; + if (intsrc & STAT_DAC2) sctrl &= ~SCTRL_P2INTEN; + + bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, sctrl); + bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); + + if (intsrc & STAT_DAC2) chn_intr(es->pch.channel); + if (intsrc & STAT_ADC) chn_intr(es->rch.channel); +} + +/* -------------------------------------------------------------------- */ + +/* + * Probe and attach the card + */ + +static int +es_init(struct es_info *es) +{ + es->ctrl = CTRL_CDC_EN | CTRL_SERR_DIS | + (DAC2_SRTODIV(DSP_DEFAULT_SPEED) << CTRL_SH_PCLKDIV); + bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); + + es->sctrl = 0; + bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); + + write_codec(es, CODEC_RES_PD, 3);/* No RST, PD */ + write_codec(es, CODEC_CSEL, 0); /* CODEC ADC and CODEC DAC use + * {LR,B}CLK2 and run off the LRCLK2 + * PLL; program DAC_SYNC=0! */ + write_codec(es, CODEC_ADSEL, 0);/* Recording source is mixer */ + write_codec(es, CODEC_MGAIN, 0);/* MIC amp is 0db */ + + return 0; +} + +static int +es_pci_probe(device_t dev) +{ + if (pci_get_devid(dev) == ES1370_PCI_ID) { + device_set_desc(dev, "AudioPCI ES1370"); + return 0; + } + return ENXIO; +} + +static int +es_pci_attach(device_t dev) +{ + snddev_info *d; + u_int32_t data; + struct es_info *es = 0; + int type = 0; + int regid; + struct resource *reg = 0; + int mapped; + int irqid; + struct resource *irq = 0; + void *ih = 0; + char status[SND_STATUSLEN]; + + d = device_get_softc(dev); + if ((es = malloc(sizeof *es, M_DEVBUF, M_NOWAIT)) == NULL) { + device_printf(dev, "cannot allocate softc\n"); + return ENXIO; + } + bzero(es, sizeof *es); + + mapped = 0; + data = pci_read_config(dev, PCIR_COMMAND, 2); + if (mapped == 0 && (data & PCIM_CMD_MEMEN)) { + regid = MEM_MAP_REG; + type = SYS_RES_MEMORY; + reg = bus_alloc_resource(dev, type, ®id, + 0, ~0, 1, RF_ACTIVE); + if (reg) { + es->st = rman_get_bustag(reg); + es->sh = rman_get_bushandle(reg); + mapped++; + } + } + if (mapped == 0 && (data & PCIM_CMD_PORTEN)) { + regid = PCI_MAP_REG_START; + type = SYS_RES_IOPORT; + reg = bus_alloc_resource(dev, type, ®id, + 0, ~0, 1, RF_ACTIVE); + if (reg) { + es->st = rman_get_bustag(reg); + es->sh = rman_get_bushandle(reg); + mapped++; + } + } + if (mapped == 0) { + device_printf(dev, "unable to map register space\n"); + goto bad; + } + + if (es_init(es) == -1) { + device_printf(dev, "unable to initialize the card\n"); + goto bad; + } + mixer_init(d, &es_mixer, es); + + irqid = 0; + irq = bus_alloc_resource(dev, SYS_RES_IRQ, &irqid, + 0, ~0, 1, RF_ACTIVE); + if (!irq + || bus_setup_intr(dev, irq, INTR_TYPE_TTY, es_intr, es, &ih)) { + device_printf(dev, "unable to map interrupt\n"); + goto bad; + } + + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/ES_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, + /*flags*/0, &es->parent_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto bad; + } + + snprintf(status, SND_STATUSLEN, "at %s 0x%lx irq %ld", + (type == SYS_RES_IOPORT)? "io" : "memory", + rman_get_start(reg), rman_get_start(irq)); + + if (pcm_register(dev, es, 1, 1)) goto bad; + pcm_addchan(dev, PCMDIR_REC, &es_chantemplate, es); + pcm_addchan(dev, PCMDIR_PLAY, &es_chantemplate, es); + pcm_setstatus(dev, status); + + return 0; + + bad: + if (es) free(es, M_DEVBUF); + if (reg) bus_release_resource(dev, type, regid, reg); + if (ih) bus_teardown_intr(dev, irq, ih); + if (irq) bus_release_resource(dev, SYS_RES_IRQ, irqid, irq); + return ENXIO; +} + +static device_method_t es_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, es_pci_probe), + DEVMETHOD(device_attach, es_pci_attach), + + { 0, 0 } +}; + +static driver_t es_driver = { + "pcm", + es_methods, + sizeof(snddev_info), +}; + +static devclass_t pcm_devclass; + +DRIVER_MODULE(es, pci, es_driver, pcm_devclass, 0, 0); + +#endif /* NPCI != 0 */ diff --git a/sys/dev/pcm/pci/es137x.h b/sys/dev/pcm/pci/es137x.h new file mode 100644 index 0000000..6c981e3 --- /dev/null +++ b/sys/dev/pcm/pci/es137x.h @@ -0,0 +1,134 @@ +/* + * This supports the ENSONIQ AudioPCI board based on the ES1370. + * + * Copyright (c) 1998 Joachim Kuebart <joki@kuebart.stuttgart.netsurf.de> + * 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 immediately at the beginning of the file, without modification, + * 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. + * 3. Absolutely no warranty of function or purpose is made by the author + * Joachim Kuebart. + * 4. Modifications may be freely made to this file if the above conditions + * are met. + * + * $Id: es1370_reg.h,v 1.1 1998/12/31 08:14:27 luigi Exp $ + */ + +#ifndef _ES1370_REG_H +#define _ES1370_REG_H + +#define ES1370_REG_CONTROL 0x00 +#define ES1370_REG_STATUS 0x04 +#define ES1370_REG_UART_DATA 0x08 +#define ES1370_REG_UART_STATUS 0x09 +#define ES1370_REG_UART_CONTROL 0x09 +#define ES1370_REG_UART_TEST 0x0a +#define ES1370_REG_MEMPAGE 0x0c +#define ES1370_REG_CODEC 0x10 +#define CODEC_INDEX_SHIFT 8 +#define ES1370_REG_SERIAL_CONTROL 0x20 +#define ES1370_REG_DAC1_SCOUNT 0x24 +#define ES1370_REG_DAC2_SCOUNT 0x28 +#define ES1370_REG_ADC_SCOUNT 0x2c + +#define ES1370_REG_DAC1_FRAMEADR 0xc30 +#define ES1370_REG_DAC1_FRAMECNT 0xc34 +#define ES1370_REG_DAC2_FRAMEADR 0xc38 +#define ES1370_REG_DAC2_FRAMECNT 0xc3c +#define ES1370_REG_ADC_FRAMEADR 0xd30 +#define ES1370_REG_ADC_FRAMECNT 0xd34 + +#define DAC2_SRTODIV(x) (((1411200 + (x) / 2) / (x) - 2) & 0x1fff) +#define DAC2_DIVTOSR(x) (1411200 / ((x) + 2)) + +#define CTRL_ADC_STOP 0x80000000 /* 1 = ADC stopped */ +#define CTRL_XCTL1 0x40000000 /* SERR pin if enabled */ +#define CTRL_OPEN 0x20000000 /* no function, can be read and + * written */ +#define CTRL_PCLKDIV 0x1fff0000 /* ADC/DAC2 clock divider */ +#define CTRL_SH_PCLKDIV 16 +#define CTRL_MSFMTSEL 0x00008000 /* MPEG serial data fmt: 0 = Sony, 1 + * = I2S */ +#define CTRL_M_SBB 0x00004000 /* DAC2 clock: 0 = PCLKDIV, 1 = MPEG */ +#define CTRL_WTSRSEL 0x00003000 /* DAC1 clock freq: 0=5512, 1=11025, + * 2=22050, 3=44100 */ +#define CTRL_SH_WTSRSEL 12 +#define CTRL_DAC_SYNC 0x00000800 /* 1 = DAC2 runs off DAC1 clock */ +#define CTRL_CCB_INTRM 0x00000400 /* 1 = CCB "voice" ints enabled */ +#define CTRL_M_CB 0x00000200 /* recording source: 0 = ADC, 1 = + * MPEG */ +#define CTRL_XCTL0 0x00000100 /* 0 = Line in, 1 = Line out */ +#define CTRL_BREQ 0x00000080 /* 1 = test mode (internal mem test) */ +#define CTRL_DAC1_EN 0x00000040 /* enable DAC1 */ +#define CTRL_DAC2_EN 0x00000020 /* enable DAC2 */ +#define CTRL_ADC_EN 0x00000010 /* enable ADC */ +#define CTRL_UART_EN 0x00000008 /* enable MIDI uart */ +#define CTRL_JYSTK_EN 0x00000004 /* enable Joystick port (presumably + * at address 0x200) */ +#define CTRL_CDC_EN 0x00000002 /* enable serial (CODEC) interface */ +#define CTRL_SERR_DIS 0x00000001 /* 1 = disable PCI SERR signal */ + +#define SCTRL_P2ENDINC 0x00380000 /* */ +#define SCTRL_SH_P2ENDINC 19 +#define SCTRL_P2STINC 0x00070000 /* */ +#define SCTRL_SH_P2STINC 16 +#define SCTRL_R1LOOPSEL 0x00008000 /* 0 = loop mode */ +#define SCTRL_P2LOOPSEL 0x00004000 /* 0 = loop mode */ +#define SCTRL_P1LOOPSEL 0x00002000 /* 0 = loop mode */ +#define SCTRL_P2PAUSE 0x00001000 /* 1 = pause mode */ +#define SCTRL_P1PAUSE 0x00000800 /* 1 = pause mode */ +#define SCTRL_R1INTEN 0x00000400 /* enable interrupt */ +#define SCTRL_P2INTEN 0x00000200 /* enable interrupt */ +#define SCTRL_P1INTEN 0x00000100 /* enable interrupt */ +#define SCTRL_P1SCTRLD 0x00000080 /* reload sample count register for + * DAC1 */ +#define SCTRL_P2DACSEN 0x00000040 /* 1 = DAC2 play back last sample + * when disabled */ +#define SCTRL_R1SEB 0x00000020 /* 1 = 16bit */ +#define SCTRL_R1SMB 0x00000010 /* 1 = stereo */ +#define SCTRL_R1FMT 0x00000030 /* format mask */ +#define SCTRL_SH_R1FMT 4 +#define SCTRL_P2SEB 0x00000008 /* 1 = 16bit */ +#define SCTRL_P2SMB 0x00000004 /* 1 = stereo */ +#define SCTRL_P2FMT 0x0000000c /* format mask */ +#define SCTRL_SH_P2FMT 2 +#define SCTRL_P1SEB 0x00000002 /* 1 = 16bit */ +#define SCTRL_P1SMB 0x00000001 /* 1 = stereo */ +#define SCTRL_P1FMT 0x00000003 /* format mask */ +#define SCTRL_SH_P1FMT 0 + +#define STAT_INTR 0x80000000 /* wired or of all interrupt bits */ +#define STAT_CSTAT 0x00000400 /* 1 = codec busy or codec write in + * progress */ +#define STAT_CBUSY 0x00000200 /* 1 = codec busy */ +#define STAT_CWRIP 0x00000100 /* 1 = codec write in progress */ +#define STAT_VC 0x00000060 /* CCB int source, 0=DAC1, 1=DAC2, + * 2=ADC, 3=undef */ +#define STAT_SH_VC 5 +#define STAT_MCCB 0x00000010 /* CCB int pending */ +#define STAT_UART 0x00000008 /* UART int pending */ +#define STAT_DAC1 0x00000004 /* DAC1 int pending */ +#define STAT_DAC2 0x00000002 /* DAC2 int pending */ +#define STAT_ADC 0x00000001 /* ADC int pending */ + +#define CODEC_OMIX1 0x10 +#define CODEC_OMIX2 0x11 +#define CODEC_LIMIX1 0x12 +#define CODEC_RIMIX1 0x13 +#define CODEC_LIMIX2 0x14 +#define CODEC_RIMIX2 0x15 +#define CODEC_RES_PD 0x16 +#define CODEC_CSEL 0x17 +#define CODEC_ADSEL 0x18 +#define CODEC_MGAIN 0x19 + +#define ES_BUFFSIZE 0x20000 /* We're PCI! Use a large buffer */ + +#endif diff --git a/sys/dev/pcm/pci/t4dwave.c b/sys/dev/pcm/pci/t4dwave.c new file mode 100644 index 0000000..5404fe2 --- /dev/null +++ b/sys/dev/pcm/pci/t4dwave.c @@ -0,0 +1,688 @@ +/* + * 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, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "pci.h" +#include "pcm.h" + +#include <dev/pcm/sound.h> +#include <dev/pcm/ac97.h> +#include <dev/pcm/pci/t4dwave.h> + +#include <pci/pcireg.h> +#include <pci/pcivar.h> + +#if NPCI != 0 + +/* -------------------------------------------------------------------- */ + +struct tr_info; + +/* channel registers */ +struct tr_chinfo { + u_int32_t cso, alpha, fms, fmc, ec; + u_int32_t lba; + u_int32_t eso, delta; + u_int32_t rvol, cvol; + u_int32_t gvsel, pan, vol, ctrl; + int index; + snd_dbuf *buffer; + pcm_channel *channel; + struct tr_info *parent; +}; + +/* device private data */ +struct tr_info { + u_int32_t type; + + bus_space_tag_t st; + bus_space_handle_t sh; + bus_dma_tag_t parent_dmat; + + struct resource *reg, *irq; + int regtype, regid, irqid; + void *ih; + + u_int32_t playchns; + struct tr_chinfo chinfo[TR_MAXPLAYCH]; + struct tr_chinfo recchinfo; +}; + +/* -------------------------------------------------------------------- */ + +/* + * prototypes + */ + +/* channel interface */ +static void *trchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); +static int trchan_setdir(void *data, int dir); +static int trchan_setformat(void *data, u_int32_t format); +static int trchan_setspeed(void *data, u_int32_t speed); +static int trchan_setblocksize(void *data, u_int32_t blocksize); +static int trchan_trigger(void *data, int go); +static int trchan_getptr(void *data); +static pcmchan_caps *trchan_getcaps(void *data); + +/* talk to the codec - called from ac97.c */ +static u_int32_t tr_rdcd(void *, int); +static void tr_wrcd(void *, int, u_int32_t); + +/* stuff */ +static int tr_init(struct tr_info *); +static void tr_intr(void *); + +/* talk to the card */ +static u_int32_t tr_rd(struct tr_info *, int, int); +static void tr_wr(struct tr_info *, int, u_int32_t, int); + +/* manipulate playback channels */ +static void tr_clrint(struct tr_info *, char); +static void tr_enaint(struct tr_info *, char, int); +static u_int32_t tr_testint(struct tr_info *, char); +static void tr_rdch(struct tr_info *, char, struct tr_chinfo *); +static void tr_wrch(struct tr_info *, char, struct tr_chinfo *); +static void tr_selch(struct tr_info *, char); +static void tr_startch(struct tr_info *, char); +static void tr_stopch(struct tr_info *, char); + +/* -------------------------------------------------------------------- */ + +static pcmchan_caps tr_reccaps = { + 4000, 48000, + AFMT_STEREO | AFMT_U8 | AFMT_S8 | AFMT_S16_LE | AFMT_U16_LE, + AFMT_STEREO | AFMT_S16_LE +}; + +static pcmchan_caps tr_playcaps = { + 4000, 48000, + AFMT_STEREO | AFMT_U8 | AFMT_S8 | AFMT_S16_LE | AFMT_U16_LE, + AFMT_U16_LE +}; + +static pcm_channel tr_chantemplate = { + trchan_init, + trchan_setdir, + trchan_setformat, + trchan_setspeed, + trchan_setblocksize, + trchan_trigger, + trchan_getptr, + trchan_getcaps, +}; + +/* -------------------------------------------------------------------- */ + +static u_int32_t +tr_fmttobits(u_int32_t fmt) +{ + u_int32_t bits = 0; + bits |= (fmt & AFMT_STEREO)? 0x4 : 0; + bits |= (fmt & (AFMT_S8 | AFMT_S16_LE))? 0x2 : 0; + bits |= (fmt & (AFMT_S16_LE | AFMT_U16_LE))? 0x8 : 0; + return bits; +} + +/* Hardware */ + +static u_int32_t +tr_rd(struct tr_info *tr, int regno, int size) +{ + switch(size) { + case 1: + return bus_space_read_1(tr->st, tr->sh, regno); + case 2: + return bus_space_read_2(tr->st, tr->sh, regno); + case 4: + return bus_space_read_4(tr->st, tr->sh, regno); + default: + return 0xffffffff; + } +} + +static void +tr_wr(struct tr_info *tr, int regno, u_int32_t data, int size) +{ + switch(size) { + case 1: + bus_space_write_1(tr->st, tr->sh, regno, data); + break; + case 2: + bus_space_write_2(tr->st, tr->sh, regno, data); + break; + case 4: + bus_space_write_4(tr->st, tr->sh, regno, data); + break; + } +} + +/* ac97 codec */ + +static u_int32_t +tr_rdcd(void *devinfo, int regno) +{ + struct tr_info *tr = (struct tr_info *)devinfo; + int i, j, treg, trw; + + switch (tr->type) { + case TDX_PCI_ID: + treg=TDX_REG_CODECRD; + trw=TDX_CDC_RWSTAT; + break; + case TNX_PCI_ID: + treg=(regno & 0x100)? TNX_REG_CODEC2RD : TNX_REG_CODEC1RD; + trw=TNX_CDC_RWSTAT; + break; + default: + printf("!!! tr_rdcd defaulted !!!\n"); + return 0xffffffff; + } + + regno &= 0x7f; + tr_wr(tr, treg, regno | trw, 4); + j=trw; + for (i=TR_TIMEOUT_CDC; (i > 0) && (j & trw); i--) j=tr_rd(tr, treg, 4); + if (i == 0) printf("codec timeout during read of register %x\n", regno); + return (j >> TR_CDC_DATA) & 0xffff; +} + +static void +tr_wrcd(void *devinfo, int regno, u_int32_t data) +{ + struct tr_info *tr = (struct tr_info *)devinfo; + int i, j, treg, trw; + + switch (tr->type) { + case TDX_PCI_ID: + treg=TDX_REG_CODECWR; + trw=TDX_CDC_RWSTAT; + break; + case TNX_PCI_ID: + treg=TNX_REG_CODECWR; + trw=TNX_CDC_RWSTAT | ((regno & 0x100)? TNX_CDC_SEC : 0); + break; + default: + printf("!!! tr_wrcd defaulted !!!"); + return; + } + + regno &= 0x7f; +#if 0 + printf("tr_wrcd: reg %x was %x", regno, tr_rdcd(devinfo, regno)); +#endif + j=trw; + for (i=TR_TIMEOUT_CDC; (i>0) && (j & trw); i--) j=tr_rd(tr, treg, 4); + tr_wr(tr, treg, (data << TR_CDC_DATA) | regno | trw, 4); +#if 0 + printf(" - wrote %x, now %x\n", data, tr_rdcd(devinfo, regno)); +#endif + if (i==0) printf("codec timeout writing %x, data %x\n", regno, data); +} + +/* playback channel interrupts */ + +static u_int32_t +tr_testint(struct tr_info *tr, char channel) +{ + return tr_rd(tr, (channel & 0x20)? TR_REG_ADDRINTB : TR_REG_ADDRINTA, + 4) & (1<<(channel & 0x1f)); +} + +static void +tr_clrint(struct tr_info *tr, char channel) +{ + tr_wr(tr, (channel & 0x20)? TR_REG_ADDRINTB : TR_REG_ADDRINTA, + 1<<(channel & 0x1f), 4); +} + +static void +tr_enaint(struct tr_info *tr, char channel, int enable) +{ + u_int32_t reg = (channel & 0x20)? TR_REG_INTENB : TR_REG_INTENA; + u_int32_t i = tr_rd(tr, reg, 4); + channel &= 0x1f; + i &= ~(1 << channel); + i |= (enable? 1 : 0) << channel; + tr_clrint(tr, channel); + tr_wr(tr, reg, i, 4); +} + +/* playback channels */ + +static void +tr_selch(struct tr_info *tr, char channel) +{ + int i=tr_rd(tr, TR_REG_CIR, 4); + i &= ~TR_CIR_MASK; + i |= channel & 0x3f; + tr_wr(tr, TR_REG_CIR, i, 4); +} + +static void +tr_startch(struct tr_info *tr, char channel) +{ + tr_wr(tr, (channel & 0x20)? TR_REG_STARTB : TR_REG_STARTA, + 1<<(channel & 0x1f), 4); +} + +static void +tr_stopch(struct tr_info *tr, char channel) +{ + tr_wr(tr, (channel & 0x20)? TR_REG_STOPB : TR_REG_STOPA, + 1<<(channel & 0x1f), 4); +} + +static void +tr_wrch(struct tr_info *tr, char channel, struct tr_chinfo *ch) +{ + u_int32_t cr[TR_CHN_REGS], i; + + ch->gvsel &= 0x00000001; + ch->fmc &= 0x00000003; + ch->fms &= 0x0000000f; + ch->ctrl &= 0x0000000f; + ch->pan &= 0x0000007f; + ch->rvol &= 0x0000007f; + ch->cvol &= 0x0000007f; + ch->vol &= 0x000000ff; + ch->ec &= 0x00000fff; + ch->alpha &= 0x00000fff; + ch->delta &= 0x0000ffff; + ch->lba &= 0x3fffffff; + + cr[1]=ch->lba; + cr[3]=(ch->rvol<<7) | (ch->cvol); + cr[4]=(ch->gvsel<<31)|(ch->pan<<24)|(ch->vol<<16)|(ch->ctrl<<12)|(ch->ec); + + switch (tr->type) { + case TDX_PCI_ID: + ch->cso &= 0x0000ffff; + ch->eso &= 0x0000ffff; + cr[0]=(ch->cso<<16) | (ch->alpha<<4) | (ch->fms); + cr[2]=(ch->eso<<16) | (ch->delta); + cr[3]|=0x0000c000; + break; + case TNX_PCI_ID: + ch->cso &= 0x00ffffff; + ch->eso &= 0x00ffffff; + cr[0]=((ch->delta & 0xff)<<24) | (ch->cso); + cr[2]=((ch->delta>>16)<<24) | (ch->eso); + cr[3]|=(ch->alpha<<20) | (ch->fms<<16) | (ch->fmc<<14); + break; + } + tr_selch(tr, channel); + for (i=0; i<TR_CHN_REGS; i++) + tr_wr(tr, TR_REG_CHNBASE+(i<<2), cr[i], 4); +} + +static void +tr_rdch(struct tr_info *tr, char channel, struct tr_chinfo *ch) +{ + u_int32_t cr[5], i; + tr_selch(tr, channel); + for (i=0; i<5; i++) cr[i]=tr_rd(tr, TR_REG_CHNBASE+(i<<2), 4); + ch->lba= (cr[1] & 0x3fffffff); + ch->fmc= (cr[3] & 0x0000c000) >> 14; + ch->rvol= (cr[3] & 0x00003f80) >> 7; + ch->cvol= (cr[3] & 0x0000007f); + ch->gvsel= (cr[4] & 0x80000000) >> 31; + ch->pan= (cr[4] & 0x7f000000) >> 24; + ch->vol= (cr[4] & 0x00ff0000) >> 16; + ch->ctrl= (cr[4] & 0x0000f000) >> 12; + ch->ec= (cr[4] & 0x00000fff); + switch(tr->type) { + case TDX_PCI_ID: + ch->cso= (cr[0] & 0xffff0000) >> 16; + ch->alpha= (cr[0] & 0x0000fff0) >> 4; + ch->fms= (cr[0] & 0x0000000f); + ch->eso= (cr[2] & 0xffff0000) >> 16; + ch->delta= (cr[2] & 0x0000ffff); + break; + case TNX_PCI_ID: + ch->cso= (cr[0] & 0x00ffffff); + ch->eso= (cr[2] & 0x00ffffff); + ch->delta= ((cr[2] & 0xff000000) >> 16) | + ((cr[0] & 0xff000000) >> 24); + ch->alpha= (cr[3] & 0xfff00000) >> 20; + ch->fms= (cr[3] & 0x000f0000) >> 16; + break; + } +} + +/* channel interface */ + +void * +trchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) +{ + struct tr_info *tr = devinfo; + struct tr_chinfo *ch; + if (dir == PCMDIR_PLAY) { + ch = &tr->chinfo[tr->playchns]; + ch->index = tr->playchns++; + } else { + ch = &tr->recchinfo; + ch->index = -1; + } + ch->buffer = b; + ch->buffer->bufsize = TR_BUFFSIZE; + ch->parent = tr; + ch->channel = c; + if (chn_allocbuf(ch->buffer, tr->parent_dmat) == -1) return NULL; + else return ch; +} + +static int +trchan_setdir(void *data, int dir) +{ + struct tr_chinfo *ch = data; + struct tr_info *tr = ch->parent; + if (dir == PCMDIR_PLAY && ch->index >= 0) { + ch->fmc = ch->fms = ch->ec = ch->alpha = 0; + ch->lba = vtophys(ch->buffer->buf); + ch->cso = 0; + ch->eso = ch->buffer->bufsize - 1; + ch->rvol = ch->cvol = 0; + ch->gvsel = 0; + ch->pan = 0; + ch->vol = 0; + ch->ctrl = 0x01; + ch->delta = 0; + tr_wrch(tr, ch->index, ch); + tr_enaint(tr, ch->index, 1); + } else if (dir == PCMDIR_REC && ch->index == -1) { + /* set up dma mode regs */ + u_int32_t i; + tr_wr(tr, TR_REG_DMAR15, 0, 1); + i = tr_rd(tr, TR_REG_DMAR11, 1) & 0x03; + tr_wr(tr, TR_REG_DMAR11, i | 0x54, 1); + /* set up base address */ + tr_wr(tr, TR_REG_DMAR0, vtophys(ch->buffer->buf), 4); + /* set up buffer size */ + i = tr_rd(tr, TR_REG_DMAR4, 4) & ~0x00ffffff; + tr_wr(tr, TR_REG_DMAR4, i | (ch->buffer->bufsize - 1), 4); + } else return -1; + return 0; +} + +static int +trchan_setformat(void *data, u_int32_t format) +{ + struct tr_chinfo *ch = data; + struct tr_info *tr = ch->parent; + u_int32_t bits = tr_fmttobits(format); + + if (ch->index >= 0) { + tr_rdch(tr, ch->index, ch); + ch->eso = (ch->buffer->bufsize / ch->buffer->sample_size) - 1; + ch->ctrl = bits | 0x01; + tr_wrch(tr, ch->index, ch); + } else { + u_int32_t i; + /* set # of samples between interrupts */ + i = (TR_INTSAMPLES >> ((bits & 0x08)? 1 : 0)) - 1; + tr_wr(tr, TR_REG_SBBL, i | (i << 16), 4); + /* set sample format */ + i = 0x18 | (bits << 4); + tr_wr(tr, TR_REG_SBCTRL, i, 1); + } + return 0; +} + +static int +trchan_setspeed(void *data, u_int32_t speed) +{ + struct tr_chinfo *ch = data; + struct tr_info *tr = ch->parent; + + if (ch->index >= 0) { + tr_rdch(tr, ch->index, ch); + ch->delta = (speed << 12) / 48000; + tr_wrch(tr, ch->index, ch); + return (ch->delta * 48000) >> 12; + } else { + /* setup speed */ + ch->delta = (48000 << 12) / speed; + tr_wr(tr, TR_REG_SBDELTA, ch->delta, 2); + return (48000 << 12) / ch->delta; + } + return 0; +} + +static int +trchan_setblocksize(void *data, u_int32_t blocksize) +{ + struct tr_chinfo *ch = data; + return ch->buffer->bufsize / 2; +} + +static int +trchan_trigger(void *data, int go) +{ + struct tr_chinfo *ch = data; + struct tr_info *tr = ch->parent; + if (ch->index >= 0) { + if (go == PCMTRIG_START) tr_startch(tr, ch->index); + else tr_stopch(tr, ch->index); + } else { + u_int32_t i = tr_rd(tr, TR_REG_SBCTRL, 1) & ~7; + tr_wr(tr, TR_REG_SBCTRL, i | (go == PCMTRIG_START)? 1 : 0, 1); + } + return 0; +} + +static int +trchan_getptr(void *data) +{ + struct tr_chinfo *ch = data; + struct tr_info *tr = ch->parent; + if (ch->index >= 0) { + tr_rdch(tr, ch->index, ch); + return ch->cso * ch->buffer->sample_size; + } else return tr_rd(tr, TR_REG_DMAR0, 4) - vtophys(ch->buffer->buf); +} + +static pcmchan_caps * +trchan_getcaps(void *data) +{ + struct tr_chinfo *ch = data; + return (ch->index >= 0)? &tr_playcaps : &tr_reccaps; +} + +/* The interrupt handler */ + +static void +tr_intr(void *p) +{ + struct tr_info *tr = (struct tr_info *)p; + u_int32_t intsrc = tr_rd(tr, TR_REG_MISCINT, 4); + + if (intsrc & TR_INT_ADDR) { + int i; + for (i = 0; i < tr->playchns; i++) { + if (tr_testint(tr, i)) { + chn_intr(tr->chinfo[i].channel); + tr_clrint(tr, i); + } + } + } + if (intsrc & TR_INT_SB) { + chn_intr(tr->recchinfo.channel); + tr_rd(tr, TR_REG_SBR9, 1); + tr_rd(tr, TR_REG_SBR10, 1); + } +} + +/* -------------------------------------------------------------------- */ + +/* + * Probe and attach the card + */ + +static int +tr_init(struct tr_info *tr) +{ + if (tr->type == TDX_PCI_ID) { + tr_wr(tr, TDX_REG_CODECST, TDX_CDC_ON, 4); + } else tr_wr(tr, TNX_REG_CODECST, TNX_CDC_ON, 4); + + tr_wr(tr, TR_REG_CIR, TR_CIR_MIDENA | TR_CIR_ADDRENA, 4); + tr->playchns = 0; + return 0; +} + +static int +tr_pci_probe(device_t dev) +{ + if (pci_get_devid(dev) == TDX_PCI_ID) { + device_set_desc(dev, "Trident 4DWave DX"); + return 0; + } + if (pci_get_devid(dev) == TNX_PCI_ID) { + device_set_desc(dev, "Trident 4DWave NX"); + return 0; + } + + return ENXIO; +} + +static int +tr_pci_attach(device_t dev) +{ + snddev_info *d; + u_int32_t data; + struct tr_info *tr; + struct ac97_info *codec; + int i; + int mapped; + char status[SND_STATUSLEN]; + + d = device_get_softc(dev); + if ((tr = malloc(sizeof(*tr), M_DEVBUF, M_NOWAIT)) == NULL) { + device_printf(dev, "cannot allocate softc\n"); + return ENXIO; + } + + bzero(tr, sizeof(*tr)); + tr->type = pci_get_devid(dev); + + data = pci_read_config(dev, PCIR_COMMAND, 2); + data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN); + pci_write_config(dev, PCIR_COMMAND, data, 2); + data = pci_read_config(dev, PCIR_COMMAND, 2); + + mapped = 0; + /* XXX dfr: is this strictly necessary? */ + for (i = 0; (mapped == 0) && (i < PCI_MAXMAPS_0); i++) { + tr->regid = PCIR_MAPS + i*4; + tr->regtype = SYS_RES_MEMORY; + tr->reg = bus_alloc_resource(dev, tr->regtype, &tr->regid, + 0, ~0, 1, RF_ACTIVE); + if (!tr->reg) { + tr->regtype = SYS_RES_IOPORT; + tr->reg = bus_alloc_resource(dev, tr->regtype, + &tr->regid, 0, ~0, 1, + RF_ACTIVE); + } + if (tr->reg) { + tr->st = rman_get_bustag(tr->reg); + tr->sh = rman_get_bushandle(tr->reg); + mapped++; + } + } + + if (mapped == 0) { + device_printf(dev, "unable to map register space\n"); + goto bad; + } + + if (tr_init(tr) == -1) { + device_printf(dev, "unable to initialize the card\n"); + goto bad; + } + + codec = ac97_create(tr, tr_rdcd, tr_wrcd); + if (codec == NULL) goto bad; + mixer_init(d, &ac97_mixer, codec); + + tr->irqid = 0; + tr->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &tr->irqid, + 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); + if (!tr->irq || + bus_setup_intr(dev, tr->irq, INTR_TYPE_TTY, tr_intr, tr, &tr->ih)) { + device_printf(dev, "unable to map interrupt\n"); + goto bad; + } + + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/TR_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, + /*flags*/0, &tr->parent_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto bad; + } + + snprintf(status, 64, "at %s 0x%lx irq %ld", + (tr->regtype == SYS_RES_IOPORT)? "io" : "memory", + rman_get_start(tr->reg), rman_get_start(tr->irq)); + + if (pcm_register(dev, tr, TR_MAXPLAYCH, 1)) goto bad; + pcm_addchan(dev, PCMDIR_REC, &tr_chantemplate, tr); + for (i = 0; i < TR_MAXPLAYCH; i++) + pcm_addchan(dev, PCMDIR_PLAY, &tr_chantemplate, tr); + pcm_setstatus(dev, status); + + return 0; + +bad: + if (tr->reg) bus_release_resource(dev, tr->regtype, tr->regid, tr->reg); + if (tr->ih) bus_teardown_intr(dev, tr->irq, tr->ih); + if (tr->irq) bus_release_resource(dev, SYS_RES_IRQ, tr->irqid, tr->irq); + free(tr, M_DEVBUF); + return ENXIO; +} + +static device_method_t tr_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, tr_pci_probe), + DEVMETHOD(device_attach, tr_pci_attach), + + { 0, 0 } +}; + +static driver_t tr_driver = { + "pcm", + tr_methods, + sizeof(snddev_info), +}; + +static devclass_t pcm_devclass; + +DRIVER_MODULE(tr, pci, tr_driver, pcm_devclass, 0, 0); + +#endif /* NPCI != 0 */ diff --git a/sys/dev/pcm/pci/t4dwave.h b/sys/dev/pcm/pci/t4dwave.h new file mode 100644 index 0000000..31f3340 --- /dev/null +++ b/sys/dev/pcm/pci/t4dwave.h @@ -0,0 +1,103 @@ +/* + * 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$ + */ + +#ifndef _T4DWAVE_REG_H +#define _T4DWAVE_REG_H + +#define TDX_PCI_ID 0x20001023 +#define TNX_PCI_ID 0x20011023 + +#define TR_BUFFSIZE 0x8000 +#define TR_TIMEOUT_CDC 0xffff +#define TR_INTSAMPLES 0x2000 +#define TR_MAXPLAYCH 64 + +#define TR_REG_CIR 0xa0 +#define TR_CIR_MASK 0x0000003f +#define TR_CIR_ADDRENA 0x00001000 +#define TR_CIR_MIDENA 0x00002000 +#define TR_REG_MISCINT 0xb0 +#define TR_INT_ADDR 0x00000020 +#define TR_INT_SB 0x00000004 + +#define TR_REG_DMAR0 0x00 +#define TR_REG_DMAR4 0x04 +#define TR_REG_DMAR11 0x0b +#define TR_REG_DMAR15 0x0f +#define TR_REG_SBR4 0x14 +#define TR_REG_SBR5 0x15 +#define TR_SB_INTSTATUS 0x82 +#define TR_REG_SBR9 0x1e +#define TR_REG_SBR10 0x1f +#define TR_REG_SBBL 0xc0 +#define TR_REG_SBCTRL 0xc4 +#define TR_REG_SBDELTA 0xac + +#define TR_CDC_DATA 16 +#define TDX_REG_CODECWR 0x40 +#define TDX_REG_CODECRD 0x44 +#define TDX_CDC_RWSTAT 0x00008000 +#define TDX_REG_CODECST 0x48 +#define TDX_CDC_SBCTRL 0x40 +#define TDX_CDC_ACTIVE 0x20 +#define TDX_CDC_READY 0x10 +#define TDX_CDC_ADCON 0x08 +#define TDX_CDC_DACON 0x02 +#define TDX_CDC_RESET 0x01 +#define TDX_CDC_ON (TDX_CDC_ADCON|TDX_CDC_DACON) + +#define TNX_REG_CODECWR 0x44 +#define TNX_REG_CODEC1RD 0x48 +#define TNX_REG_CODEC2RD 0x4c +#define TNX_CDC_RWSTAT 0x00000c00 +#define TNX_CDC_SEC 0x00000100 +#define TNX_REG_CODECST 0x40 +#define TNX_CDC_READY2 0x40 +#define TNX_CDC_ADC2ON 0x20 +#define TNX_CDC_DAC2ON 0x10 +#define TNX_CDC_READY1 0x08 +#define TNX_CDC_ADC1ON 0x04 +#define TNX_CDC_DAC1ON 0x02 +#define TNX_CDC_RESET 0x01 +#define TNX_CDC_ON (TNX_CDC_ADC1ON|TNX_CDC_DAC1ON) + + +#define TR_REG_STARTA 0x80 +#define TR_REG_STOPA 0x84 +#define TR_REG_ADDRINTA 0x98 +#define TR_REG_INTENA 0xa4 + +#define TR_REG_STARTB 0xb4 +#define TR_REG_STOPB 0xb8 +#define TR_REG_ADDRINTB 0xd8 +#define TR_REG_INTENB 0xdc + +#define TR_REG_CHNBASE 0xe0 +#define TR_CHN_REGS 5 + +#endif diff --git a/sys/dev/pcm/sound.c b/sys/dev/pcm/sound.c new file mode 100644 index 0000000..102b87a --- /dev/null +++ b/sys/dev/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/pcm/sound.h b/sys/dev/pcm/sound.h new file mode 100644 index 0000000..5d17624 --- /dev/null +++ b/sys/dev/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 diff --git a/sys/dev/sound/pci/aureal.c b/sys/dev/sound/pci/aureal.c new file mode 100644 index 0000000..49900e6 --- /dev/null +++ b/sys/dev/sound/pci/aureal.c @@ -0,0 +1,693 @@ +/* + * 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 "pci.h" +#include "pcm.h" + +#include <dev/pcm/sound.h> +#include <dev/pcm/ac97.h> +#include <dev/pcm/pci/aureal.h> + +#include <pci/pcireg.h> +#include <pci/pcivar.h> + +#if NPCI != 0 + +/* PCI IDs of supported chips */ +#define AU8820_PCI_ID 0x000112eb + +/* channel interface */ +static void *auchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); +static int auchan_setdir(void *data, int dir); +static int auchan_setformat(void *data, u_int32_t format); +static int auchan_setspeed(void *data, u_int32_t speed); +static int auchan_setblocksize(void *data, u_int32_t blocksize); +static int auchan_trigger(void *data, int go); +static int auchan_getptr(void *data); +static pcmchan_caps *auchan_getcaps(void *data); + +static pcmchan_caps au_playcaps = { + 4000, 48000, + AFMT_STEREO | AFMT_U8 | AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE +}; + +static pcmchan_caps au_reccaps = { + 4000, 48000, + AFMT_STEREO | AFMT_U8 | AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE +}; + +static pcm_channel au_chantemplate = { + auchan_init, + auchan_setdir, + auchan_setformat, + auchan_setspeed, + auchan_setblocksize, + auchan_trigger, + auchan_getptr, + auchan_getcaps, +}; + +/* -------------------------------------------------------------------- */ + +static u_int32_t au_rdcd(void *arg, int regno); +static void au_wrcd(void *arg, int regno, u_int32_t data); + +struct au_info; + +struct au_chinfo { + struct au_info *parent; + pcm_channel *channel; + snd_dbuf *buffer; + int dir; +}; + +struct au_info { + int unit; + + bus_space_tag_t st[3]; + bus_space_handle_t sh[3]; + + bus_dma_tag_t parent_dmat; + + u_int32_t x[32], y[128]; + char z[128]; + u_int32_t routes[4], interrupts; + struct au_chinfo pch; +}; + +static int au_init(device_t dev, struct au_info *au); +static void au_intr(void *); + +/* -------------------------------------------------------------------- */ + +static u_int32_t +au_rd(struct au_info *au, int mapno, int regno, int size) +{ + switch(size) { + case 1: + return bus_space_read_1(au->st[mapno], au->sh[mapno], regno); + case 2: + return bus_space_read_2(au->st[mapno], au->sh[mapno], regno); + case 4: + return bus_space_read_4(au->st[mapno], au->sh[mapno], regno); + default: + return 0xffffffff; + } +} + +static void +au_wr(struct au_info *au, int mapno, int regno, u_int32_t data, int size) +{ + switch(size) { + case 1: + bus_space_write_1(au->st[mapno], au->sh[mapno], regno, data); + break; + case 2: + bus_space_write_2(au->st[mapno], au->sh[mapno], regno, data); + break; + case 4: + bus_space_write_4(au->st[mapno], au->sh[mapno], regno, data); + break; + } +} + +static u_int32_t +au_rdcd(void *arg, int regno) +{ + struct au_info *au = (struct au_info *)arg; + int i=0, j=0; + + regno<<=16; + au_wr(au, 0, AU_REG_CODECIO, regno, 4); + while (j<50) { + i=au_rd(au, 0, AU_REG_CODECIO, 4); + if ((i & 0x00ff0000) == (regno | 0x00800000)) break; + DELAY(j * 200 + 2000); + j++; + } + if (j==50) printf("pcm%d: codec timeout reading register %x (%x)\n", + au->unit, (regno & AU_CDC_REGMASK)>>16, i); + return i & AU_CDC_DATAMASK; +} + +static void +au_wrcd(void *arg, int regno, u_int32_t data) +{ + struct au_info *au = (struct au_info *)arg; + int i, j, tries; + i=j=tries=0; + do { + while (j<50 && (i & AU_CDC_WROK) == 0) { + i=au_rd(au, 0, AU_REG_CODECST, 4); + DELAY(2000); + j++; + } + if (j==50) printf("codec timeout during write of register %x, data %x\n", + regno, data); + au_wr(au, 0, AU_REG_CODECIO, (regno<<16) | AU_CDC_REGSET | data, 4); +/* DELAY(20000); + i=au_rdcd(au, regno); +*/ tries++; + } while (0); /* (i != data && tries < 3); */ + /* + if (tries == 3) printf("giving up writing 0x%4x to codec reg %2x\n", data, regno); + */ +} + +static void +au_setbit(u_int32_t *p, char bit, u_int32_t value) +{ + p += bit >> 5; + bit &= 0x1f; + *p &= ~ (1 << bit); + *p |= (value << bit); +} + +static void +au_addroute(struct au_info *au, int a, int b, int route) +{ + int j = 0x1099c+(a<<2); + if (au->x[a] != a+0x67) j = AU_REG_RTBASE+(au->x[a]<<2); + + au_wr(au, 0, AU_REG_RTBASE+(route<<2), 0xffffffff, 4); + au_wr(au, 0, j, route | (b<<7), 4); + au->y[route]=au->x[a]; + au->x[a]=route; + au->z[route]=a & 0x000000ff; + au_setbit(au->routes, route, 1); +} + +static void +au_delroute(struct au_info *au, int route) +{ + int i; + int j=au->z[route]; + + au_setbit(au->routes, route, 0); + au->z[route]=0x1f; + i=au_rd(au, 0, AU_REG_RTBASE+(route<<2), 4); + au_wr(au, 0, AU_REG_RTBASE+(au->y[route]<<2), i, 4); + au->y[i & 0x7f]=au->y[route]; + au_wr(au, 0, AU_REG_RTBASE+(route<<2), 0xfffffffe, 4); + if (au->x[j] == route) au->x[j]=au->y[route]; + au->y[route]=0x7f; +} + +static void +au_encodec(struct au_info *au, char channel) +{ + au_wr(au, 0, AU_REG_CODECEN, + au_rd(au, 0, AU_REG_CODECEN, 4) | (1 << (channel + 8)), 4); +} + +static void +au_clrfifo(struct au_info *au, u_int32_t c) +{ + u_int32_t i; + + for (i=0; i<32; i++) au_wr(au, 0, AU_REG_FIFOBASE+(c<<7)+(i<<2), 0, 4); +} + +static void +au_setadb(struct au_info *au, u_int32_t c, u_int32_t enable) +{ + int x; + + x = au_rd(au, 0, AU_REG_ADB, 4); + x &= ~(1 << c); + x |= (enable << c); + au_wr(au, 0, AU_REG_ADB, x, 4); +} + +static void +au_prepareoutput(struct au_chinfo *ch, u_int32_t format) +{ + struct au_info *au = ch->parent; + int i, stereo = (format & AFMT_STEREO)? 1 : 0; + u_int32_t baseaddr = vtophys(ch->buffer->buf); + + au_wr(au, 0, 0x1061c, 0, 4); + au_wr(au, 0, 0x10620, 0, 4); + au_wr(au, 0, 0x10624, 0, 4); + switch(format & ~AFMT_STEREO) { + case 1: + i=0xb000; + break; + case 2: + i=0xf000; + break; + case 8: + i=0x7000; + break; + case 16: + i=0x23000; + break; + default: + i=0x3000; + } + au_wr(au, 0, 0x10200, baseaddr, 4); + au_wr(au, 0, 0x10204, baseaddr+0x1000, 4); + au_wr(au, 0, 0x10208, baseaddr+0x2000, 4); + au_wr(au, 0, 0x1020c, baseaddr+0x3000, 4); + + au_wr(au, 0, 0x10400, 0xdeffffff, 4); + au_wr(au, 0, 0x10404, 0xfcffffff, 4); + + au_wr(au, 0, 0x10580, i, 4); + + au_wr(au, 0, 0x10210, baseaddr, 4); + au_wr(au, 0, 0x10214, baseaddr+0x1000, 4); + au_wr(au, 0, 0x10218, baseaddr+0x2000, 4); + au_wr(au, 0, 0x1021c, baseaddr+0x3000, 4); + + au_wr(au, 0, 0x10408, 0x00fff000 | 0x56000000 | 0x00000fff, 4); + au_wr(au, 0, 0x1040c, 0x00fff000 | 0x74000000 | 0x00000fff, 4); + + au_wr(au, 0, 0x10584, i, 4); + + au_wr(au, 0, 0x0f800, stereo? 0x00030032 : 0x00030030, 4); + au_wr(au, 0, 0x0f804, stereo? 0x00030032 : 0x00030030, 4); + + au_addroute(au, 0x11, 0, 0x58); + au_addroute(au, 0x11, stereo? 0 : 1, 0x59); +} + +/* channel interface */ +static void * +auchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) +{ + struct au_info *au = devinfo; + struct au_chinfo *ch = (dir == PCMDIR_PLAY)? &au->pch : NULL; + + ch->parent = au; + ch->channel = c; + ch->buffer = b; + ch->buffer->bufsize = AU_BUFFSIZE; + if (chn_allocbuf(ch->buffer, au->parent_dmat) == -1) return NULL; + return ch; +} + +static int +auchan_setdir(void *data, int dir) +{ + struct au_chinfo *ch = data; + if (dir == PCMDIR_PLAY) { + } else { + } + ch->dir = dir; + return 0; +} + +static int +auchan_setformat(void *data, u_int32_t format) +{ + struct au_chinfo *ch = data; + + if (ch->dir == PCMDIR_PLAY) au_prepareoutput(ch, format); + return 0; +} + +static int +auchan_setspeed(void *data, u_int32_t speed) +{ + struct au_chinfo *ch = data; + if (ch->dir == PCMDIR_PLAY) { + } else { + } + return speed; +} + +static int +auchan_setblocksize(void *data, u_int32_t blocksize) +{ + return blocksize; +} + +static int +auchan_trigger(void *data, int go) +{ + struct au_chinfo *ch = data; + struct au_info *au = ch->parent; + if (ch->dir == PCMDIR_PLAY) { + au_setadb(au, 0x11, (go)? 1 : 0); + if (!go) { + au_wr(au, 0, 0xf800, 0, 4); + au_wr(au, 0, 0xf804, 0, 4); + au_delroute(au, 0x58); + au_delroute(au, 0x59); + } + } else { + } + return 0; +} + +static int +auchan_getptr(void *data) +{ + struct au_chinfo *ch = data; + struct au_info *au = ch->parent; + if (ch->dir == PCMDIR_PLAY) { + return au_rd(au, 0, AU_REG_UNK2, 4) & (AU_BUFFSIZE-1); + } else { + return 0; + } +} + +static pcmchan_caps * +auchan_getcaps(void *data) +{ + struct au_chinfo *ch = data; + return (ch->dir == PCMDIR_PLAY)? &au_playcaps : &au_reccaps; +} + +/* The interrupt handler */ +static void +au_intr (void *p) +{ + struct au_info *au = p; + u_int32_t intsrc, i; + + au->interrupts++; + intsrc=au_rd(au, 0, AU_REG_IRQSRC, 4); + printf("pcm%d: interrupt with src %x\n", au->unit, intsrc); + if (intsrc & AU_IRQ_FATAL) printf("pcm%d: fatal error irq\n", au->unit); + if (intsrc & AU_IRQ_PARITY) printf("pcm%d: parity error irq\n", au->unit); + if (intsrc & AU_IRQ_UNKNOWN) { + (void)au_rd(au, 0, AU_REG_UNK1, 4); + au_wr(au, 0, AU_REG_UNK1, 0, 4); + au_wr(au, 0, AU_REG_UNK1, 0x10000, 4); + } + if (intsrc & AU_IRQ_PCMOUT) { + i=au_rd(au, 0, AU_REG_UNK2, 4) & (AU_BUFFSIZE-1); + chn_intr(au->pch.channel); + (void)au_rd(au, 0, AU_REG_UNK3, 4); + (void)au_rd(au, 0, AU_REG_UNK4, 4); + (void)au_rd(au, 0, AU_REG_UNK5, 4); + } +/* don't support midi + if (intsrc & AU_IRQ_MIDI) { + i=au_rd(au, 0, 0x11004, 4); + j=10; + while (i & 0xff) { + if (j-- <= 0) break; + i=au_rd(au, 0, 0x11000, 4); + if ((au->midi_stat & 1) && (au->midi_out)) + au->midi_out(au->midi_devno, i); + i=au_rd(au, 0, 0x11004); + } + } +*/ + au_wr(au, 0, AU_REG_IRQSRC, intsrc & 0x7ff, 4); + au_rd(au, 0, AU_REG_IRQSRC, 4); +} + + +/* -------------------------------------------------------------------- */ + +/* Probe and attach the card */ + +static int +au_init(device_t dev, struct au_info *au) +{ + u_int32_t i, j; + + au_wr(au, 0, AU_REG_IRQGLOB, 0xffffffff, 4); + DELAY(100000); + + /* init codec */ + /* cold reset */ + for (i=0; i<32; i++) { + au_wr(au, 0, AU_REG_CODECCHN+(i<<2), 0, 4); + DELAY(10000); + } + if (1) { + au_wr(au, 0, AU_REG_CODECST, 0x8068, 4); + DELAY(10000); + au_wr(au, 0, AU_REG_CODECST, 0x00e8, 4); + DELAY(10000); + } else { + au_wr(au, 0, AU_REG_CODECST, 0x00a8, 4); + DELAY(100000); + au_wr(au, 0, AU_REG_CODECST, 0x80a8, 4); + DELAY(100000); + au_wr(au, 0, AU_REG_CODECST, 0x80e8, 4); + DELAY(100000); + au_wr(au, 0, AU_REG_CODECST, 0x80a8, 4); + DELAY(100000); + au_wr(au, 0, AU_REG_CODECST, 0x00a8, 4); + DELAY(100000); + au_wr(au, 0, AU_REG_CODECST, 0x00e8, 4); + DELAY(100000); + } + + /* init */ + for (i=0; i<32; i++) { + au_wr(au, 0, AU_REG_CODECCHN+(i<<2), 0, 4); + DELAY(10000); + } + au_wr(au, 0, AU_REG_CODECST, 0xe8, 4); + DELAY(10000); + au_wr(au, 0, AU_REG_CODECEN, 0, 4); + + /* setup codec */ + i=j=0; + while (j<100 && (i & AU_CDC_READY)==0) { + i=au_rd(au, 0, AU_REG_CODECST, 4); + DELAY(1000); + j++; + } + if (j==100) device_printf(dev, "codec not ready, status 0x%x\n", i); + + /* init adb */ + /*au->x5c=0;*/ + for (i=0; i<32; i++) au->x[i]=i+0x67; + for (i=0; i<128; i++) au->y[i]=0x7f; + for (i=0; i<128; i++) au->z[i]=0x1f; + au_wr(au, 0, AU_REG_ADB, 0, 4); + for (i=0; i<124; i++) au_wr(au, 0, AU_REG_RTBASE+(i<<2), 0xffffffff, 4); + + /* test */ + i=au_rd(au, 0, 0x107c0, 4); + if (i!=0xdeadbeef) device_printf(dev, "dma check failed: 0x%x\n", i); + + /* install mixer */ + au_wr(au, 0, AU_REG_IRQGLOB, + au_rd(au, 0, AU_REG_IRQGLOB, 4) | AU_IRQ_ENABLE, 4); + /* braindead but it's what the oss/linux driver does + * for (i=0; i<0x80000000; i++) au_wr(au, 0, i<<2, 0, 4); + */ + au->routes[0]=au->routes[1]=au->routes[2]=au->routes[3]=0; + /*au->x1e4=0;*/ + + /* attach channel */ + au_addroute(au, 0x11, 0x48, 0x02); + au_addroute(au, 0x11, 0x49, 0x03); + au_encodec(au, 0); + au_encodec(au, 1); + + for (i=0; i<48; i++) au_wr(au, 0, 0xf800+(i<<2), 0x20, 4); + for (i=2; i<6; i++) au_wr(au, 0, 0xf800+(i<<2), 0, 4); + au_wr(au, 0, 0xf8c0, 0x0843, 4); + for (i=0; i<4; i++) au_clrfifo(au, i); + + return (0); +} + +static int +au_testirq(struct au_info *au) +{ + au_wr(au, 0, AU_REG_UNK1, 0x80001000, 4); + au_wr(au, 0, AU_REG_IRQEN, 0x00001030, 4); + au_wr(au, 0, AU_REG_IRQSRC, 0x000007ff, 4); + DELAY(1000000); + if (au->interrupts==0) printf("pcm%d: irq test failed\n", au->unit); + /* this apparently generates an irq */ + return 0; +} + +static int +au_pci_probe(device_t dev) +{ + if (pci_get_devid(dev) == AU8820_PCI_ID) { + device_set_desc(dev, "Aureal Vortex 8820"); + return 0; + } + + return ENXIO; +} + +static int +au_pci_attach(device_t dev) +{ + snddev_info *d; + u_int32_t data; + struct au_info *au; + int type[10]; + int regid[10]; + struct resource *reg[10]; + int i, j, mapped = 0; + int irqid; + struct resource *irq = 0; + void *ih = 0; + struct ac97_info *codec; + char status[SND_STATUSLEN]; + + d = device_get_softc(dev); + if ((au = malloc(sizeof(*au), M_DEVBUF, M_NOWAIT)) == NULL) { + device_printf(dev, "cannot allocate softc\n"); + return ENXIO; + } + + bzero(au, sizeof(*au)); + au->unit = device_get_unit(dev); + + data = pci_read_config(dev, PCIR_COMMAND, 2); + data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN); + pci_write_config(dev, PCIR_COMMAND, data, 2); + data = pci_read_config(dev, PCIR_COMMAND, 2); + + j=0; + /* XXX dfr: is this strictly necessary? */ + for (i=0; i<PCI_MAXMAPS_0; i++) { +#if 0 + /* Slapped wrist: config_id and map are private structures */ + if (bootverbose) { + printf("pcm%d: map %d - allocating ", unit, i+1); + printf("0x%x bytes of ", 1<<config_id->map[i].ln2size); + printf("%s space ", (config_id->map[i].type & PCI_MAPPORT)? + "io" : "memory"); + printf("at 0x%x...", config_id->map[i].base); + } +#endif + regid[j] = PCIR_MAPS + i*4; + type[j] = SYS_RES_MEMORY; + reg[j] = bus_alloc_resource(dev, type[j], ®id[j], + 0, ~0, 1, RF_ACTIVE); + if (!reg[j]) { + type[j] = SYS_RES_IOPORT; + reg[j] = bus_alloc_resource(dev, type[j], ®id[j], + 0, ~0, 1, RF_ACTIVE); + } + if (reg[j]) { + au->st[i] = rman_get_bustag(reg[j]); + au->sh[i] = rman_get_bushandle(reg[j]); + mapped++; + } +#if 0 + if (bootverbose) printf("%s\n", mapped? "ok" : "failed"); +#endif + if (mapped) j++; + if (j == 10) { + /* XXX */ + device_printf(dev, "too many resources"); + goto bad; + } + } + +#if 0 + if (j < config_id->nummaps) { + printf("pcm%d: unable to map a required resource\n", unit); + free(au, M_DEVBUF); + return; + } +#endif + + au_wr(au, 0, AU_REG_IRQEN, 0, 4); + + irqid = 0; + irq = bus_alloc_resource(dev, SYS_RES_IRQ, &irqid, + 0, ~0, 1, RF_ACTIVE); + if (!irq + || bus_setup_intr(dev, irq, INTR_TYPE_TTY, au_intr, au, &ih)) { + device_printf(dev, "unable to map interrupt\n"); + goto bad; + } + + if (au_testirq(au)) device_printf(dev, "irq test failed\n"); + + if (au_init(dev, au) == -1) { + device_printf(dev, "unable to initialize the card\n"); + goto bad; + } + + codec = ac97_create(au, au_rdcd, au_wrcd); + if (codec == NULL) goto bad; + mixer_init(d, &ac97_mixer, codec); + + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/AU_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, + /*flags*/0, &au->parent_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto bad; + } + + snprintf(status, SND_STATUSLEN, "at %s 0x%lx irq %ld", + (type[0] == SYS_RES_IOPORT)? "io" : "memory", + rman_get_start(reg[0]), rman_get_start(irq)); + + if (pcm_register(dev, au, 1, 1)) goto bad; + /* pcm_addchan(dev, PCMDIR_REC, &au_chantemplate, au); */ + pcm_addchan(dev, PCMDIR_PLAY, &au_chantemplate, au); + pcm_setstatus(dev, status); + + return 0; + + bad: + if (au) free(au, M_DEVBUF); + for (i = 0; i < j; i++) + bus_release_resource(dev, type[i], regid[i], reg[i]); + if (ih) bus_teardown_intr(dev, irq, ih); + if (irq) bus_release_resource(dev, SYS_RES_IRQ, irqid, irq); + return ENXIO; +} + +static device_method_t au_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, au_pci_probe), + DEVMETHOD(device_attach, au_pci_attach), + + { 0, 0 } +}; + +static driver_t au_driver = { + "pcm", + au_methods, + sizeof(snddev_info), +}; + +static devclass_t pcm_devclass; + +DRIVER_MODULE(au, pci, au_driver, pcm_devclass, 0, 0); + +#endif /* NPCI != 0 */ + diff --git a/sys/dev/sound/pci/aureal.h b/sys/dev/sound/pci/aureal.h new file mode 100644 index 0000000..c5bcb29 --- /dev/null +++ b/sys/dev/sound/pci/aureal.h @@ -0,0 +1,99 @@ +/* + * 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$ + */ + +#ifndef _AU8820_REG_H +#define _AU8820_REG_H + +#define AU_BUFFSIZE 0x4000 + +#define AU_REG_FIFOBASE 0x0e000 + +#define AU_REG_UNK2 0x105c0 +#define AU_REG_UNK3 0x10600 +#define AU_REG_UNK4 0x10604 +#define AU_REG_UNK5 0x10608 + +#define AU_REG_RTBASE 0x10800 + +#define AU_REG_ADB 0x10a00 + +#define AU_REG_CODECCHN 0x11880 + +#define AU_REG_CODECST 0x11984 +#define AU_CDC_RUN 0x00000040 +#define AU_CDC_WROK 0x00000100 +#define AU_CDC_RESET 0x00008000 + +#define AU_REG_CODECIO 0x11988 +#define AU_CDC_DATAMASK 0x0000ffff +#define AU_CDC_REGMASK 0x007f0000 +#define AU_CDC_REGSET 0x00800000 +#define AU_CDC_READY 0x04000000 + +#define AU_REG_CODECEN 0x11990 +#define AU_CDC_CHAN1EN 0x00000100 +#define AU_CDC_CHAN2EN 0x00000200 + +#define AU_REG_UNK1 0x1199c + +#define AU_REG_IRQSRC 0x12800 +#define AU_IRQ_FATAL 0x0001 +#define AU_IRQ_PARITY 0x0002 +#define AU_IRQ_PCMOUT 0x0020 +#define AU_IRQ_UNKNOWN 0x1000 +#define AU_IRQ_MIDI 0x2000 +#define AU_REG_IRQEN 0x12804 + +#define AU_REG_IRQGLOB 0x1280c +#define AU_IRQ_ENABLE 0x4000 + +#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 + + +#endif diff --git a/sys/dev/sound/pci/es137x.c b/sys/dev/sound/pci/es137x.c new file mode 100644 index 0000000..f0f1413 --- /dev/null +++ b/sys/dev/sound/pci/es137x.c @@ -0,0 +1,543 @@ +/* + * Support the ENSONIQ AudioPCI board based on the ES1370 and Codec + * AK4531. + * + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> + * Copyright (c) 1998 by Joachim Kuebart. 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. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgement: + * This product includes software developed by Joachim Kuebart. + * + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``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 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: es1370.c,v 1.4 1999/05/09 17:06:45 peter Exp $ + */ + +#include "pci.h" +#include "pcm.h" + +#include <dev/pcm/sound.h> +#include <dev/pcm/pci/es1370.h> + +#include <pci/pcireg.h> +#include <pci/pcivar.h> + +#if NPCI != 0 + +#define MEM_MAP_REG 0x14 + +/* PCI IDs of supported chips */ +#define ES1370_PCI_ID 0x50001274 + +/* device private data */ +struct es_info; + +struct es_chinfo { + struct es_info *parent; + pcm_channel *channel; + snd_dbuf *buffer; + int dir; + u_int32_t fmt; +}; + +struct es_info { + bus_space_tag_t st; + bus_space_handle_t sh; + bus_dma_tag_t parent_dmat; + + /* Contents of board's registers */ + u_long ctrl; + u_long sctrl; + struct es_chinfo pch, rch; +}; + +/* -------------------------------------------------------------------- */ + +/* prototypes */ +static int es_init(struct es_info *); +static void es_intr(void *); +static int write_codec(struct es_info *, u_char, u_char); + +/* channel interface */ +static void *eschan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); +static int eschan_setdir(void *data, int dir); +static int eschan_setformat(void *data, u_int32_t format); +static int eschan_setspeed(void *data, u_int32_t speed); +static int eschan_setblocksize(void *data, u_int32_t blocksize); +static int eschan_trigger(void *data, int go); +static int eschan_getptr(void *data); +static pcmchan_caps *eschan_getcaps(void *data); + +static pcmchan_caps es_playcaps = { + 4000, 48000, + AFMT_STEREO | AFMT_U8 | AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE +}; + +static pcmchan_caps es_reccaps = { + 4000, 48000, + AFMT_STEREO | AFMT_U8 | AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE +}; + +static pcm_channel es_chantemplate = { + eschan_init, + eschan_setdir, + eschan_setformat, + eschan_setspeed, + eschan_setblocksize, + eschan_trigger, + eschan_getptr, + eschan_getcaps, +}; + +/* -------------------------------------------------------------------- */ + +/* The mixer interface */ + +static int es_mixinit(snd_mixer *m); +static int es_mixset(snd_mixer *m, unsigned dev, unsigned left, unsigned right); +static int es_mixsetrecsrc(snd_mixer *m, u_int32_t src); + +static snd_mixer es_mixer = { + "Ensoniq AudioPCI 1370 mixer", + es_mixinit, + es_mixset, + es_mixsetrecsrc, +}; + +static const struct { + unsigned volidx:4; + unsigned left:4; + unsigned right:4; + unsigned stereo:1; + unsigned recmask:13; + unsigned avail:1; +} mixtable[SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_VOLUME] = { 0, 0x0, 0x1, 1, 0x0000, 1 }, + [SOUND_MIXER_PCM] = { 1, 0x2, 0x3, 1, 0x0400, 1 }, + [SOUND_MIXER_SYNTH] = { 2, 0x4, 0x5, 1, 0x0060, 1 }, + [SOUND_MIXER_CD] = { 3, 0x6, 0x7, 1, 0x0006, 1 }, + [SOUND_MIXER_LINE] = { 4, 0x8, 0x9, 1, 0x0018, 1 }, + [SOUND_MIXER_LINE1] = { 5, 0xa, 0xb, 1, 0x1800, 1 }, + [SOUND_MIXER_LINE2] = { 6, 0xc, 0x0, 0, 0x0100, 1 }, + [SOUND_MIXER_LINE3] = { 7, 0xd, 0x0, 0, 0x0200, 1 }, + [SOUND_MIXER_MIC] = { 8, 0xe, 0x0, 0, 0x0001, 1 }, + [SOUND_MIXER_OGAIN] = { 9, 0xf, 0x0, 0, 0x0000, 1 } }; + +static int +es_mixinit(snd_mixer *m) +{ + int i; + u_int32_t v; + + v = 0; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].avail) v |= (1 << i); + mix_setdevs(m, v); + v = 0; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].recmask) v |= (1 << i); + mix_setrecdevs(m, v); + return 0; +} + +static int +es_mixset(snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + int l, r, rl, rr; + + if (!mixtable[dev].avail) return -1; + l = left; + r = mixtable[dev].stereo? right : l; + if (mixtable[dev].left == 0xf) { + rl = (l < 2)? 0x80 : 7 - (l - 2) / 14; + } else { + rl = (l < 10)? 0x80 : 15 - (l - 10) / 6; + } + if (mixtable[dev].stereo) { + rr = (r < 10)? 0x80 : 15 - (r - 10) / 6; + write_codec(mix_getdevinfo(m), mixtable[dev].right, rr); + } + write_codec(mix_getdevinfo(m), mixtable[dev].left, rl); + return l | (r << 8); +} + +static int +es_mixsetrecsrc(snd_mixer *m, u_int32_t src) +{ + int i, j = 0; + + if (src == 0) src = 1 << SOUND_MIXER_MIC; + src &= mix_getrecdevs(m); + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if ((src & (1 << i)) != 0) j |= mixtable[i].recmask; + + write_codec(mix_getdevinfo(m), CODEC_LIMIX1, j & 0x55); + write_codec(mix_getdevinfo(m), CODEC_RIMIX1, j & 0xaa); + write_codec(mix_getdevinfo(m), CODEC_LIMIX2, (j >> 8) & 0x17); + write_codec(mix_getdevinfo(m), CODEC_RIMIX2, (j >> 8) & 0x0f); + write_codec(mix_getdevinfo(m), CODEC_OMIX1, 0x7f); + write_codec(mix_getdevinfo(m), CODEC_OMIX2, 0x3f); + return src; +} + +static int +write_codec(struct es_info *es, u_char i, u_char data) +{ + int wait = 100; /* 100 msec timeout */ + + do { + if ((bus_space_read_4(es->st, es->sh, ES1370_REG_STATUS) & + STAT_CSTAT) == 0) { + bus_space_write_2(es->st, es->sh, ES1370_REG_CODEC, + ((u_short)i << CODEC_INDEX_SHIFT) | data); + return 0; + } + DELAY(1000); + } while (--wait); + printf("pcm: write_codec timed out\n"); + return -1; +} + +/* -------------------------------------------------------------------- */ + +/* channel interface */ +static void * +eschan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) +{ + struct es_info *es = devinfo; + struct es_chinfo *ch = (dir == PCMDIR_PLAY)? &es->pch : &es->rch; + + ch->parent = es; + ch->channel = c; + ch->buffer = b; + ch->buffer->bufsize = ES_BUFFSIZE; + if (chn_allocbuf(ch->buffer, es->parent_dmat) == -1) return NULL; + return ch; +} + +static int +eschan_setdir(void *data, int dir) +{ + struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + + if (dir == PCMDIR_PLAY) { + bus_space_write_1(es->st, es->sh, ES1370_REG_MEMPAGE, + ES1370_REG_DAC2_FRAMEADR >> 8); + bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_FRAMEADR & 0xff, + vtophys(ch->buffer->buf)); + bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_FRAMECNT & 0xff, + (ch->buffer->bufsize >> 2) - 1); + } else { + bus_space_write_1(es->st, es->sh, ES1370_REG_MEMPAGE, + ES1370_REG_ADC_FRAMEADR >> 8); + bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_FRAMEADR & 0xff, + vtophys(ch->buffer->buf)); + bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_FRAMECNT & 0xff, + (ch->buffer->bufsize >> 2) - 1); + } + ch->dir = dir; + return 0; +} + +static int +eschan_setformat(void *data, u_int32_t format) +{ + struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + + if (ch->dir == PCMDIR_PLAY) { + es->sctrl &= ~SCTRL_P2FMT; + if (format & AFMT_S16_LE) es->sctrl |= SCTRL_P2SEB; + if (format & AFMT_STEREO) es->sctrl |= SCTRL_P2SMB; + } else { + es->sctrl &= ~SCTRL_R1FMT; + if (format & AFMT_S16_LE) es->sctrl |= SCTRL_R1SEB; + if (format & AFMT_STEREO) es->sctrl |= SCTRL_R1SMB; + } + bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); + ch->fmt = format; + return 0; +} + +static int +eschan_setspeed(void *data, u_int32_t speed) +{ + struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + + es->ctrl &= ~CTRL_PCLKDIV; + es->ctrl |= DAC2_SRTODIV(speed) << CTRL_SH_PCLKDIV; + bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); + /* rec/play speeds locked together - should indicate in flags */ +#if 0 + if (ch->direction == PCMDIR_PLAY) d->rec[0].speed = speed; + else d->play[0].speed = speed; +#endif + return speed; /* XXX calc real speed */ +} + +static int +eschan_setblocksize(void *data, u_int32_t blocksize) +{ + return blocksize; +} + +static int +eschan_trigger(void *data, int go) +{ + struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + unsigned cnt = ch->buffer->dl / ch->buffer->sample_size - 1; + + if (ch->dir == PCMDIR_PLAY) { + if (go == PCMTRIG_START) { + int b = (ch->fmt & AFMT_S16_LE)? 2 : 1; + es->ctrl |= CTRL_DAC2_EN; + es->sctrl &= ~(SCTRL_P2ENDINC | SCTRL_P2STINC | + SCTRL_P2LOOPSEL | SCTRL_P2PAUSE | + SCTRL_P2DACSEN); + es->sctrl |= SCTRL_P2INTEN | (b << SCTRL_SH_P2ENDINC); + bus_space_write_4(es->st, es->sh, + ES1370_REG_DAC2_SCOUNT, cnt); + } else es->ctrl &= ~CTRL_DAC2_EN; + } else { + if (go == PCMTRIG_START) { + es->ctrl |= CTRL_ADC_EN; + es->sctrl &= ~SCTRL_R1LOOPSEL; + es->sctrl |= SCTRL_R1INTEN; + bus_space_write_4(es->st, es->sh, + ES1370_REG_ADC_SCOUNT, cnt); + } else es->ctrl &= ~CTRL_ADC_EN; + } + bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); + bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); + return 0; +} + +static int +eschan_getptr(void *data) +{ + struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + if (ch->dir == PCMDIR_PLAY) { + bus_space_write_4(es->st, es->sh, ES1370_REG_MEMPAGE, + ES1370_REG_DAC2_FRAMECNT >> 8); + return (bus_space_read_4(es->st, es->sh, + ES1370_REG_DAC2_FRAMECNT & 0xff) >> 14) & 0x3fffc; + } else { + bus_space_write_4(es->st, es->sh, ES1370_REG_MEMPAGE, + ES1370_REG_ADC_FRAMECNT >> 8); + return (bus_space_read_4(es->st, es->sh, + ES1370_REG_ADC_FRAMECNT & 0xff) >> 14) & 0x3fffc; + } +} + +static pcmchan_caps * +eschan_getcaps(void *data) +{ + struct es_chinfo *ch = data; + return (ch->dir == PCMDIR_PLAY)? &es_playcaps : &es_reccaps; +} + +/* The interrupt handler */ +static void +es_intr (void *p) +{ + struct es_info *es = p; + unsigned intsrc, sctrl; + + intsrc = bus_space_read_4(es->st, es->sh, ES1370_REG_STATUS); + if ((intsrc & STAT_INTR) == 0) return; + + sctrl = es->sctrl; + if (intsrc & STAT_ADC) sctrl &= ~SCTRL_R1INTEN; + if (intsrc & STAT_DAC1) sctrl &= ~SCTRL_P1INTEN; + if (intsrc & STAT_DAC2) sctrl &= ~SCTRL_P2INTEN; + + bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, sctrl); + bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); + + if (intsrc & STAT_DAC2) chn_intr(es->pch.channel); + if (intsrc & STAT_ADC) chn_intr(es->rch.channel); +} + +/* -------------------------------------------------------------------- */ + +/* + * Probe and attach the card + */ + +static int +es_init(struct es_info *es) +{ + es->ctrl = CTRL_CDC_EN | CTRL_SERR_DIS | + (DAC2_SRTODIV(DSP_DEFAULT_SPEED) << CTRL_SH_PCLKDIV); + bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); + + es->sctrl = 0; + bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); + + write_codec(es, CODEC_RES_PD, 3);/* No RST, PD */ + write_codec(es, CODEC_CSEL, 0); /* CODEC ADC and CODEC DAC use + * {LR,B}CLK2 and run off the LRCLK2 + * PLL; program DAC_SYNC=0! */ + write_codec(es, CODEC_ADSEL, 0);/* Recording source is mixer */ + write_codec(es, CODEC_MGAIN, 0);/* MIC amp is 0db */ + + return 0; +} + +static int +es_pci_probe(device_t dev) +{ + if (pci_get_devid(dev) == ES1370_PCI_ID) { + device_set_desc(dev, "AudioPCI ES1370"); + return 0; + } + return ENXIO; +} + +static int +es_pci_attach(device_t dev) +{ + snddev_info *d; + u_int32_t data; + struct es_info *es = 0; + int type = 0; + int regid; + struct resource *reg = 0; + int mapped; + int irqid; + struct resource *irq = 0; + void *ih = 0; + char status[SND_STATUSLEN]; + + d = device_get_softc(dev); + if ((es = malloc(sizeof *es, M_DEVBUF, M_NOWAIT)) == NULL) { + device_printf(dev, "cannot allocate softc\n"); + return ENXIO; + } + bzero(es, sizeof *es); + + mapped = 0; + data = pci_read_config(dev, PCIR_COMMAND, 2); + if (mapped == 0 && (data & PCIM_CMD_MEMEN)) { + regid = MEM_MAP_REG; + type = SYS_RES_MEMORY; + reg = bus_alloc_resource(dev, type, ®id, + 0, ~0, 1, RF_ACTIVE); + if (reg) { + es->st = rman_get_bustag(reg); + es->sh = rman_get_bushandle(reg); + mapped++; + } + } + if (mapped == 0 && (data & PCIM_CMD_PORTEN)) { + regid = PCI_MAP_REG_START; + type = SYS_RES_IOPORT; + reg = bus_alloc_resource(dev, type, ®id, + 0, ~0, 1, RF_ACTIVE); + if (reg) { + es->st = rman_get_bustag(reg); + es->sh = rman_get_bushandle(reg); + mapped++; + } + } + if (mapped == 0) { + device_printf(dev, "unable to map register space\n"); + goto bad; + } + + if (es_init(es) == -1) { + device_printf(dev, "unable to initialize the card\n"); + goto bad; + } + mixer_init(d, &es_mixer, es); + + irqid = 0; + irq = bus_alloc_resource(dev, SYS_RES_IRQ, &irqid, + 0, ~0, 1, RF_ACTIVE); + if (!irq + || bus_setup_intr(dev, irq, INTR_TYPE_TTY, es_intr, es, &ih)) { + device_printf(dev, "unable to map interrupt\n"); + goto bad; + } + + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/ES_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, + /*flags*/0, &es->parent_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto bad; + } + + snprintf(status, SND_STATUSLEN, "at %s 0x%lx irq %ld", + (type == SYS_RES_IOPORT)? "io" : "memory", + rman_get_start(reg), rman_get_start(irq)); + + if (pcm_register(dev, es, 1, 1)) goto bad; + pcm_addchan(dev, PCMDIR_REC, &es_chantemplate, es); + pcm_addchan(dev, PCMDIR_PLAY, &es_chantemplate, es); + pcm_setstatus(dev, status); + + return 0; + + bad: + if (es) free(es, M_DEVBUF); + if (reg) bus_release_resource(dev, type, regid, reg); + if (ih) bus_teardown_intr(dev, irq, ih); + if (irq) bus_release_resource(dev, SYS_RES_IRQ, irqid, irq); + return ENXIO; +} + +static device_method_t es_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, es_pci_probe), + DEVMETHOD(device_attach, es_pci_attach), + + { 0, 0 } +}; + +static driver_t es_driver = { + "pcm", + es_methods, + sizeof(snddev_info), +}; + +static devclass_t pcm_devclass; + +DRIVER_MODULE(es, pci, es_driver, pcm_devclass, 0, 0); + +#endif /* NPCI != 0 */ diff --git a/sys/dev/sound/pci/es137x.h b/sys/dev/sound/pci/es137x.h new file mode 100644 index 0000000..6c981e3 --- /dev/null +++ b/sys/dev/sound/pci/es137x.h @@ -0,0 +1,134 @@ +/* + * This supports the ENSONIQ AudioPCI board based on the ES1370. + * + * Copyright (c) 1998 Joachim Kuebart <joki@kuebart.stuttgart.netsurf.de> + * 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 immediately at the beginning of the file, without modification, + * 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. + * 3. Absolutely no warranty of function or purpose is made by the author + * Joachim Kuebart. + * 4. Modifications may be freely made to this file if the above conditions + * are met. + * + * $Id: es1370_reg.h,v 1.1 1998/12/31 08:14:27 luigi Exp $ + */ + +#ifndef _ES1370_REG_H +#define _ES1370_REG_H + +#define ES1370_REG_CONTROL 0x00 +#define ES1370_REG_STATUS 0x04 +#define ES1370_REG_UART_DATA 0x08 +#define ES1370_REG_UART_STATUS 0x09 +#define ES1370_REG_UART_CONTROL 0x09 +#define ES1370_REG_UART_TEST 0x0a +#define ES1370_REG_MEMPAGE 0x0c +#define ES1370_REG_CODEC 0x10 +#define CODEC_INDEX_SHIFT 8 +#define ES1370_REG_SERIAL_CONTROL 0x20 +#define ES1370_REG_DAC1_SCOUNT 0x24 +#define ES1370_REG_DAC2_SCOUNT 0x28 +#define ES1370_REG_ADC_SCOUNT 0x2c + +#define ES1370_REG_DAC1_FRAMEADR 0xc30 +#define ES1370_REG_DAC1_FRAMECNT 0xc34 +#define ES1370_REG_DAC2_FRAMEADR 0xc38 +#define ES1370_REG_DAC2_FRAMECNT 0xc3c +#define ES1370_REG_ADC_FRAMEADR 0xd30 +#define ES1370_REG_ADC_FRAMECNT 0xd34 + +#define DAC2_SRTODIV(x) (((1411200 + (x) / 2) / (x) - 2) & 0x1fff) +#define DAC2_DIVTOSR(x) (1411200 / ((x) + 2)) + +#define CTRL_ADC_STOP 0x80000000 /* 1 = ADC stopped */ +#define CTRL_XCTL1 0x40000000 /* SERR pin if enabled */ +#define CTRL_OPEN 0x20000000 /* no function, can be read and + * written */ +#define CTRL_PCLKDIV 0x1fff0000 /* ADC/DAC2 clock divider */ +#define CTRL_SH_PCLKDIV 16 +#define CTRL_MSFMTSEL 0x00008000 /* MPEG serial data fmt: 0 = Sony, 1 + * = I2S */ +#define CTRL_M_SBB 0x00004000 /* DAC2 clock: 0 = PCLKDIV, 1 = MPEG */ +#define CTRL_WTSRSEL 0x00003000 /* DAC1 clock freq: 0=5512, 1=11025, + * 2=22050, 3=44100 */ +#define CTRL_SH_WTSRSEL 12 +#define CTRL_DAC_SYNC 0x00000800 /* 1 = DAC2 runs off DAC1 clock */ +#define CTRL_CCB_INTRM 0x00000400 /* 1 = CCB "voice" ints enabled */ +#define CTRL_M_CB 0x00000200 /* recording source: 0 = ADC, 1 = + * MPEG */ +#define CTRL_XCTL0 0x00000100 /* 0 = Line in, 1 = Line out */ +#define CTRL_BREQ 0x00000080 /* 1 = test mode (internal mem test) */ +#define CTRL_DAC1_EN 0x00000040 /* enable DAC1 */ +#define CTRL_DAC2_EN 0x00000020 /* enable DAC2 */ +#define CTRL_ADC_EN 0x00000010 /* enable ADC */ +#define CTRL_UART_EN 0x00000008 /* enable MIDI uart */ +#define CTRL_JYSTK_EN 0x00000004 /* enable Joystick port (presumably + * at address 0x200) */ +#define CTRL_CDC_EN 0x00000002 /* enable serial (CODEC) interface */ +#define CTRL_SERR_DIS 0x00000001 /* 1 = disable PCI SERR signal */ + +#define SCTRL_P2ENDINC 0x00380000 /* */ +#define SCTRL_SH_P2ENDINC 19 +#define SCTRL_P2STINC 0x00070000 /* */ +#define SCTRL_SH_P2STINC 16 +#define SCTRL_R1LOOPSEL 0x00008000 /* 0 = loop mode */ +#define SCTRL_P2LOOPSEL 0x00004000 /* 0 = loop mode */ +#define SCTRL_P1LOOPSEL 0x00002000 /* 0 = loop mode */ +#define SCTRL_P2PAUSE 0x00001000 /* 1 = pause mode */ +#define SCTRL_P1PAUSE 0x00000800 /* 1 = pause mode */ +#define SCTRL_R1INTEN 0x00000400 /* enable interrupt */ +#define SCTRL_P2INTEN 0x00000200 /* enable interrupt */ +#define SCTRL_P1INTEN 0x00000100 /* enable interrupt */ +#define SCTRL_P1SCTRLD 0x00000080 /* reload sample count register for + * DAC1 */ +#define SCTRL_P2DACSEN 0x00000040 /* 1 = DAC2 play back last sample + * when disabled */ +#define SCTRL_R1SEB 0x00000020 /* 1 = 16bit */ +#define SCTRL_R1SMB 0x00000010 /* 1 = stereo */ +#define SCTRL_R1FMT 0x00000030 /* format mask */ +#define SCTRL_SH_R1FMT 4 +#define SCTRL_P2SEB 0x00000008 /* 1 = 16bit */ +#define SCTRL_P2SMB 0x00000004 /* 1 = stereo */ +#define SCTRL_P2FMT 0x0000000c /* format mask */ +#define SCTRL_SH_P2FMT 2 +#define SCTRL_P1SEB 0x00000002 /* 1 = 16bit */ +#define SCTRL_P1SMB 0x00000001 /* 1 = stereo */ +#define SCTRL_P1FMT 0x00000003 /* format mask */ +#define SCTRL_SH_P1FMT 0 + +#define STAT_INTR 0x80000000 /* wired or of all interrupt bits */ +#define STAT_CSTAT 0x00000400 /* 1 = codec busy or codec write in + * progress */ +#define STAT_CBUSY 0x00000200 /* 1 = codec busy */ +#define STAT_CWRIP 0x00000100 /* 1 = codec write in progress */ +#define STAT_VC 0x00000060 /* CCB int source, 0=DAC1, 1=DAC2, + * 2=ADC, 3=undef */ +#define STAT_SH_VC 5 +#define STAT_MCCB 0x00000010 /* CCB int pending */ +#define STAT_UART 0x00000008 /* UART int pending */ +#define STAT_DAC1 0x00000004 /* DAC1 int pending */ +#define STAT_DAC2 0x00000002 /* DAC2 int pending */ +#define STAT_ADC 0x00000001 /* ADC int pending */ + +#define CODEC_OMIX1 0x10 +#define CODEC_OMIX2 0x11 +#define CODEC_LIMIX1 0x12 +#define CODEC_RIMIX1 0x13 +#define CODEC_LIMIX2 0x14 +#define CODEC_RIMIX2 0x15 +#define CODEC_RES_PD 0x16 +#define CODEC_CSEL 0x17 +#define CODEC_ADSEL 0x18 +#define CODEC_MGAIN 0x19 + +#define ES_BUFFSIZE 0x20000 /* We're PCI! Use a large buffer */ + +#endif diff --git a/sys/dev/sound/pci/t4dwave.c b/sys/dev/sound/pci/t4dwave.c new file mode 100644 index 0000000..5404fe2 --- /dev/null +++ b/sys/dev/sound/pci/t4dwave.c @@ -0,0 +1,688 @@ +/* + * 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, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "pci.h" +#include "pcm.h" + +#include <dev/pcm/sound.h> +#include <dev/pcm/ac97.h> +#include <dev/pcm/pci/t4dwave.h> + +#include <pci/pcireg.h> +#include <pci/pcivar.h> + +#if NPCI != 0 + +/* -------------------------------------------------------------------- */ + +struct tr_info; + +/* channel registers */ +struct tr_chinfo { + u_int32_t cso, alpha, fms, fmc, ec; + u_int32_t lba; + u_int32_t eso, delta; + u_int32_t rvol, cvol; + u_int32_t gvsel, pan, vol, ctrl; + int index; + snd_dbuf *buffer; + pcm_channel *channel; + struct tr_info *parent; +}; + +/* device private data */ +struct tr_info { + u_int32_t type; + + bus_space_tag_t st; + bus_space_handle_t sh; + bus_dma_tag_t parent_dmat; + + struct resource *reg, *irq; + int regtype, regid, irqid; + void *ih; + + u_int32_t playchns; + struct tr_chinfo chinfo[TR_MAXPLAYCH]; + struct tr_chinfo recchinfo; +}; + +/* -------------------------------------------------------------------- */ + +/* + * prototypes + */ + +/* channel interface */ +static void *trchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); +static int trchan_setdir(void *data, int dir); +static int trchan_setformat(void *data, u_int32_t format); +static int trchan_setspeed(void *data, u_int32_t speed); +static int trchan_setblocksize(void *data, u_int32_t blocksize); +static int trchan_trigger(void *data, int go); +static int trchan_getptr(void *data); +static pcmchan_caps *trchan_getcaps(void *data); + +/* talk to the codec - called from ac97.c */ +static u_int32_t tr_rdcd(void *, int); +static void tr_wrcd(void *, int, u_int32_t); + +/* stuff */ +static int tr_init(struct tr_info *); +static void tr_intr(void *); + +/* talk to the card */ +static u_int32_t tr_rd(struct tr_info *, int, int); +static void tr_wr(struct tr_info *, int, u_int32_t, int); + +/* manipulate playback channels */ +static void tr_clrint(struct tr_info *, char); +static void tr_enaint(struct tr_info *, char, int); +static u_int32_t tr_testint(struct tr_info *, char); +static void tr_rdch(struct tr_info *, char, struct tr_chinfo *); +static void tr_wrch(struct tr_info *, char, struct tr_chinfo *); +static void tr_selch(struct tr_info *, char); +static void tr_startch(struct tr_info *, char); +static void tr_stopch(struct tr_info *, char); + +/* -------------------------------------------------------------------- */ + +static pcmchan_caps tr_reccaps = { + 4000, 48000, + AFMT_STEREO | AFMT_U8 | AFMT_S8 | AFMT_S16_LE | AFMT_U16_LE, + AFMT_STEREO | AFMT_S16_LE +}; + +static pcmchan_caps tr_playcaps = { + 4000, 48000, + AFMT_STEREO | AFMT_U8 | AFMT_S8 | AFMT_S16_LE | AFMT_U16_LE, + AFMT_U16_LE +}; + +static pcm_channel tr_chantemplate = { + trchan_init, + trchan_setdir, + trchan_setformat, + trchan_setspeed, + trchan_setblocksize, + trchan_trigger, + trchan_getptr, + trchan_getcaps, +}; + +/* -------------------------------------------------------------------- */ + +static u_int32_t +tr_fmttobits(u_int32_t fmt) +{ + u_int32_t bits = 0; + bits |= (fmt & AFMT_STEREO)? 0x4 : 0; + bits |= (fmt & (AFMT_S8 | AFMT_S16_LE))? 0x2 : 0; + bits |= (fmt & (AFMT_S16_LE | AFMT_U16_LE))? 0x8 : 0; + return bits; +} + +/* Hardware */ + +static u_int32_t +tr_rd(struct tr_info *tr, int regno, int size) +{ + switch(size) { + case 1: + return bus_space_read_1(tr->st, tr->sh, regno); + case 2: + return bus_space_read_2(tr->st, tr->sh, regno); + case 4: + return bus_space_read_4(tr->st, tr->sh, regno); + default: + return 0xffffffff; + } +} + +static void +tr_wr(struct tr_info *tr, int regno, u_int32_t data, int size) +{ + switch(size) { + case 1: + bus_space_write_1(tr->st, tr->sh, regno, data); + break; + case 2: + bus_space_write_2(tr->st, tr->sh, regno, data); + break; + case 4: + bus_space_write_4(tr->st, tr->sh, regno, data); + break; + } +} + +/* ac97 codec */ + +static u_int32_t +tr_rdcd(void *devinfo, int regno) +{ + struct tr_info *tr = (struct tr_info *)devinfo; + int i, j, treg, trw; + + switch (tr->type) { + case TDX_PCI_ID: + treg=TDX_REG_CODECRD; + trw=TDX_CDC_RWSTAT; + break; + case TNX_PCI_ID: + treg=(regno & 0x100)? TNX_REG_CODEC2RD : TNX_REG_CODEC1RD; + trw=TNX_CDC_RWSTAT; + break; + default: + printf("!!! tr_rdcd defaulted !!!\n"); + return 0xffffffff; + } + + regno &= 0x7f; + tr_wr(tr, treg, regno | trw, 4); + j=trw; + for (i=TR_TIMEOUT_CDC; (i > 0) && (j & trw); i--) j=tr_rd(tr, treg, 4); + if (i == 0) printf("codec timeout during read of register %x\n", regno); + return (j >> TR_CDC_DATA) & 0xffff; +} + +static void +tr_wrcd(void *devinfo, int regno, u_int32_t data) +{ + struct tr_info *tr = (struct tr_info *)devinfo; + int i, j, treg, trw; + + switch (tr->type) { + case TDX_PCI_ID: + treg=TDX_REG_CODECWR; + trw=TDX_CDC_RWSTAT; + break; + case TNX_PCI_ID: + treg=TNX_REG_CODECWR; + trw=TNX_CDC_RWSTAT | ((regno & 0x100)? TNX_CDC_SEC : 0); + break; + default: + printf("!!! tr_wrcd defaulted !!!"); + return; + } + + regno &= 0x7f; +#if 0 + printf("tr_wrcd: reg %x was %x", regno, tr_rdcd(devinfo, regno)); +#endif + j=trw; + for (i=TR_TIMEOUT_CDC; (i>0) && (j & trw); i--) j=tr_rd(tr, treg, 4); + tr_wr(tr, treg, (data << TR_CDC_DATA) | regno | trw, 4); +#if 0 + printf(" - wrote %x, now %x\n", data, tr_rdcd(devinfo, regno)); +#endif + if (i==0) printf("codec timeout writing %x, data %x\n", regno, data); +} + +/* playback channel interrupts */ + +static u_int32_t +tr_testint(struct tr_info *tr, char channel) +{ + return tr_rd(tr, (channel & 0x20)? TR_REG_ADDRINTB : TR_REG_ADDRINTA, + 4) & (1<<(channel & 0x1f)); +} + +static void +tr_clrint(struct tr_info *tr, char channel) +{ + tr_wr(tr, (channel & 0x20)? TR_REG_ADDRINTB : TR_REG_ADDRINTA, + 1<<(channel & 0x1f), 4); +} + +static void +tr_enaint(struct tr_info *tr, char channel, int enable) +{ + u_int32_t reg = (channel & 0x20)? TR_REG_INTENB : TR_REG_INTENA; + u_int32_t i = tr_rd(tr, reg, 4); + channel &= 0x1f; + i &= ~(1 << channel); + i |= (enable? 1 : 0) << channel; + tr_clrint(tr, channel); + tr_wr(tr, reg, i, 4); +} + +/* playback channels */ + +static void +tr_selch(struct tr_info *tr, char channel) +{ + int i=tr_rd(tr, TR_REG_CIR, 4); + i &= ~TR_CIR_MASK; + i |= channel & 0x3f; + tr_wr(tr, TR_REG_CIR, i, 4); +} + +static void +tr_startch(struct tr_info *tr, char channel) +{ + tr_wr(tr, (channel & 0x20)? TR_REG_STARTB : TR_REG_STARTA, + 1<<(channel & 0x1f), 4); +} + +static void +tr_stopch(struct tr_info *tr, char channel) +{ + tr_wr(tr, (channel & 0x20)? TR_REG_STOPB : TR_REG_STOPA, + 1<<(channel & 0x1f), 4); +} + +static void +tr_wrch(struct tr_info *tr, char channel, struct tr_chinfo *ch) +{ + u_int32_t cr[TR_CHN_REGS], i; + + ch->gvsel &= 0x00000001; + ch->fmc &= 0x00000003; + ch->fms &= 0x0000000f; + ch->ctrl &= 0x0000000f; + ch->pan &= 0x0000007f; + ch->rvol &= 0x0000007f; + ch->cvol &= 0x0000007f; + ch->vol &= 0x000000ff; + ch->ec &= 0x00000fff; + ch->alpha &= 0x00000fff; + ch->delta &= 0x0000ffff; + ch->lba &= 0x3fffffff; + + cr[1]=ch->lba; + cr[3]=(ch->rvol<<7) | (ch->cvol); + cr[4]=(ch->gvsel<<31)|(ch->pan<<24)|(ch->vol<<16)|(ch->ctrl<<12)|(ch->ec); + + switch (tr->type) { + case TDX_PCI_ID: + ch->cso &= 0x0000ffff; + ch->eso &= 0x0000ffff; + cr[0]=(ch->cso<<16) | (ch->alpha<<4) | (ch->fms); + cr[2]=(ch->eso<<16) | (ch->delta); + cr[3]|=0x0000c000; + break; + case TNX_PCI_ID: + ch->cso &= 0x00ffffff; + ch->eso &= 0x00ffffff; + cr[0]=((ch->delta & 0xff)<<24) | (ch->cso); + cr[2]=((ch->delta>>16)<<24) | (ch->eso); + cr[3]|=(ch->alpha<<20) | (ch->fms<<16) | (ch->fmc<<14); + break; + } + tr_selch(tr, channel); + for (i=0; i<TR_CHN_REGS; i++) + tr_wr(tr, TR_REG_CHNBASE+(i<<2), cr[i], 4); +} + +static void +tr_rdch(struct tr_info *tr, char channel, struct tr_chinfo *ch) +{ + u_int32_t cr[5], i; + tr_selch(tr, channel); + for (i=0; i<5; i++) cr[i]=tr_rd(tr, TR_REG_CHNBASE+(i<<2), 4); + ch->lba= (cr[1] & 0x3fffffff); + ch->fmc= (cr[3] & 0x0000c000) >> 14; + ch->rvol= (cr[3] & 0x00003f80) >> 7; + ch->cvol= (cr[3] & 0x0000007f); + ch->gvsel= (cr[4] & 0x80000000) >> 31; + ch->pan= (cr[4] & 0x7f000000) >> 24; + ch->vol= (cr[4] & 0x00ff0000) >> 16; + ch->ctrl= (cr[4] & 0x0000f000) >> 12; + ch->ec= (cr[4] & 0x00000fff); + switch(tr->type) { + case TDX_PCI_ID: + ch->cso= (cr[0] & 0xffff0000) >> 16; + ch->alpha= (cr[0] & 0x0000fff0) >> 4; + ch->fms= (cr[0] & 0x0000000f); + ch->eso= (cr[2] & 0xffff0000) >> 16; + ch->delta= (cr[2] & 0x0000ffff); + break; + case TNX_PCI_ID: + ch->cso= (cr[0] & 0x00ffffff); + ch->eso= (cr[2] & 0x00ffffff); + ch->delta= ((cr[2] & 0xff000000) >> 16) | + ((cr[0] & 0xff000000) >> 24); + ch->alpha= (cr[3] & 0xfff00000) >> 20; + ch->fms= (cr[3] & 0x000f0000) >> 16; + break; + } +} + +/* channel interface */ + +void * +trchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) +{ + struct tr_info *tr = devinfo; + struct tr_chinfo *ch; + if (dir == PCMDIR_PLAY) { + ch = &tr->chinfo[tr->playchns]; + ch->index = tr->playchns++; + } else { + ch = &tr->recchinfo; + ch->index = -1; + } + ch->buffer = b; + ch->buffer->bufsize = TR_BUFFSIZE; + ch->parent = tr; + ch->channel = c; + if (chn_allocbuf(ch->buffer, tr->parent_dmat) == -1) return NULL; + else return ch; +} + +static int +trchan_setdir(void *data, int dir) +{ + struct tr_chinfo *ch = data; + struct tr_info *tr = ch->parent; + if (dir == PCMDIR_PLAY && ch->index >= 0) { + ch->fmc = ch->fms = ch->ec = ch->alpha = 0; + ch->lba = vtophys(ch->buffer->buf); + ch->cso = 0; + ch->eso = ch->buffer->bufsize - 1; + ch->rvol = ch->cvol = 0; + ch->gvsel = 0; + ch->pan = 0; + ch->vol = 0; + ch->ctrl = 0x01; + ch->delta = 0; + tr_wrch(tr, ch->index, ch); + tr_enaint(tr, ch->index, 1); + } else if (dir == PCMDIR_REC && ch->index == -1) { + /* set up dma mode regs */ + u_int32_t i; + tr_wr(tr, TR_REG_DMAR15, 0, 1); + i = tr_rd(tr, TR_REG_DMAR11, 1) & 0x03; + tr_wr(tr, TR_REG_DMAR11, i | 0x54, 1); + /* set up base address */ + tr_wr(tr, TR_REG_DMAR0, vtophys(ch->buffer->buf), 4); + /* set up buffer size */ + i = tr_rd(tr, TR_REG_DMAR4, 4) & ~0x00ffffff; + tr_wr(tr, TR_REG_DMAR4, i | (ch->buffer->bufsize - 1), 4); + } else return -1; + return 0; +} + +static int +trchan_setformat(void *data, u_int32_t format) +{ + struct tr_chinfo *ch = data; + struct tr_info *tr = ch->parent; + u_int32_t bits = tr_fmttobits(format); + + if (ch->index >= 0) { + tr_rdch(tr, ch->index, ch); + ch->eso = (ch->buffer->bufsize / ch->buffer->sample_size) - 1; + ch->ctrl = bits | 0x01; + tr_wrch(tr, ch->index, ch); + } else { + u_int32_t i; + /* set # of samples between interrupts */ + i = (TR_INTSAMPLES >> ((bits & 0x08)? 1 : 0)) - 1; + tr_wr(tr, TR_REG_SBBL, i | (i << 16), 4); + /* set sample format */ + i = 0x18 | (bits << 4); + tr_wr(tr, TR_REG_SBCTRL, i, 1); + } + return 0; +} + +static int +trchan_setspeed(void *data, u_int32_t speed) +{ + struct tr_chinfo *ch = data; + struct tr_info *tr = ch->parent; + + if (ch->index >= 0) { + tr_rdch(tr, ch->index, ch); + ch->delta = (speed << 12) / 48000; + tr_wrch(tr, ch->index, ch); + return (ch->delta * 48000) >> 12; + } else { + /* setup speed */ + ch->delta = (48000 << 12) / speed; + tr_wr(tr, TR_REG_SBDELTA, ch->delta, 2); + return (48000 << 12) / ch->delta; + } + return 0; +} + +static int +trchan_setblocksize(void *data, u_int32_t blocksize) +{ + struct tr_chinfo *ch = data; + return ch->buffer->bufsize / 2; +} + +static int +trchan_trigger(void *data, int go) +{ + struct tr_chinfo *ch = data; + struct tr_info *tr = ch->parent; + if (ch->index >= 0) { + if (go == PCMTRIG_START) tr_startch(tr, ch->index); + else tr_stopch(tr, ch->index); + } else { + u_int32_t i = tr_rd(tr, TR_REG_SBCTRL, 1) & ~7; + tr_wr(tr, TR_REG_SBCTRL, i | (go == PCMTRIG_START)? 1 : 0, 1); + } + return 0; +} + +static int +trchan_getptr(void *data) +{ + struct tr_chinfo *ch = data; + struct tr_info *tr = ch->parent; + if (ch->index >= 0) { + tr_rdch(tr, ch->index, ch); + return ch->cso * ch->buffer->sample_size; + } else return tr_rd(tr, TR_REG_DMAR0, 4) - vtophys(ch->buffer->buf); +} + +static pcmchan_caps * +trchan_getcaps(void *data) +{ + struct tr_chinfo *ch = data; + return (ch->index >= 0)? &tr_playcaps : &tr_reccaps; +} + +/* The interrupt handler */ + +static void +tr_intr(void *p) +{ + struct tr_info *tr = (struct tr_info *)p; + u_int32_t intsrc = tr_rd(tr, TR_REG_MISCINT, 4); + + if (intsrc & TR_INT_ADDR) { + int i; + for (i = 0; i < tr->playchns; i++) { + if (tr_testint(tr, i)) { + chn_intr(tr->chinfo[i].channel); + tr_clrint(tr, i); + } + } + } + if (intsrc & TR_INT_SB) { + chn_intr(tr->recchinfo.channel); + tr_rd(tr, TR_REG_SBR9, 1); + tr_rd(tr, TR_REG_SBR10, 1); + } +} + +/* -------------------------------------------------------------------- */ + +/* + * Probe and attach the card + */ + +static int +tr_init(struct tr_info *tr) +{ + if (tr->type == TDX_PCI_ID) { + tr_wr(tr, TDX_REG_CODECST, TDX_CDC_ON, 4); + } else tr_wr(tr, TNX_REG_CODECST, TNX_CDC_ON, 4); + + tr_wr(tr, TR_REG_CIR, TR_CIR_MIDENA | TR_CIR_ADDRENA, 4); + tr->playchns = 0; + return 0; +} + +static int +tr_pci_probe(device_t dev) +{ + if (pci_get_devid(dev) == TDX_PCI_ID) { + device_set_desc(dev, "Trident 4DWave DX"); + return 0; + } + if (pci_get_devid(dev) == TNX_PCI_ID) { + device_set_desc(dev, "Trident 4DWave NX"); + return 0; + } + + return ENXIO; +} + +static int +tr_pci_attach(device_t dev) +{ + snddev_info *d; + u_int32_t data; + struct tr_info *tr; + struct ac97_info *codec; + int i; + int mapped; + char status[SND_STATUSLEN]; + + d = device_get_softc(dev); + if ((tr = malloc(sizeof(*tr), M_DEVBUF, M_NOWAIT)) == NULL) { + device_printf(dev, "cannot allocate softc\n"); + return ENXIO; + } + + bzero(tr, sizeof(*tr)); + tr->type = pci_get_devid(dev); + + data = pci_read_config(dev, PCIR_COMMAND, 2); + data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN); + pci_write_config(dev, PCIR_COMMAND, data, 2); + data = pci_read_config(dev, PCIR_COMMAND, 2); + + mapped = 0; + /* XXX dfr: is this strictly necessary? */ + for (i = 0; (mapped == 0) && (i < PCI_MAXMAPS_0); i++) { + tr->regid = PCIR_MAPS + i*4; + tr->regtype = SYS_RES_MEMORY; + tr->reg = bus_alloc_resource(dev, tr->regtype, &tr->regid, + 0, ~0, 1, RF_ACTIVE); + if (!tr->reg) { + tr->regtype = SYS_RES_IOPORT; + tr->reg = bus_alloc_resource(dev, tr->regtype, + &tr->regid, 0, ~0, 1, + RF_ACTIVE); + } + if (tr->reg) { + tr->st = rman_get_bustag(tr->reg); + tr->sh = rman_get_bushandle(tr->reg); + mapped++; + } + } + + if (mapped == 0) { + device_printf(dev, "unable to map register space\n"); + goto bad; + } + + if (tr_init(tr) == -1) { + device_printf(dev, "unable to initialize the card\n"); + goto bad; + } + + codec = ac97_create(tr, tr_rdcd, tr_wrcd); + if (codec == NULL) goto bad; + mixer_init(d, &ac97_mixer, codec); + + tr->irqid = 0; + tr->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &tr->irqid, + 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); + if (!tr->irq || + bus_setup_intr(dev, tr->irq, INTR_TYPE_TTY, tr_intr, tr, &tr->ih)) { + device_printf(dev, "unable to map interrupt\n"); + goto bad; + } + + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/TR_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, + /*flags*/0, &tr->parent_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto bad; + } + + snprintf(status, 64, "at %s 0x%lx irq %ld", + (tr->regtype == SYS_RES_IOPORT)? "io" : "memory", + rman_get_start(tr->reg), rman_get_start(tr->irq)); + + if (pcm_register(dev, tr, TR_MAXPLAYCH, 1)) goto bad; + pcm_addchan(dev, PCMDIR_REC, &tr_chantemplate, tr); + for (i = 0; i < TR_MAXPLAYCH; i++) + pcm_addchan(dev, PCMDIR_PLAY, &tr_chantemplate, tr); + pcm_setstatus(dev, status); + + return 0; + +bad: + if (tr->reg) bus_release_resource(dev, tr->regtype, tr->regid, tr->reg); + if (tr->ih) bus_teardown_intr(dev, tr->irq, tr->ih); + if (tr->irq) bus_release_resource(dev, SYS_RES_IRQ, tr->irqid, tr->irq); + free(tr, M_DEVBUF); + return ENXIO; +} + +static device_method_t tr_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, tr_pci_probe), + DEVMETHOD(device_attach, tr_pci_attach), + + { 0, 0 } +}; + +static driver_t tr_driver = { + "pcm", + tr_methods, + sizeof(snddev_info), +}; + +static devclass_t pcm_devclass; + +DRIVER_MODULE(tr, pci, tr_driver, pcm_devclass, 0, 0); + +#endif /* NPCI != 0 */ diff --git a/sys/dev/sound/pci/t4dwave.h b/sys/dev/sound/pci/t4dwave.h new file mode 100644 index 0000000..31f3340 --- /dev/null +++ b/sys/dev/sound/pci/t4dwave.h @@ -0,0 +1,103 @@ +/* + * 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$ + */ + +#ifndef _T4DWAVE_REG_H +#define _T4DWAVE_REG_H + +#define TDX_PCI_ID 0x20001023 +#define TNX_PCI_ID 0x20011023 + +#define TR_BUFFSIZE 0x8000 +#define TR_TIMEOUT_CDC 0xffff +#define TR_INTSAMPLES 0x2000 +#define TR_MAXPLAYCH 64 + +#define TR_REG_CIR 0xa0 +#define TR_CIR_MASK 0x0000003f +#define TR_CIR_ADDRENA 0x00001000 +#define TR_CIR_MIDENA 0x00002000 +#define TR_REG_MISCINT 0xb0 +#define TR_INT_ADDR 0x00000020 +#define TR_INT_SB 0x00000004 + +#define TR_REG_DMAR0 0x00 +#define TR_REG_DMAR4 0x04 +#define TR_REG_DMAR11 0x0b +#define TR_REG_DMAR15 0x0f +#define TR_REG_SBR4 0x14 +#define TR_REG_SBR5 0x15 +#define TR_SB_INTSTATUS 0x82 +#define TR_REG_SBR9 0x1e +#define TR_REG_SBR10 0x1f +#define TR_REG_SBBL 0xc0 +#define TR_REG_SBCTRL 0xc4 +#define TR_REG_SBDELTA 0xac + +#define TR_CDC_DATA 16 +#define TDX_REG_CODECWR 0x40 +#define TDX_REG_CODECRD 0x44 +#define TDX_CDC_RWSTAT 0x00008000 +#define TDX_REG_CODECST 0x48 +#define TDX_CDC_SBCTRL 0x40 +#define TDX_CDC_ACTIVE 0x20 +#define TDX_CDC_READY 0x10 +#define TDX_CDC_ADCON 0x08 +#define TDX_CDC_DACON 0x02 +#define TDX_CDC_RESET 0x01 +#define TDX_CDC_ON (TDX_CDC_ADCON|TDX_CDC_DACON) + +#define TNX_REG_CODECWR 0x44 +#define TNX_REG_CODEC1RD 0x48 +#define TNX_REG_CODEC2RD 0x4c +#define TNX_CDC_RWSTAT 0x00000c00 +#define TNX_CDC_SEC 0x00000100 +#define TNX_REG_CODECST 0x40 +#define TNX_CDC_READY2 0x40 +#define TNX_CDC_ADC2ON 0x20 +#define TNX_CDC_DAC2ON 0x10 +#define TNX_CDC_READY1 0x08 +#define TNX_CDC_ADC1ON 0x04 +#define TNX_CDC_DAC1ON 0x02 +#define TNX_CDC_RESET 0x01 +#define TNX_CDC_ON (TNX_CDC_ADC1ON|TNX_CDC_DAC1ON) + + +#define TR_REG_STARTA 0x80 +#define TR_REG_STOPA 0x84 +#define TR_REG_ADDRINTA 0x98 +#define TR_REG_INTENA 0xa4 + +#define TR_REG_STARTB 0xb4 +#define TR_REG_STOPB 0xb8 +#define TR_REG_ADDRINTB 0xd8 +#define TR_REG_INTENB 0xdc + +#define TR_REG_CHNBASE 0xe0 +#define TR_CHN_REGS 5 + +#endif 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 |