1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-12-24 11:29:10 +00:00
freebsd/sys/pci/es1370.c
Peter Wemm 579f45fa60 Simplify the COMPAT_PCI_DRIVER/DATA_SET hack. We can add:
#define COMPAT_PCI_DRIVER(name,data) DATA_SET(pcidevice_set,data)
.. to 2.2.x and 3.x if people think it's worth it.  Driver writers can do
this if it's not defined.  (The reason for this is that I'm trying to
progressively eliminate use of linker_sets where it hurts modularity and
runtime load capability, and these DATA_SET's keep getting in the way.)
1999-05-09 17:07:30 +00:00

1129 lines
25 KiB
C

/*
* 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: es1370.c,v 1.3 1999/05/09 10:43:54 peter Exp $
*/
#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>
#define DSP_ULAW_NOT_WANTED
#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 const 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
};
COMPAT_PCI_DRIVER(es_pci, 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 const 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 */