diff options
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 |