41b420910c
#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.)
1129 lines
25 KiB
C
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 */
|