8fb9a995cf
because they bogusly check for defined(INTR_MPSAFE) -- something which never was a #define. Correct the definitions. This make INTR_TYPE_AV finally get used instead of the lower-priority INTR_TYPE_TTY, so it's quite possible some improvement will be had on sound driver performance. It would also make all the drivers marked INTR_MPSAFE actually run without Giant (which does seem to work for me), but: INTR_MPSAFE HAS BEEN REMOVED FROM EVERY SOUND DRIVER! It needs to be re-added on a case-by-case basis since there is no one who will vouch for which sound drivers, if any, willy actually operate correctly without Giant, since there hasn't been testing because of this bug disabling INTR_MPSAFE. Found by: "Yuriy Tsibizov" <Yuriy.Tsibizov@gfk.ru>
1089 lines
27 KiB
C
1089 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.
|
|
*/
|
|
|
|
#include <dev/sound/pcm/sound.h>
|
|
#include <dev/sound/pcm/ac97.h>
|
|
|
|
#include <dev/pci/pcireg.h>
|
|
#include <dev/pci/pcivar.h>
|
|
|
|
#include <dev/sound/pci/ds1.h>
|
|
#include <dev/sound/pci/ds1-fw.h>
|
|
|
|
SND_DECLARE_FILE("$FreeBSD$");
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
#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 buffer_dmat, control_dmat;
|
|
bus_dmamap_t map;
|
|
|
|
struct resource *reg, *irq;
|
|
int regid, irqid;
|
|
void *ih;
|
|
struct mtx *lock;
|
|
|
|
void *regbase;
|
|
u_int32_t *pbase, pbankbase, pbanksize;
|
|
volatile struct pbank *pbank[2 * 64];
|
|
volatile struct rbank *rbank;
|
|
int pslotfree, currbank, pchn, rchn;
|
|
unsigned int bufsz;
|
|
|
|
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, bus_addr_t 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? 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;
|
|
bus_addr_t addr;
|
|
|
|
stereo = (ch->fmt & AFMT_STEREO)? 1 : 0;
|
|
b16 = (ch->fmt & AFMT_16BIT)? 1 : 0;
|
|
c = stereo? 1 : 0;
|
|
addr = sndbuf_getbufaddr(ch->buffer);
|
|
sz = sndbuf_getsize(ch->buffer);
|
|
|
|
ds_initpbank(ch->lslot, c, stereo, b16, ch->spd, addr, sz);
|
|
ds_initpbank(ch->lslot + 1, c, stereo, b16, ch->spd, addr, sz);
|
|
ds_initpbank(ch->rslot, 2, stereo, b16, ch->spd, addr, sz);
|
|
ds_initpbank(ch->rslot + 1, 2, stereo, b16, ch->spd, addr, 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;
|
|
bus_addr_t addr;
|
|
|
|
stereo = (ch->fmt & AFMT_STEREO)? 1 : 0;
|
|
b16 = (ch->fmt & AFMT_16BIT)? 1 : 0;
|
|
addr = sndbuf_getbufaddr(ch->buffer);
|
|
sz = sndbuf_getsize(ch->buffer);
|
|
pri = (ch->num == DS1_RECPRIMARY)? 1 : 0;
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
ch->slot[i].PgBase = addr;
|
|
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->buffer_dmat, sc->bufsz) == -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;
|
|
struct sc_info *sc = ch->parent;
|
|
int drate;
|
|
|
|
/* irq rate is fixed at 187.5hz */
|
|
drate = ch->spd * sndbuf_getbps(ch->buffer);
|
|
blocksize = roundup2((drate << 8) / DS1_IRQHZ, 4);
|
|
sndbuf_resize(ch->buffer, sc->bufsz / 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->buffer_dmat, sc->bufsz) == -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;
|
|
struct sc_info *sc = ch->parent;
|
|
int drate;
|
|
|
|
/* irq rate is fixed at 187.5hz */
|
|
drate = ch->spd * sndbuf_getbps(ch->buffer);
|
|
blocksize = roundup2((drate << 8) / DS1_IRQHZ, 4);
|
|
sndbuf_resize(ch->buffer, sc->bufsz / 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_dma_tag_create(NULL, 2, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR,
|
|
NULL, NULL, memsz, 1, memsz, 0, busdma_lock_mutex,
|
|
&Giant, &sc->control_dmat))
|
|
return -1;
|
|
if (bus_dmamem_alloc(sc->control_dmat, &buf, BUS_DMA_NOWAIT, &sc->map))
|
|
return -1;
|
|
if (bus_dmamap_load(sc->control_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->control_dmat, sc->map);
|
|
bus_dmamem_free(sc->control_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), "sound softc");
|
|
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_BAR(0);
|
|
sc->reg = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->regid,
|
|
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);
|
|
|
|
sc->bufsz = pcm_getbuffersize(dev, 4096, DS1_BUFFSIZE, 65536);
|
|
|
|
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*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff,
|
|
/*flags*/0, /*lockfunc*/busdma_lock_mutex,
|
|
/*lockarg*/&Giant, &sc->buffer_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_any(dev, SYS_RES_IRQ, &sc->irqid,
|
|
RF_ACTIVE | RF_SHAREABLE);
|
|
if (!sc->irq || snd_setup_intr(dev, sc->irq, 0, 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 %s",
|
|
rman_get_start(sc->reg), rman_get_start(sc->irq),PCM_KLDSTRING(snd_ds1));
|
|
|
|
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->buffer_dmat)
|
|
bus_dma_tag_destroy(sc->buffer_dmat);
|
|
if (sc->control_dmat)
|
|
bus_dma_tag_destroy(sc->control_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->buffer_dmat);
|
|
bus_dma_tag_destroy(sc->control_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,
|
|
PCM_SOFTC_SIZE,
|
|
};
|
|
|
|
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);
|