summaryrefslogtreecommitdiffstats
path: root/sys/dev/sound/pci/ds1.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/sound/pci/ds1.c')
-rw-r--r--sys/dev/sound/pci/ds1.c1084
1 files changed, 1084 insertions, 0 deletions
diff --git a/sys/dev/sound/pci/ds1.c b/sys/dev/sound/pci/ds1.c
new file mode 100644
index 0000000..896a8af
--- /dev/null
+++ b/sys/dev/sound/pci/ds1.c
@@ -0,0 +1,1084 @@
+/*
+ * 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 <pci/pcireg.h>
+#include <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, 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->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;
+ 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->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;
+ 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_dma_tag_create(NULL, 2, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR,
+ NULL, NULL, memsz, 1, 1, 0, &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_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);
+
+ 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, &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(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->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);
OpenPOWER on IntegriCloud