diff options
| author | Luigi Rizzo <luigi@FreeBSD.org> | 1998-12-31 08:14:27 +0000 |
|---|---|---|
| committer | Luigi Rizzo <luigi@FreeBSD.org> | 1998-12-31 08:14:27 +0000 |
| commit | e987b015bd01c858f583ad6ec23b844784cb625f (patch) | |
| tree | f95284c386a88161b15323f671bd25516728ddca | |
| parent | 68ec4eb602ad64325fca17c83b81432450931084 (diff) | |
Notes
| -rw-r--r-- | sys/pci/es1370.c | 1127 | ||||
| -rw-r--r-- | sys/pci/es1370_reg.h | 134 |
2 files changed, 1261 insertions, 0 deletions
diff --git a/sys/pci/es1370.c b/sys/pci/es1370.c new file mode 100644 index 0000000000000..7277e7ab14811 --- /dev/null +++ b/sys/pci/es1370.c @@ -0,0 +1,1127 @@ +/* + * Support the ENSONIQ AudioPCI board based on the ES1370 and Codec + * AK4531. + * + * 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$ + */ + +#include "pci.h" +#include "pcm.h" + +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/kernel.h> +#include <machine/bus_pio.h> +#include <machine/bus_memio.h> +#include <machine/bus.h> +#include <pci/pcireg.h> +#include <pci/pcivar.h> + +#include <pci/es1370_reg.h> +#include <i386/isa/snd/sound.h> +#include <i386/isa/snd/ulaw.h> + +#if NPCI != 0 + + +/* -------------------------------------------------------------------- */ + +/* + * #defines + */ + +#ifdef __alpha__ +#define IO_SPACE_MAPPING ALPHA_BUS_SPACE_IO +#define MEM_SPACE_MAPPING ALPHA_BUS_SPACE_MEM +#else /* not __alpha__ */ +#define IO_SPACE_MAPPING I386_BUS_SPACE_IO +#define MEM_SPACE_MAPPING I386_BUS_SPACE_MEM +#endif /* not __alpha__ */ + +#define DMA_ALIGN_THRESHOLD 4 +#define DMA_ALIGN_MASK (~(DMA_ALIGN_THRESHOLD - 1)) +#define DMA_READ_THRESHOLD 0x200 + +#define MEM_MAP_REG 0x14 + +#define UNIT(minor) ((minor) >> 4) +#define DEV(minor) ((minor) & 0xf) + + +/* -------------------------------------------------------------------- */ + +/* + * PCI IDs of supported chips + */ + +#define ES1370_PCI_ID 0x50001274 + + +/* -------------------------------------------------------------------- */ + +/* + * device private data + */ + +struct es_info { + bus_space_tag_t st; + bus_space_handle_t sh; + + bus_dma_tag_t parent_dmat; + bus_dmamap_t dmam_in, dmam_out; + + /* Contents of board's registers */ + u_long ctrl; + u_long sctrl; +}; + + +/* -------------------------------------------------------------------- */ + +/* + * prototypes + */ + +static void dma_wrintr(snddev_info *); +static void dma_rdintr(snddev_info *); +static int es_init(snddev_info *); +static snd_callback_t es_callback; +static d_open_t es_dsp_open; +static d_close_t es_dsp_close; +static d_ioctl_t es_dsp_ioctl; +static d_read_t es_dsp_read; +static d_write_t es_dsp_write; +static void es_intr(void *); +static int es_rdabort(snddev_info *); +static void es_rd_map(void *, bus_dma_segment_t *, int, int); +static int es_wrabort(snddev_info *); +static void es_wr_map(void *, bus_dma_segment_t *, int, int); +static char *es_pci_probe __P((pcici_t, pcidi_t)); +static void es_pci_attach __P((pcici_t, int)); +static int es_rd_dmaupdate(snddev_info *); +static d_select_t es_select; +static int es_wr_dmaupdate(snddev_info *); +static int alloc_dmabuf(snddev_info *, int); +static int write_codec(snddev_info *, u_char, u_char); + + +/* -------------------------------------------------------------------- */ + +/* + * PCI driver and PCM driver method tables + */ + +static struct pci_device es_pci_driver = { + "es", + es_pci_probe, + es_pci_attach, + &nsnd, + NULL +}; + +DATA_SET(pcidevice_set, es_pci_driver); + +static snddev_info es_op_desc = { + "ENSONIQ AudioPCI", + + 0, /* type, apparently unused */ + NULL, /* ISA probe */ + NULL, /* ISA attach */ + + es_dsp_open, + es_dsp_close, + es_dsp_read, + es_dsp_write, + es_dsp_ioctl, + es_select, + + NULL, /* Interrupt Service Routine */ + es_callback, + + ES_BUFFSIZE, + + AFMT_FULLDUPLEX | AFMT_STEREO | AFMT_U8 | AFMT_S16_LE, /* brag :-) */ +}; + + +/* -------------------------------------------------------------------- */ + +/* + * The mixer interface + */ + +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 +mixer_ioctl(snddev_info *d, u_long cmd, caddr_t data, int fflag, struct proc *p) +{ + int i, j, *val, ret = 0; + + val = (int *)data; + i = cmd & 0xff; + + switch (cmd & IOC_DIRMASK) { + case IOC_IN | IOC_OUT: /* _IOWR */ + switch (i) { + case SOUND_MIXER_RECSRC: + for (i = j = 0; i != SOUND_MIXER_NRDEVICES; i++) + if ((*val & (1 << i)) != 0) { + if (!mixtable[i].recmask) + *val &= ~(1 << i); + else + j |= mixtable[i].recmask; + } + d->mix_recsrc = *val; + write_codec(d, CODEC_LIMIX1, j & 0x55); + write_codec(d, CODEC_RIMIX1, j & 0xaa); + write_codec(d, CODEC_LIMIX2, (j >> 8) & 0x17); + write_codec(d, CODEC_RIMIX2, (j >> 8) & 0x0f); + write_codec(d, CODEC_OMIX1, 0x7f); + write_codec(d, CODEC_OMIX2, 0x3f); + break; + + default: + if (i >= SOUND_MIXER_NRDEVICES || !mixtable[i].avail) + ret = EINVAL; + else { + int l, r, rl, rr; + + l = *val & 0xff; + if (l > 100) + l = 100; + if (mixtable[i].left == 0xf) { + if (l < 2) + rl = 0x80; + else + rl = 7 - (l - 2) / 14; + } else { + if (l < 10) + rl = 0x80; + else + rl = 15 - (l - 10) / 6; + } + if (mixtable[i].stereo) { + r = (*val >> 8) & 0xff; + if (r > 100) + r = 100; + if (r < 10) + rr = 0x80; + else + rr = 15 - (r - 10) / 6; + write_codec(d, mixtable[i].right, rr); + } else + r = l; + write_codec(d, mixtable[i].left, rl); + *val = d->mix_levels[i] = ((u_int) r << 8) | l; + } + break; + } + break; + + default: + ret = ENOSYS; + break; + } + + return (ret); +} + + +/* -------------------------------------------------------------------- */ + +/* + * File operations + */ + +static int +es_dsp_open(dev_t dev, int oflags, int devtype, struct proc *p) +{ + int unit = UNIT(minor(dev)); + snddev_info *d = &pcm_info[unit]; + + if (d->flags & SND_F_BUSY) + return (EBUSY); + d->flags = 0; + + d->dbuf_out.total = d->dbuf_out.prev_total = + d->dbuf_in.total = d->dbuf_in.prev_total = 0; + + switch (DEV(minor(dev))) { + case SND_DEV_DSP16: + d->play_fmt = d->rec_fmt = AFMT_S16_LE; + break; + + case SND_DEV_DSP: + d->play_fmt = d->rec_fmt = AFMT_U8; + break; + + case SND_DEV_AUDIO: + d->play_fmt = d->rec_fmt = AFMT_MU_LAW; + break; + + default: + return (ENXIO); + } + + if ((oflags & FREAD) == 0) + d->rec_fmt = 0; + else if ((oflags & FWRITE) == 0) + d->play_fmt = 0; + + d->play_speed = d->rec_speed = DSP_DEFAULT_SPEED; + + d->flags |= SND_F_BUSY; + if (oflags & O_NONBLOCK) + d->flags |= SND_F_NBIO; + + ask_init(d); + + return (0); +} + +static int +es_dsp_close(dev_t dev, int cflags, int devtype, struct proc *p) +{ + int unit = UNIT(minor(dev)); + snddev_info *d = &pcm_info[unit]; + + d->flags &= ~SND_F_BUSY; + + es_rdabort(d); + + return (0); +} + +static int +es_dsp_read(dev_t dev, struct uio *buf, int flag) +{ + int l, l1, limit, ret = 0, unit = UNIT(minor(dev)); + long s; + snddev_info *d = &pcm_info[unit]; + snd_dbuf *b = &d->dbuf_in; + + if (d->flags & SND_F_READING) { + /* This shouldn't happen and is actually silly */ + tsleep(&s, PZERO, "sndar", hz); + return (EBUSY); + } + d->flags |= SND_F_READING; + + /* + * XXX Check for SND_F_INIT. If set, wait for DMA to run empty and + * re-initialize the board + */ + + if (buf->uio_resid - d->rec_blocksize > 0) + limit = buf->uio_resid - d->rec_blocksize; + else + limit = 0; + + while ((l = buf->uio_resid) > limit) { + s = spltty(); + es_rd_dmaupdate(d); + if ((l = min(l, b->rl)) == 0) { + int timeout; + if (b->dl == 0) + dma_rdintr(d); + if (d->flags & SND_F_NBIO) { + splx(s); + break; + } + if (buf->uio_resid - limit > b->dl) + timeout = hz; + else + timeout = 1; + splx(s); + switch (ret = tsleep((caddr_t)b, PRIBIO | PCATCH, + "dsprd", timeout)) { + case EINTR: + es_rdabort(d); + /* FALLTHROUGH */ + + case ERESTART: + break; + + default: + continue; + } + break; + } + splx(s); + + if ((l1 = b->bufsize - b->rp) < l) { + if (d->flags & SND_F_XLAT8) { + translate_bytes(ulaw_dsp, b->buf + b->rp, l1); + translate_bytes(ulaw_dsp, b->buf, l - l1); + } + uiomove(b->buf + b->rp, l1, buf); + uiomove(b->buf, l - l1, buf); + } else { + if (d->flags & SND_F_XLAT8) + translate_bytes(ulaw_dsp, b->buf + b->rp, l); + uiomove(b->buf + b->rp, l, buf); + } + + s = spltty(); + b->fl += l; + b->rl -= l; + b->rp = (b->rp + l) % b->bufsize; + splx(s); + } + + d->flags &= ~SND_F_READING; + + return (ret); +} + +static int +es_dsp_write(dev_t dev, struct uio *buf, int flag) +{ + int l, l1, ret = 0, unit = UNIT(minor(dev)); + long s; + snddev_info *d = &pcm_info[unit]; + snd_dbuf *b = &d->dbuf_out; + + if (d->flags & SND_F_WRITING) { + /* This shouldn't happen and is actually silly */ + tsleep(&s, PZERO, "sndaw", hz); + return (EBUSY); + } + d->flags |= SND_F_WRITING; + + /* + * XXX Check for SND_F_INIT. If set, wait for DMA to run empty and + * re-initialize the board + */ + + while ((l = buf->uio_resid) != 0) { + s = spltty(); + es_wr_dmaupdate(d); + if ((l = min(l, b->fl)) == 0) { + int timeout; + if (d->flags & SND_F_NBIO) { + splx(s); + break; + } + if (buf->uio_resid >= b->dl) + timeout = hz; + else + timeout = 1; + splx(s); + switch (ret = tsleep((caddr_t)b, PRIBIO | PCATCH, + "dspwr", timeout)) { + case EINTR: + es_wrabort(d); + /* FALLTHROUGH */ + + case ERESTART: + break; + + default: + continue; + } + break; + } + splx(s); + + if ((l1 = b->bufsize - b->fp) < l) { + uiomove(b->buf + b->fp, l1, buf); + uiomove(b->buf, l - l1, buf); + if (d->flags & SND_F_XLAT8) { + translate_bytes(ulaw_dsp, b->buf + b->fp, l1); + translate_bytes(ulaw_dsp, b->buf, l - l1); + } + } else { + uiomove(b->buf + b->fp, l, buf); + if (d->flags & SND_F_XLAT8) + translate_bytes(ulaw_dsp, b->buf + b->fp, l); + } + + s = spltty(); + b->rl += l; + b->fl -= l; + b->fp = (b->fp + l) % b->bufsize; + if (b->dl == 0) + dma_wrintr(d); + splx(s); + } + + d->flags &= ~SND_F_WRITING; + + return (ret); +} + +static int +es_dsp_ioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc *p) +{ + int ret = 0, unit = UNIT(minor(dev)); + snddev_info *d = &pcm_info[unit]; + long s; + + if ((cmd & MIXER_WRITE(0)) == MIXER_WRITE(0)) + return mixer_ioctl(d, cmd, data, fflag, p); + + switch(cmd) { + case AIONWRITE: + if (d->dbuf_out.dl != 0) { + s = spltty(); + es_wr_dmaupdate(d); + splx(s); + } + *(int *)data = d->dbuf_out.fl; + break; + + case FIONREAD: + if (d->dbuf_in.dl != 0) { + s = spltty(); + es_rd_dmaupdate(d); + splx(s); + } + *(int *)data = d->dbuf_in.rl; + break; + + case SNDCTL_DSP_GETISPACE: + { + audio_buf_info *a = (audio_buf_info *)data; + snd_dbuf *b = &d->dbuf_in; + if (b->dl != 0) { + s = spltty(); + es_rd_dmaupdate(d); + splx(s); + } + a->bytes = b->fl; + a->fragments = b->fl / d->rec_blocksize; + a->fragstotal = b->bufsize / d->rec_blocksize; + a->fragsize = d->rec_blocksize; + } + break; + + case SNDCTL_DSP_GETOSPACE: + { + audio_buf_info *a = (audio_buf_info *)data; + snd_dbuf *b = &d->dbuf_out; + if (b->dl != 0) { + s = spltty(); + es_wr_dmaupdate(d); + splx(s); + } + a->bytes = b->fl; + a->fragments = b->fl / d->rec_blocksize; + a->fragstotal = b->bufsize / d->play_blocksize; + a->fragsize = d->play_blocksize; + } + break; + + case SNDCTL_DSP_GETIPTR: + { + count_info *c = (count_info *)data; + snd_dbuf *b = &d->dbuf_in; + if (b->dl != 0) { + s = spltty(); + es_rd_dmaupdate(d); + splx(s); + } + c->bytes = b->total; + c->blocks = (b->total - b->prev_total + + d->rec_blocksize - 1) / d->rec_blocksize; + c->ptr = b->fp; + b->prev_total = b->total; + } + break; + + case SNDCTL_DSP_GETOPTR: + { + count_info *c = (count_info *)data; + snd_dbuf *b = &d->dbuf_out; + if (b->dl != 0) { + s = spltty(); + es_wr_dmaupdate(d); + splx(s); + } + c->bytes = b->total; + c->blocks = (b->total - b->prev_total + + d->play_blocksize - 1) / d->play_blocksize; + c->ptr = b->rp; + b->prev_total = b->total; + } + break; + + case AIOSTOP: + case SNDCTL_DSP_RESET: + case SNDCTL_DSP_SYNC: + ret = EINVAL; + break; + + default: + ret = ENOSYS; + break; + } + return (ret); +} + +static int +es_select(dev_t i_dev, int rw, struct proc * p) +{ + return (ENOSYS); +} + + +/* -------------------------------------------------------------------- */ + +/* + * The interrupt handler + */ + +static void +es_intr (void *p) +{ + snddev_info *d = (snddev_info *)p; + struct es_info *es = (struct es_info *)d->device_data; + 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) + dma_wrintr(d); + if (intsrc & STAT_ADC) + dma_rdintr(d); +} + + +/* -------------------------------------------------------------------- */ + +/* + * DMA hassle + */ + +static int +alloc_dmabuf(snddev_info *d, int rd) +{ + struct es_info *es = (struct es_info *)d->device_data; + snd_dbuf *b = rd ? &d->dbuf_in : &d->dbuf_out; + bus_dmamap_t *dmam = rd ? &es->dmam_in : &es->dmam_out; + + if (bus_dmamem_alloc(es->parent_dmat, (void **)&b->buf, BUS_DMA_NOWAIT, + dmam) != 0 || + bus_dmamap_load(es->parent_dmat, *dmam, b->buf, d->bufsize, + rd ? es_rd_map : es_wr_map, es, 0) != 0) + return -1; + + b->rp = b->fp = b->dl = b->rl = 0; + b->fl = b->bufsize = d->bufsize; + return (0); +} + +static int +es_wr_dmaupdate(snddev_info *d) +{ + struct es_info *es = (struct es_info *)d->device_data; + unsigned hwptr, delta; + + bus_space_write_4(es->st, es->sh, ES1370_REG_MEMPAGE, + ES1370_REG_DAC2_FRAMECNT >> 8); + hwptr = (bus_space_read_4(es->st, es->sh, + ES1370_REG_DAC2_FRAMECNT & 0xff) >> 14) & 0x3fffc; + delta = (d->dbuf_out.bufsize + hwptr - d->dbuf_out.rp) % + d->dbuf_out.bufsize; + d->dbuf_out.rp = hwptr; + d->dbuf_out.rl -= delta; + d->dbuf_out.fl += delta; + d->dbuf_out.total += delta; + + return delta; +} + +static int +es_rd_dmaupdate(snddev_info *d) +{ + struct es_info *es = (struct es_info *)d->device_data; + unsigned hwptr, delta; + + bus_space_write_4(es->st, es->sh, ES1370_REG_MEMPAGE, + ES1370_REG_ADC_FRAMECNT >> 8); + hwptr = (bus_space_read_4(es->st, es->sh, + ES1370_REG_ADC_FRAMECNT & 0xff) >> 14) & 0x3fffc; + delta = (d->dbuf_in.bufsize + hwptr - d->dbuf_in.fp) % + d->dbuf_in.bufsize; + d->dbuf_in.fp = hwptr; + d->dbuf_in.rl += delta; + d->dbuf_in.fl -= delta; + d->dbuf_in.total += delta; + return delta; +} + + +/* -------------------------------------------------------------------- */ + +/* + * Hardware + */ + +static int +es_callback(snddev_info *d, int reason) +{ + struct es_info *es = (struct es_info *)d->device_data; + int rd = reason & SND_CB_RD; + + switch(reason & SND_CB_REASON_MASK) { + case SND_CB_INIT: + es->ctrl = (es->ctrl & ~CTRL_PCLKDIV) | + (DAC2_SRTODIV(d->play_speed) << CTRL_SH_PCLKDIV); + snd_set_blocksize(d); + + es->sctrl &= ~(SCTRL_R1FMT | SCTRL_P2FMT); + d->flags &= ~SND_F_XLAT8; + switch(d->play_fmt) { + case 0: + case AFMT_U8: + break; + + case AFMT_S16_LE: + es->sctrl |= SCTRL_P2SEB; + break; + + case AFMT_MU_LAW: + d->flags |= SND_F_XLAT8; + break; + + default: + return (-1); + } + + switch(d->rec_fmt) { + case 0: + case AFMT_U8: + break; + + case AFMT_S16_LE: + es->sctrl |= SCTRL_R1SEB; + break; + + case AFMT_MU_LAW: + d->flags |= SND_F_XLAT8; + break; + + default: + return (-1); + } + + if (d->flags & SND_F_STEREO) + es->sctrl |= SCTRL_P2SMB | SCTRL_R1SMB; + + bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, + es->ctrl); + bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, + es->sctrl); + break; + + case SND_CB_START: + if (rd) { + es->ctrl |= CTRL_ADC_EN; + es->sctrl = (es->sctrl & ~SCTRL_R1LOOPSEL) | + SCTRL_R1INTEN; + bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_SCOUNT, + d->dbuf_in.dl / d->dbuf_in.sample_size - 1); + } else { + es->ctrl |= CTRL_DAC2_EN; + es->sctrl = (es->sctrl & ~(SCTRL_P2ENDINC | + SCTRL_P2STINC | SCTRL_P2LOOPSEL | SCTRL_P2PAUSE | + SCTRL_P2DACSEN)) | SCTRL_P2INTEN | + (((d->play_fmt == AFMT_S16_LE) ? 2 : 1) + << SCTRL_SH_P2ENDINC); + bus_space_write_4(es->st, es->sh, + ES1370_REG_DAC2_SCOUNT, + d->dbuf_out.dl / d->dbuf_out.sample_size - 1); + } + 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); + break; + + case SND_CB_ABORT: + case SND_CB_STOP: + if (rd) + es->ctrl &= ~CTRL_ADC_EN; + else + es->ctrl &= ~CTRL_DAC2_EN; + bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); + break; + + default: + return (-1); + } + return (0); +} + +static int +write_codec(snddev_info *d, u_char i, u_char data) +{ + struct es_info *es = (struct es_info *)d->device_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); + /* tsleep(&wait, PZERO, "sndaw", hz / 1000); */ + } while (--wait); + printf("pcm: write_codec timed out\n"); + return (-1); +} + +static void +es_wr_map(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct es_info *es = (struct es_info *)arg; + + 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, + segs->ds_addr); + bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_FRAMECNT & 0xff, + (segs->ds_len >> 2) - 1); +} + +static void +es_rd_map(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct es_info *es = (struct es_info *)arg; + + 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, + segs->ds_addr); + bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_FRAMECNT & 0xff, + (segs->ds_len >> 2) - 1); +} + +static void +dma_wrintr(snddev_info *d) +{ + snd_dbuf *b = &d->dbuf_out; + + /* + * According to Linux driver: + * dmaupdate() + * Bei underrun error++ + * wake_up(dac2.wait) + */ + + if (b->dl != 0) { + es_wr_dmaupdate(d); + wakeup(b); + } + + if (b->rl >= DMA_ALIGN_THRESHOLD && + !(d->flags & SND_F_ABORTING)) { + int l = min(b->rl, d->play_blocksize); + l &= DMA_ALIGN_MASK; + + if (l != b->dl) { + if (b->dl != 0) { + d->callback(d, SND_CB_WR | SND_CB_STOP); + es_wr_dmaupdate(d); + l = min(b->rl, d->play_blocksize); + l &= DMA_ALIGN_MASK; + } + b->dl = l; + d->callback(d, SND_CB_WR | SND_CB_START); + } + } else if (b->dl != 0) { + b->dl = 0; + d->callback(d, SND_CB_WR | SND_CB_STOP); + es_wr_dmaupdate(d); + } +} + +static void +dma_rdintr(snddev_info *d) +{ + snd_dbuf *b = &d->dbuf_in; + + if (b->dl != 0) { + es_rd_dmaupdate(d); + wakeup(b); + } + + if (b->fl >= DMA_READ_THRESHOLD && + !(d->flags & SND_F_ABORTING)) { + int l = min(b->fl, d->rec_blocksize); + l &= DMA_ALIGN_MASK; + + if (l != b->dl) { + if (b->dl != 0) { + d->callback(d, SND_CB_RD | SND_CB_STOP); + es_rd_dmaupdate(d); + l = min(b->fl, d->rec_blocksize); + l &= DMA_ALIGN_MASK; + } + b->dl = l; + d->callback(d, SND_CB_RD | SND_CB_START); + } + } else { + if (b->dl != 0) { + b->dl = 0; + d->callback(d, SND_CB_RD | SND_CB_STOP); + es_rd_dmaupdate(d); + } + } +} + +static int +es_wrabort(snddev_info *d) +{ + snd_dbuf *b = &d->dbuf_out; + long s; + int missing; + + s = spltty(); + if (b->dl != 0) { + wakeup(b); + b->dl = 0; + d->callback(d, SND_CB_WR | SND_CB_ABORT); + } + es_wr_dmaupdate(d); + missing = b->rl; + b->rl = 0; + b->fp = b->rp; + b->fl = b->bufsize; + splx(s); + return missing; +} + +static int +es_rdabort(snddev_info *d) +{ + snd_dbuf *b = &d->dbuf_in; + long s; + int missing; + + s = spltty(); + if (b->dl != 0) { + wakeup(b); + b->dl = 0; + d->callback(d, SND_CB_RD | SND_CB_ABORT); + es_rd_dmaupdate(d); + } + missing = b->rl; + b->rl = 0; + b->fp = b->rp; + b->fl = b->bufsize; + splx(s); + return missing; +} + + +/* -------------------------------------------------------------------- */ + +/* + * Probe and attach the card + */ + +static int +es_init(snddev_info *d) +{ + struct es_info *es = (struct es_info *)d->device_data; + u_int i; + + 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(d, CODEC_RES_PD, 3);/* No RST, PD */ + write_codec(d, CODEC_CSEL, 0); /* CODEC ADC and CODEC DAC use + * {LR,B}CLK2 and run off the LRCLK2 + * PLL; program DAC_SYNC=0! */ + write_codec(d, CODEC_ADSEL, 0); /* Recording source is mixer */ + write_codec(d, CODEC_MGAIN, 0); /* MIC amp is 0db */ + + i = SOUND_MASK_MIC; + mixer_ioctl(d, SOUND_MIXER_WRITE_RECSRC, (caddr_t) &i, 0, NULL); + i = 0; + mixer_ioctl(d, SOUND_MIXER_WRITE_VOLUME, (caddr_t) &i, 0, NULL); + mixer_ioctl(d, SOUND_MIXER_WRITE_PCM, (caddr_t) &i, 0, NULL); + mixer_ioctl(d, SOUND_MIXER_WRITE_SYNTH, (caddr_t) &i, 0, NULL); + mixer_ioctl(d, SOUND_MIXER_WRITE_CD, (caddr_t) &i, 0, NULL); + mixer_ioctl(d, SOUND_MIXER_WRITE_LINE, (caddr_t) &i, 0, NULL); + mixer_ioctl(d, SOUND_MIXER_WRITE_LINE1, (caddr_t) &i, 0, NULL); + mixer_ioctl(d, SOUND_MIXER_WRITE_LINE2, (caddr_t) &i, 0, NULL); + mixer_ioctl(d, SOUND_MIXER_WRITE_LINE3, (caddr_t) &i, 0, NULL); + mixer_ioctl(d, SOUND_MIXER_WRITE_MIC, (caddr_t) &i, 0, NULL); + + return (0); +} + +static char * +es_pci_probe(pcici_t tag, pcidi_t type) +{ + if (type == ES1370_PCI_ID) + return ("AudioPCI ES1370"); + + return (NULL); +} + +static void +es_pci_attach(pcici_t config_id, int unit) +{ + snddev_info *d; + u_int32_t data; + struct es_info *es; + pci_port_t io_port; + int i, mapped; + vm_offset_t vaddr, paddr; + + if (unit > NPCM_MAX) + return; + + d = &pcm_info[unit]; + *d = es_op_desc; + if ((es = malloc(sizeof(*es), M_DEVBUF, M_NOWAIT)) == NULL) { + printf("pcm%d: cannot allocate softc\n", unit); + return; + } + bzero(es, sizeof(*es)); + d->device_data = es; + + vaddr = paddr = NULL; + mapped = 0; + data = pci_conf_read(config_id, PCI_COMMAND_STATUS_REG); + if (mapped == 0 && (data & PCI_COMMAND_MEM_ENABLE)) { + if (pci_map_mem(config_id, MEM_MAP_REG, &vaddr, &paddr)) { + es->st = MEM_SPACE_MAPPING; + es->sh = vaddr; + mapped++; + } + } + if (mapped == 0 && (data & PCI_COMMAND_IO_ENABLE)) { + if (pci_map_port(config_id, PCI_MAP_REG_START, &io_port)) { + es->st = IO_SPACE_MAPPING; + es->sh = io_port; + mapped++; + } + } + if (mapped == 0) { + printf("pcm%d: unable to map any ports\n", unit); + free(es, M_DEVBUF); + return; + } + printf("pcm%d: using %s space register mapping at %#x\n", unit, + es->st == IO_SPACE_MAPPING ? "I/O" : "Memory", es->sh); + + d->io_base = es->sh; + d->mix_devs = 0; + for (i = 0; i != SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].avail) + d->mix_devs |= (1 << i); + d->mix_rec_devs = 0; + for (i = 0; i != SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].recmask) + d->mix_rec_devs |= (1 << i); + + if (es_init(d) == -1) { + printf("pcm%d: unable to initialize the card\n", unit); + free(es, M_DEVBUF); + d->io_base = 0; + return; + } + if (pci_map_int(config_id, es_intr, d, &tty_imask) == 0) { + printf("pcm%d: unable to map interrupt\n", unit); + free(es, M_DEVBUF); + d->io_base = 0; + return; + } + 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*/d->bufsize, /*nsegments*/1, /*maxsegz*/0x3ffff, + /*flags*/0, &es->parent_dmat) != 0) { + printf("pcm%d: unable to create dma tag\n", unit); + free(es, M_DEVBUF); + d->io_base = 0; + return; + } + + if (alloc_dmabuf(d, 0) == -1 || + alloc_dmabuf(d, 1) == -1) { + printf("pcm%d: unable to allocate dma buffers\n", unit); + free(es, M_DEVBUF); + d->io_base = 0; + return; + } + + pcminit(d, unit); + + return; +} + +#endif /* NPCI != 0 */ diff --git a/sys/pci/es1370_reg.h b/sys/pci/es1370_reg.h new file mode 100644 index 0000000000000..72546c61449a2 --- /dev/null +++ b/sys/pci/es1370_reg.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$ + */ + +#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 |
