freebsd-nq/sys/dev/sound/pci/ds1.c
Cameron Grant 66ef8af5b0 mega-commit.
this introduces a new buffering mechanism which results in dramatic
simplification of the channel manager.

as several structures have changed, we take the opportunity to move their
definitions into the source files where they are used, make them private and
de-typedef them.

the sound drivers are updated to use snd_setup_intr instead of
bus_setup_intr, and to comply with the de-typedefed structures.

the ac97, mixer and channel layers have been updated with finegrained
locking, as have some drivers- not all though.  the rest will follow soon.
2001-03-24 23:10:29 +00:00

1079 lines
27 KiB
C

/*
* Copyright (c) 2000 Cameron Grant <cg@freebsd.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <dev/sound/pcm/sound.h>
#include <dev/sound/pcm/ac97.h>
#include <pci/pcireg.h>
#include <pci/pcivar.h>
#include <dev/sound/pci/ds1.h>
#include <dev/sound/pci/ds1-fw.h>
/* -------------------------------------------------------------------- */
#define DS1_CHANS 4
#define DS1_RECPRIMARY 0
#define DS1_IRQHZ ((48000 << 8) / 256)
#define DS1_BUFFSIZE 4096
struct pbank {
volatile u_int32_t Format;
volatile u_int32_t LoopDefault;
volatile u_int32_t PgBase;
volatile u_int32_t PgLoop;
volatile u_int32_t PgLoopEnd;
volatile u_int32_t PgLoopFrac;
volatile u_int32_t PgDeltaEnd;
volatile u_int32_t LpfKEnd;
volatile u_int32_t EgGainEnd;
volatile u_int32_t LchGainEnd;
volatile u_int32_t RchGainEnd;
volatile u_int32_t Effect1GainEnd;
volatile u_int32_t Effect2GainEnd;
volatile u_int32_t Effect3GainEnd;
volatile u_int32_t LpfQ;
volatile u_int32_t Status;
volatile u_int32_t NumOfFrames;
volatile u_int32_t LoopCount;
volatile u_int32_t PgStart;
volatile u_int32_t PgStartFrac;
volatile u_int32_t PgDelta;
volatile u_int32_t LpfK;
volatile u_int32_t EgGain;
volatile u_int32_t LchGain;
volatile u_int32_t RchGain;
volatile u_int32_t Effect1Gain;
volatile u_int32_t Effect2Gain;
volatile u_int32_t Effect3Gain;
volatile u_int32_t LpfD1;
volatile u_int32_t LpfD2;
};
struct rbank {
volatile u_int32_t PgBase;
volatile u_int32_t PgLoopEnd;
volatile u_int32_t PgStart;
volatile u_int32_t NumOfLoops;
};
struct sc_info;
/* channel registers */
struct sc_pchinfo {
int run, spd, dir, fmt;
struct snd_dbuf *buffer;
struct pcm_channel *channel;
volatile struct pbank *lslot, *rslot;
int lsnum, rsnum;
struct sc_info *parent;
};
struct sc_rchinfo {
int run, spd, dir, fmt, num;
struct snd_dbuf *buffer;
struct pcm_channel *channel;
volatile struct rbank *slot;
struct sc_info *parent;
};
/* device private data */
struct sc_info {
device_t dev;
u_int32_t type, rev;
u_int32_t cd2id, ctrlbase;
bus_space_tag_t st;
bus_space_handle_t sh;
bus_dma_tag_t parent_dmat;
bus_dmamap_t map;
struct resource *reg, *irq;
int regid, irqid;
void *ih;
void *lock;
void *regbase;
u_int32_t *pbase, pbankbase, pbanksize;
volatile struct pbank *pbank[2 * 64];
volatile struct rbank *rbank;
int pslotfree, currbank, pchn, rchn;
struct sc_pchinfo pch[DS1_CHANS];
struct sc_rchinfo rch[2];
};
struct {
u_int32_t dev, subdev;
char *name;
u_int32_t *mcode;
} ds_devs[] = {
{0x00041073, 0, "Yamaha DS-1 (YMF724)", CntrlInst},
{0x000d1073, 0, "Yamaha DS-1E (YMF724F)", CntrlInst1E},
{0x00051073, 0, "Yamaha DS-1? (YMF734)", CntrlInst},
{0x00081073, 0, "Yamaha DS-1? (YMF737)", CntrlInst},
{0x00201073, 0, "Yamaha DS-1? (YMF738)", CntrlInst},
{0x00061073, 0, "Yamaha DS-1? (YMF738_TEG)", CntrlInst},
{0x000a1073, 0x00041073, "Yamaha DS-1 (YMF740)", CntrlInst},
{0x000a1073, 0x000a1073, "Yamaha DS-1 (YMF740B)", CntrlInst},
{0x000a1073, 0x53328086, "Yamaha DS-1 (YMF740I)", CntrlInst},
{0x000a1073, 0, "Yamaha DS-1 (YMF740?)", CntrlInst},
{0x000c1073, 0, "Yamaha DS-1E (YMF740C)", CntrlInst1E},
{0x00101073, 0, "Yamaha DS-1E (YMF744)", CntrlInst1E},
{0x00121073, 0, "Yamaha DS-1E (YMF754)", CntrlInst1E},
{0, 0, NULL, NULL}
};
/* -------------------------------------------------------------------- */
/*
* prototypes
*/
/* stuff */
static int ds_init(struct sc_info *);
static void ds_intr(void *);
/* talk to the card */
static u_int32_t ds_rd(struct sc_info *, int, int);
static void ds_wr(struct sc_info *, int, u_int32_t, int);
/* -------------------------------------------------------------------- */
static u_int32_t ds_recfmt[] = {
AFMT_U8,
AFMT_STEREO | AFMT_U8,
AFMT_S8,
AFMT_STEREO | AFMT_S8,
AFMT_S16_LE,
AFMT_STEREO | AFMT_S16_LE,
AFMT_U16_LE,
AFMT_STEREO | AFMT_U16_LE,
0
};
static struct pcmchan_caps ds_reccaps = {4000, 48000, ds_recfmt, 0};
static u_int32_t ds_playfmt[] = {
AFMT_U8,
AFMT_STEREO | AFMT_U8,
/* AFMT_S16_LE, */
AFMT_STEREO | AFMT_S16_LE,
0
};
static struct pcmchan_caps ds_playcaps = {4000, 96000, ds_playfmt, 0};
/* -------------------------------------------------------------------- */
/* Hardware */
static u_int32_t
ds_rd(struct sc_info *sc, int regno, int size)
{
switch (size) {
case 1:
return bus_space_read_1(sc->st, sc->sh, regno);
case 2:
return bus_space_read_2(sc->st, sc->sh, regno);
case 4:
return bus_space_read_4(sc->st, sc->sh, regno);
default:
return 0xffffffff;
}
}
static void
ds_wr(struct sc_info *sc, int regno, u_int32_t data, int size)
{
switch (size) {
case 1:
bus_space_write_1(sc->st, sc->sh, regno, data);
break;
case 2:
bus_space_write_2(sc->st, sc->sh, regno, data);
break;
case 4:
bus_space_write_4(sc->st, sc->sh, regno, data);
break;
}
}
static void
wrl(struct sc_info *sc, u_int32_t *ptr, u_int32_t val)
{
*(volatile u_int32_t *)ptr = val;
bus_space_barrier(sc->st, sc->sh, 0, 0, BUS_SPACE_BARRIER_WRITE);
}
/* -------------------------------------------------------------------- */
/* ac97 codec */
static int
ds_cdbusy(struct sc_info *sc, int sec)
{
int i, reg;
reg = sec? YDSXGR_SECSTATUSADR : YDSXGR_PRISTATUSADR;
i = YDSXG_AC97TIMEOUT;
while (i > 0) {
if (!(ds_rd(sc, reg, 2) & 0x8000))
return 0;
i--;
}
return ETIMEDOUT;
}
static u_int32_t
ds_initcd(kobj_t obj, void *devinfo)
{
struct sc_info *sc = (struct sc_info *)devinfo;
u_int32_t x;
x = pci_read_config(sc->dev, PCIR_DSXGCTRL, 1);
if (x & 0x03) {
pci_write_config(sc->dev, PCIR_DSXGCTRL, x & ~0x03, 1);
pci_write_config(sc->dev, PCIR_DSXGCTRL, x | 0x03, 1);
pci_write_config(sc->dev, PCIR_DSXGCTRL, x & ~0x03, 1);
/*
* The YMF740 on some Intel motherboards requires a pretty
* hefty delay after this reset for some reason... Otherwise:
* "pcm0: ac97 codec init failed"
* Maybe this is needed for all YMF740's?
* 400ms and 500ms here seem to work, 300ms does not.
*
* do it for all chips -cg
*/
DELAY(500000);
}
return ds_cdbusy(sc, 0)? 0 : 1;
}
static int
ds_rdcd(kobj_t obj, void *devinfo, int regno)
{
struct sc_info *sc = (struct sc_info *)devinfo;
int sec, cid, i;
u_int32_t cmd, reg;
sec = regno & 0x100;
regno &= 0xff;
cid = sec? (sc->cd2id << 8) : 0;
reg = sec? YDSXGR_SECSTATUSDATA : YDSXGR_PRISTATUSDATA;
if (sec && cid == 0)
return 0xffffffff;
cmd = YDSXG_AC97READCMD | cid | regno;
ds_wr(sc, YDSXGR_AC97CMDADR, cmd, 2);
if (ds_cdbusy(sc, sec))
return 0xffffffff;
if (sc->type == 11 && sc->rev < 2)
for (i = 0; i < 600; i++)
ds_rd(sc, reg, 2);
return ds_rd(sc, reg, 2);
}
static int
ds_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data)
{
struct sc_info *sc = (struct sc_info *)devinfo;
int sec, cid;
u_int32_t cmd;
sec = regno & 0x100;
regno &= 0xff;
cid = sec? (sc->cd2id << 8) : 0;
if (sec && cid == 0)
return ENXIO;
cmd = YDSXG_AC97WRITECMD | cid | regno;
cmd <<= 16;
cmd |= data;
ds_wr(sc, YDSXGR_AC97CMDDATA, cmd, 4);
return ds_cdbusy(sc, sec);
}
static kobj_method_t ds_ac97_methods[] = {
KOBJMETHOD(ac97_init, ds_initcd),
KOBJMETHOD(ac97_read, ds_rdcd),
KOBJMETHOD(ac97_write, ds_wrcd),
{ 0, 0 }
};
AC97_DECLARE(ds_ac97);
/* -------------------------------------------------------------------- */
static void
ds_enadsp(struct sc_info *sc, int on)
{
u_int32_t v, i;
v = on? 1 : 0;
if (on) {
ds_wr(sc, YDSXGR_CONFIG, 0x00000001, 4);
} else {
if (ds_rd(sc, YDSXGR_CONFIG, 4))
ds_wr(sc, YDSXGR_CONFIG, 0x00000000, 4);
i = YDSXG_WORKBITTIMEOUT;
while (i > 0) {
if (!(ds_rd(sc, YDSXGR_CONFIG, 4) & 0x00000002))
break;
i--;
}
}
}
static volatile struct pbank *
ds_allocpslot(struct sc_info *sc)
{
int slot;
if (sc->pslotfree > 63)
return NULL;
slot = sc->pslotfree++;
return sc->pbank[slot * 2];
}
static int
ds_initpbank(volatile struct pbank *pb, int ch, int b16, int stereo, u_int32_t rate, void *base, u_int32_t len)
{
u_int32_t lv[] = {1, 1, 0, 0, 0};
u_int32_t rv[] = {1, 0, 1, 0, 0};
u_int32_t e1[] = {0, 0, 0, 0, 0};
u_int32_t e2[] = {1, 0, 0, 1, 0};
u_int32_t e3[] = {1, 0, 0, 0, 1};
int ss, i;
u_int32_t delta;
struct {
int rate, fK, fQ;
} speedinfo[] = {
{ 100, 0x00570000, 0x35280000},
{ 2000, 0x06aa0000, 0x34a70000},
{ 8000, 0x18b20000, 0x32020000},
{11025, 0x20930000, 0x31770000},
{16000, 0x2b9a0000, 0x31390000},
{22050, 0x35a10000, 0x31c90000},
{32000, 0x3eaa0000, 0x33d00000},
/* {44100, 0x04646000, 0x370a0000},
*/ {48000, 0x40000000, 0x40000000},
};
ss = b16? 1 : 0;
ss += stereo? 1 : 0;
delta = (65536 * rate) / 48000;
i = 0;
while (i < 7 && speedinfo[i].rate < rate)
i++;
pb->Format = stereo? 0x00010000 : 0;
pb->Format |= b16? 0 : 0x80000000;
pb->Format |= (stereo && (ch == 2 || ch == 4))? 0x00000001 : 0;
pb->LoopDefault = 0;
pb->PgBase = base? vtophys(base) : 0;
pb->PgLoop = 0;
pb->PgLoopEnd = len >> ss;
pb->PgLoopFrac = 0;
pb->Status = 0;
pb->NumOfFrames = 0;
pb->LoopCount = 0;
pb->PgStart = 0;
pb->PgStartFrac = 0;
pb->PgDelta = pb->PgDeltaEnd = delta << 12;
pb->LpfQ = speedinfo[i].fQ;
pb->LpfK = pb->LpfKEnd = speedinfo[i].fK;
pb->LpfD1 = pb->LpfD2 = 0;
pb->EgGain = pb->EgGainEnd = 0x40000000;
pb->LchGain = pb->LchGainEnd = lv[ch] * 0x40000000;
pb->RchGain = pb->RchGainEnd = rv[ch] * 0x40000000;
pb->Effect1Gain = pb->Effect1GainEnd = e1[ch] * 0x40000000;
pb->Effect2Gain = pb->Effect2GainEnd = e2[ch] * 0x40000000;
pb->Effect3Gain = pb->Effect3GainEnd = e3[ch] * 0x40000000;
return 0;
}
static void
ds_enapslot(struct sc_info *sc, int slot, int go)
{
wrl(sc, &sc->pbase[slot + 1], go? (sc->pbankbase + 2 * slot * sc->pbanksize) : 0);
/* printf("pbase[%d] = 0x%x\n", slot + 1, go? (sc->pbankbase + 2 * slot * sc->pbanksize) : 0); */
}
static void
ds_setuppch(struct sc_pchinfo *ch)
{
int stereo, b16, c, sz;
void *buf;
stereo = (ch->fmt & AFMT_STEREO)? 1 : 0;
b16 = (ch->fmt & AFMT_16BIT)? 1 : 0;
c = stereo? 1 : 0;
buf = sndbuf_getbuf(ch->buffer);
sz = sndbuf_getsize(ch->buffer);
ds_initpbank(ch->lslot, c, stereo, b16, ch->spd, buf, sz);
ds_initpbank(ch->lslot + 1, c, stereo, b16, ch->spd, buf, sz);
ds_initpbank(ch->rslot, 2, stereo, b16, ch->spd, buf, sz);
ds_initpbank(ch->rslot + 1, 2, stereo, b16, ch->spd, buf, sz);
}
static void
ds_setuprch(struct sc_rchinfo *ch)
{
struct sc_info *sc = ch->parent;
int stereo, b16, i, sz, pri;
u_int32_t x, y;
void *buf;
stereo = (ch->fmt & AFMT_STEREO)? 1 : 0;
b16 = (ch->fmt & AFMT_16BIT)? 1 : 0;
buf = sndbuf_getbuf(ch->buffer);
sz = sndbuf_getsize(ch->buffer);
pri = (ch->num == DS1_RECPRIMARY)? 1 : 0;
for (i = 0; i < 2; i++) {
ch->slot[i].PgBase = vtophys(buf);
ch->slot[i].PgLoopEnd = sz;
ch->slot[i].PgStart = 0;
ch->slot[i].NumOfLoops = 0;
}
x = (b16? 0x00 : 0x01) | (stereo? 0x02 : 0x00);
y = (48000 * 4096) / ch->spd;
y--;
/* printf("pri = %d, x = %d, y = %d\n", pri, x, y); */
ds_wr(sc, pri? YDSXGR_ADCFORMAT : YDSXGR_RECFORMAT, x, 4);
ds_wr(sc, pri? YDSXGR_ADCSLOTSR : YDSXGR_RECSLOTSR, y, 4);
}
/* -------------------------------------------------------------------- */
/* play channel interface */
static void *
ds1pchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir)
{
struct sc_info *sc = devinfo;
struct sc_pchinfo *ch;
KASSERT(dir == PCMDIR_PLAY, ("ds1pchan_init: bad direction"));
ch = &sc->pch[sc->pchn++];
ch->buffer = b;
ch->parent = sc;
ch->channel = c;
ch->dir = dir;
ch->fmt = AFMT_U8;
ch->spd = 8000;
ch->run = 0;
if (sndbuf_alloc(ch->buffer, sc->parent_dmat, DS1_BUFFSIZE) == -1)
return NULL;
else {
ch->lsnum = sc->pslotfree;
ch->lslot = ds_allocpslot(sc);
ch->rsnum = sc->pslotfree;
ch->rslot = ds_allocpslot(sc);
ds_setuppch(ch);
return ch;
}
}
static int
ds1pchan_setformat(kobj_t obj, void *data, u_int32_t format)
{
struct sc_pchinfo *ch = data;
ch->fmt = format;
return 0;
}
static int
ds1pchan_setspeed(kobj_t obj, void *data, u_int32_t speed)
{
struct sc_pchinfo *ch = data;
ch->spd = speed;
return speed;
}
static int
ds1pchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize)
{
struct sc_pchinfo *ch = data;
int drate;
/* irq rate is fixed at 187.5hz */
drate = ch->spd * sndbuf_getbps(ch->buffer);
blocksize = (drate << 8) / DS1_IRQHZ;
sndbuf_resize(ch->buffer, DS1_BUFFSIZE / blocksize, blocksize);
return blocksize;
}
/* semantic note: must start at beginning of buffer */
static int
ds1pchan_trigger(kobj_t obj, void *data, int go)
{
struct sc_pchinfo *ch = data;
struct sc_info *sc = ch->parent;
int stereo;
if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD)
return 0;
stereo = (ch->fmt & AFMT_STEREO)? 1 : 0;
if (go == PCMTRIG_START) {
ch->run = 1;
ds_setuppch(ch);
ds_enapslot(sc, ch->lsnum, 1);
ds_enapslot(sc, ch->rsnum, stereo);
snd_mtxlock(sc->lock);
ds_wr(sc, YDSXGR_MODE, 0x00000003, 4);
snd_mtxunlock(sc->lock);
} else {
ch->run = 0;
/* ds_setuppch(ch); */
ds_enapslot(sc, ch->lsnum, 0);
ds_enapslot(sc, ch->rsnum, 0);
}
return 0;
}
static int
ds1pchan_getptr(kobj_t obj, void *data)
{
struct sc_pchinfo *ch = data;
struct sc_info *sc = ch->parent;
volatile struct pbank *bank;
int ss;
u_int32_t ptr;
ss = (ch->fmt & AFMT_STEREO)? 1 : 0;
ss += (ch->fmt & AFMT_16BIT)? 1 : 0;
bank = ch->lslot + sc->currbank;
/* printf("getptr: %d\n", bank->PgStart << ss); */
ptr = bank->PgStart;
ptr <<= ss;
return ptr;
}
static struct pcmchan_caps *
ds1pchan_getcaps(kobj_t obj, void *data)
{
return &ds_playcaps;
}
static kobj_method_t ds1pchan_methods[] = {
KOBJMETHOD(channel_init, ds1pchan_init),
KOBJMETHOD(channel_setformat, ds1pchan_setformat),
KOBJMETHOD(channel_setspeed, ds1pchan_setspeed),
KOBJMETHOD(channel_setblocksize, ds1pchan_setblocksize),
KOBJMETHOD(channel_trigger, ds1pchan_trigger),
KOBJMETHOD(channel_getptr, ds1pchan_getptr),
KOBJMETHOD(channel_getcaps, ds1pchan_getcaps),
{ 0, 0 }
};
CHANNEL_DECLARE(ds1pchan);
/* -------------------------------------------------------------------- */
/* record channel interface */
static void *
ds1rchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir)
{
struct sc_info *sc = devinfo;
struct sc_rchinfo *ch;
KASSERT(dir == PCMDIR_REC, ("ds1rchan_init: bad direction"));
ch = &sc->rch[sc->rchn];
ch->num = sc->rchn++;
ch->buffer = b;
ch->parent = sc;
ch->channel = c;
ch->dir = dir;
ch->fmt = AFMT_U8;
ch->spd = 8000;
if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 4096) == -1)
return NULL;
else {
ch->slot = (ch->num == DS1_RECPRIMARY)? sc->rbank + 2: sc->rbank;
ds_setuprch(ch);
return ch;
}
}
static int
ds1rchan_setformat(kobj_t obj, void *data, u_int32_t format)
{
struct sc_rchinfo *ch = data;
ch->fmt = format;
return 0;
}
static int
ds1rchan_setspeed(kobj_t obj, void *data, u_int32_t speed)
{
struct sc_rchinfo *ch = data;
ch->spd = speed;
return speed;
}
static int
ds1rchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize)
{
struct sc_rchinfo *ch = data;
int drate;
/* irq rate is fixed at 187.5hz */
drate = ch->spd * sndbuf_getbps(ch->buffer);
blocksize = (drate << 8) / DS1_IRQHZ;
sndbuf_resize(ch->buffer, DS1_BUFFSIZE / blocksize, blocksize);
return blocksize;
}
/* semantic note: must start at beginning of buffer */
static int
ds1rchan_trigger(kobj_t obj, void *data, int go)
{
struct sc_rchinfo *ch = data;
struct sc_info *sc = ch->parent;
u_int32_t x;
if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD)
return 0;
if (go == PCMTRIG_START) {
ch->run = 1;
ds_setuprch(ch);
snd_mtxlock(sc->lock);
x = ds_rd(sc, YDSXGR_MAPOFREC, 4);
x |= (ch->num == DS1_RECPRIMARY)? 0x02 : 0x01;
ds_wr(sc, YDSXGR_MAPOFREC, x, 4);
ds_wr(sc, YDSXGR_MODE, 0x00000003, 4);
snd_mtxunlock(sc->lock);
} else {
ch->run = 0;
snd_mtxlock(sc->lock);
x = ds_rd(sc, YDSXGR_MAPOFREC, 4);
x &= ~((ch->num == DS1_RECPRIMARY)? 0x02 : 0x01);
ds_wr(sc, YDSXGR_MAPOFREC, x, 4);
snd_mtxunlock(sc->lock);
}
return 0;
}
static int
ds1rchan_getptr(kobj_t obj, void *data)
{
struct sc_rchinfo *ch = data;
struct sc_info *sc = ch->parent;
return ch->slot[sc->currbank].PgStart;
}
static struct pcmchan_caps *
ds1rchan_getcaps(kobj_t obj, void *data)
{
return &ds_reccaps;
}
static kobj_method_t ds1rchan_methods[] = {
KOBJMETHOD(channel_init, ds1rchan_init),
KOBJMETHOD(channel_setformat, ds1rchan_setformat),
KOBJMETHOD(channel_setspeed, ds1rchan_setspeed),
KOBJMETHOD(channel_setblocksize, ds1rchan_setblocksize),
KOBJMETHOD(channel_trigger, ds1rchan_trigger),
KOBJMETHOD(channel_getptr, ds1rchan_getptr),
KOBJMETHOD(channel_getcaps, ds1rchan_getcaps),
{ 0, 0 }
};
CHANNEL_DECLARE(ds1rchan);
/* -------------------------------------------------------------------- */
/* The interrupt handler */
static void
ds_intr(void *p)
{
struct sc_info *sc = (struct sc_info *)p;
u_int32_t i, x;
snd_mtxlock(sc->lock);
i = ds_rd(sc, YDSXGR_STATUS, 4);
if (i & 0x00008000)
device_printf(sc->dev, "timeout irq\n");
if (i & 0x80008000) {
ds_wr(sc, YDSXGR_STATUS, i & 0x80008000, 4);
sc->currbank = ds_rd(sc, YDSXGR_CTRLSELECT, 4) & 0x00000001;
x = 0;
for (i = 0; i < DS1_CHANS; i++) {
if (sc->pch[i].run) {
x = 1;
chn_intr(sc->pch[i].channel);
}
}
for (i = 0; i < 2; i++) {
if (sc->rch[i].run) {
x = 1;
chn_intr(sc->rch[i].channel);
}
}
i = ds_rd(sc, YDSXGR_MODE, 4);
if (x)
ds_wr(sc, YDSXGR_MODE, i | 0x00000002, 4);
}
snd_mtxunlock(sc->lock);
}
/* -------------------------------------------------------------------- */
/*
* Probe and attach the card
*/
static void
ds_setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error)
{
struct sc_info *sc = arg;
sc->ctrlbase = error? 0 : (u_int32_t)segs->ds_addr;
if (bootverbose) {
printf("ds1: setmap (%lx, %lx), nseg=%d, error=%d\n",
(unsigned long)segs->ds_addr, (unsigned long)segs->ds_len,
nseg, error);
}
}
static int
ds_init(struct sc_info *sc)
{
int i;
u_int32_t *ci, r, pcs, rcs, ecs, ws, memsz, cb;
u_int8_t *t;
void *buf;
ci = ds_devs[sc->type].mcode;
ds_wr(sc, YDSXGR_NATIVEDACOUTVOL, 0x00000000, 4);
ds_enadsp(sc, 0);
ds_wr(sc, YDSXGR_MODE, 0x00010000, 4);
ds_wr(sc, YDSXGR_MODE, 0x00000000, 4);
ds_wr(sc, YDSXGR_MAPOFREC, 0x00000000, 4);
ds_wr(sc, YDSXGR_MAPOFEFFECT, 0x00000000, 4);
ds_wr(sc, YDSXGR_PLAYCTRLBASE, 0x00000000, 4);
ds_wr(sc, YDSXGR_RECCTRLBASE, 0x00000000, 4);
ds_wr(sc, YDSXGR_EFFCTRLBASE, 0x00000000, 4);
r = ds_rd(sc, YDSXGR_GLOBALCTRL, 2);
ds_wr(sc, YDSXGR_GLOBALCTRL, r & ~0x0007, 2);
for (i = 0; i < YDSXG_DSPLENGTH; i += 4)
ds_wr(sc, YDSXGR_DSPINSTRAM + i, DspInst[i >> 2], 4);
for (i = 0; i < YDSXG_CTRLLENGTH; i += 4)
ds_wr(sc, YDSXGR_CTRLINSTRAM + i, ci[i >> 2], 4);
ds_enadsp(sc, 1);
pcs = 0;
for (i = 100; i > 0; i--) {
pcs = ds_rd(sc, YDSXGR_PLAYCTRLSIZE, 4) << 2;
if (pcs == sizeof(struct pbank))
break;
DELAY(1000);
}
if (pcs != sizeof(struct pbank)) {
device_printf(sc->dev, "preposterous playctrlsize (%d)\n", pcs);
return -1;
}
rcs = ds_rd(sc, YDSXGR_RECCTRLSIZE, 4) << 2;
ecs = ds_rd(sc, YDSXGR_EFFCTRLSIZE, 4) << 2;
ws = ds_rd(sc, YDSXGR_WORKSIZE, 4) << 2;
memsz = 64 * 2 * pcs + 2 * 2 * rcs + 5 * 2 * ecs + ws;
memsz += (64 + 1) * 4;
if (sc->regbase == NULL) {
if (bus_dmamem_alloc(sc->parent_dmat, &buf, BUS_DMA_NOWAIT, &sc->map))
return -1;
if (bus_dmamap_load(sc->parent_dmat, sc->map, buf, memsz, ds_setmap, sc, 0)
|| !sc->ctrlbase) {
device_printf(sc->dev, "pcs=%d, rcs=%d, ecs=%d, ws=%d, memsz=%d\n",
pcs, rcs, ecs, ws, memsz);
return -1;
}
sc->regbase = buf;
} else
buf = sc->regbase;
cb = 0;
t = buf;
ds_wr(sc, YDSXGR_WORKBASE, sc->ctrlbase + cb, 4);
cb += ws;
sc->pbase = (u_int32_t *)(t + cb);
/* printf("pbase = %p -> 0x%x\n", sc->pbase, sc->ctrlbase + cb); */
ds_wr(sc, YDSXGR_PLAYCTRLBASE, sc->ctrlbase + cb, 4);
cb += (64 + 1) * 4;
sc->rbank = (struct rbank *)(t + cb);
ds_wr(sc, YDSXGR_RECCTRLBASE, sc->ctrlbase + cb, 4);
cb += 2 * 2 * rcs;
ds_wr(sc, YDSXGR_EFFCTRLBASE, sc->ctrlbase + cb, 4);
cb += 5 * 2 * ecs;
sc->pbankbase = sc->ctrlbase + cb;
sc->pbanksize = pcs;
for (i = 0; i < 64; i++) {
wrl(sc, &sc->pbase[i + 1], 0);
sc->pbank[i * 2] = (struct pbank *)(t + cb);
/* printf("pbank[%d] = %p -> 0x%x; ", i * 2, (struct pbank *)(t + cb), sc->ctrlbase + cb - vtophys(t + cb)); */
cb += pcs;
sc->pbank[i * 2 + 1] = (struct pbank *)(t + cb);
/* printf("pbank[%d] = %p -> 0x%x\n", i * 2 + 1, (struct pbank *)(t + cb), sc->ctrlbase + cb - vtophys(t + cb)); */
cb += pcs;
}
wrl(sc, &sc->pbase[0], DS1_CHANS * 2);
sc->pchn = sc->rchn = 0;
ds_wr(sc, YDSXGR_NATIVEDACOUTVOL, 0x3fff3fff, 4);
ds_wr(sc, YDSXGR_NATIVEADCINVOL, 0x3fff3fff, 4);
ds_wr(sc, YDSXGR_NATIVEDACINVOL, 0x3fff3fff, 4);
return 0;
}
static int
ds_uninit(struct sc_info *sc)
{
ds_wr(sc, YDSXGR_NATIVEDACOUTVOL, 0x00000000, 4);
ds_wr(sc, YDSXGR_NATIVEADCINVOL, 0, 4);
ds_wr(sc, YDSXGR_NATIVEDACINVOL, 0, 4);
ds_enadsp(sc, 0);
ds_wr(sc, YDSXGR_MODE, 0x00010000, 4);
ds_wr(sc, YDSXGR_MAPOFREC, 0x00000000, 4);
ds_wr(sc, YDSXGR_MAPOFEFFECT, 0x00000000, 4);
ds_wr(sc, YDSXGR_PLAYCTRLBASE, 0x00000000, 4);
ds_wr(sc, YDSXGR_RECCTRLBASE, 0x00000000, 4);
ds_wr(sc, YDSXGR_EFFCTRLBASE, 0x00000000, 4);
ds_wr(sc, YDSXGR_GLOBALCTRL, 0, 2);
bus_dmamap_unload(sc->parent_dmat, sc->map);
bus_dmamem_free(sc->parent_dmat, sc->regbase, sc->map);
return 0;
}
static int
ds_finddev(u_int32_t dev, u_int32_t subdev)
{
int i;
for (i = 0; ds_devs[i].dev; i++) {
if (ds_devs[i].dev == dev &&
(ds_devs[i].subdev == subdev || ds_devs[i].subdev == 0))
return i;
}
return -1;
}
static int
ds_pci_probe(device_t dev)
{
int i;
u_int32_t subdev;
subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev);
i = ds_finddev(pci_get_devid(dev), subdev);
if (i >= 0) {
device_set_desc(dev, ds_devs[i].name);
return 0;
} else
return ENXIO;
}
static int
ds_pci_attach(device_t dev)
{
u_int32_t data;
u_int32_t subdev, i;
struct sc_info *sc;
struct ac97_info *codec = NULL;
char status[SND_STATUSLEN];
if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO)) == NULL) {
device_printf(dev, "cannot allocate softc\n");
return ENXIO;
}
sc->lock = snd_mtxcreate(device_get_nameunit(dev));
sc->dev = dev;
subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev);
sc->type = ds_finddev(pci_get_devid(dev), subdev);
sc->rev = pci_get_revid(dev);
data = pci_read_config(dev, PCIR_COMMAND, 2);
data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN);
pci_write_config(dev, PCIR_COMMAND, data, 2);
data = pci_read_config(dev, PCIR_COMMAND, 2);
sc->regid = PCIR_MAPS;
sc->reg = bus_alloc_resource(dev, SYS_RES_MEMORY, &sc->regid,
0, ~0, 1, RF_ACTIVE);
if (!sc->reg) {
device_printf(dev, "unable to map register space\n");
goto bad;
}
sc->st = rman_get_bustag(sc->reg);
sc->sh = rman_get_bushandle(sc->reg);
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*/65536, /*nsegments*/1, /*maxsegz*/0x3ffff,
/*flags*/0, &sc->parent_dmat) != 0) {
device_printf(dev, "unable to create dma tag\n");
goto bad;
}
sc->regbase = NULL;
if (ds_init(sc) == -1) {
device_printf(dev, "unable to initialize the card\n");
goto bad;
}
codec = AC97_CREATE(dev, sc, ds_ac97);
if (codec == NULL)
goto bad;
mixer_init(dev, ac97_getmixerclass(), codec);
sc->irqid = 0;
sc->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->irqid,
0, ~0, 1, RF_ACTIVE | RF_SHAREABLE);
if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, ds_intr, sc, &sc->ih)) {
device_printf(dev, "unable to map interrupt\n");
goto bad;
}
snprintf(status, SND_STATUSLEN, "at memory 0x%lx irq %ld",
rman_get_start(sc->reg), rman_get_start(sc->irq));
if (pcm_register(dev, sc, DS1_CHANS, 2))
goto bad;
for (i = 0; i < DS1_CHANS; i++)
pcm_addchan(dev, PCMDIR_PLAY, &ds1pchan_class, sc);
for (i = 0; i < 2; i++)
pcm_addchan(dev, PCMDIR_REC, &ds1rchan_class, sc);
pcm_setstatus(dev, status);
return 0;
bad:
if (codec)
ac97_destroy(codec);
if (sc->reg)
bus_release_resource(dev, SYS_RES_MEMORY, sc->regid, sc->reg);
if (sc->ih)
bus_teardown_intr(dev, sc->irq, sc->ih);
if (sc->irq)
bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq);
if (sc->parent_dmat)
bus_dma_tag_destroy(sc->parent_dmat);
if (sc->lock)
snd_mtxfree(sc->lock);
free(sc, M_DEVBUF);
return ENXIO;
}
static int
ds_pci_resume(device_t dev)
{
struct sc_info *sc;
sc = pcm_getdevinfo(dev);
if (ds_init(sc) == -1) {
device_printf(dev, "unable to reinitialize the card\n");
return ENXIO;
}
if (mixer_reinit(dev) == -1) {
device_printf(dev, "unable to reinitialize the mixer\n");
return ENXIO;
}
return 0;
}
static int
ds_pci_detach(device_t dev)
{
int r;
struct sc_info *sc;
r = pcm_unregister(dev);
if (r)
return r;
sc = pcm_getdevinfo(dev);
ds_uninit(sc);
bus_release_resource(dev, SYS_RES_MEMORY, sc->regid, sc->reg);
bus_teardown_intr(dev, sc->irq, sc->ih);
bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq);
bus_dma_tag_destroy(sc->parent_dmat);
snd_mtxfree(sc->lock);
free(sc, M_DEVBUF);
return 0;
}
static device_method_t ds1_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, ds_pci_probe),
DEVMETHOD(device_attach, ds_pci_attach),
DEVMETHOD(device_detach, ds_pci_detach),
DEVMETHOD(device_resume, ds_pci_resume),
{ 0, 0 }
};
static driver_t ds1_driver = {
"pcm",
ds1_methods,
sizeof(struct snddev_info),
};
static devclass_t pcm_devclass;
DRIVER_MODULE(snd_ds1, pci, ds1_driver, pcm_devclass, 0, 0);
MODULE_DEPEND(snd_ds1, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER);
MODULE_VERSION(snd_ds1, 1);