mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-22 11:17:19 +00:00
e987b015bd
this has a problem with capture but i am not sure if it is related to the mixer or what else. But in the meantime, this is ok to listen to mpegs. I also have a much simpler version of the driver in the works which reuses a lot more of the existing "pcm" routines. Next year...
1128 lines
25 KiB
C
1128 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$
|
|
*/
|
|
|
|
#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 */
|