summaryrefslogtreecommitdiffstats
path: root/sys
diff options
context:
space:
mode:
authormav <mav@FreeBSD.org>2012-03-01 13:10:18 +0000
committermav <mav@FreeBSD.org>2012-03-01 13:10:18 +0000
commitd844e42d76db648c162bab17d351a353b5793366 (patch)
treefbf090c92a3e174411a3b0627fb5925b6bfd80a3 /sys
parent3e4d13da1e3c73a8b50ee86331dc86105fccc5fa (diff)
downloadFreeBSD-src-d844e42d76db648c162bab17d351a353b5793366.zip
FreeBSD-src-d844e42d76db648c162bab17d351a353b5793366.tar.gz
Add driver for the RME HDSPe AIO/RayDAT sound cards -- snd_hdspe(4).
Cards are expensive and so rare, so leave the driver as module. Submitted by: Ruslan Bukin <br@bsdpad.com> MFC after: 2 weeks
Diffstat (limited to 'sys')
-rw-r--r--sys/conf/NOTES2
-rw-r--r--sys/conf/files2
-rw-r--r--sys/dev/sound/pci/hdspe-pcm.c709
-rw-r--r--sys/dev/sound/pci/hdspe.c410
-rw-r--r--sys/dev/sound/pci/hdspe.h179
-rw-r--r--sys/modules/sound/driver/Makefile4
-rw-r--r--sys/modules/sound/driver/hdspe/Makefile9
7 files changed, 1313 insertions, 2 deletions
diff --git a/sys/conf/NOTES b/sys/conf/NOTES
index 63457cd..6bca3d5 100644
--- a/sys/conf/NOTES
+++ b/sys/conf/NOTES
@@ -2257,6 +2257,7 @@ device sound
# snd_gusc: Gravis UltraSound ISA PnP/non-PnP.
# snd_hda: Intel High Definition Audio (Controller) and
# compatible.
+# snd_hdspe: RME HDSPe AIO and RayDAT.
# snd_ich: Intel ICH AC'97 and some more audio controllers
# embedded in a chipset, for example nVidia
# nForce controllers.
@@ -2296,6 +2297,7 @@ device snd_ess
device snd_fm801
device snd_gusc
device snd_hda
+device snd_hdspe
device snd_ich
device snd_maestro
device snd_maestro3
diff --git a/sys/conf/files b/sys/conf/files
index b8c2261..4a6201a 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1770,6 +1770,8 @@ dev/sound/pci/hda/hdaa_patches.c optional snd_hda pci
dev/sound/pci/hda/hdac.c optional snd_hda pci
dev/sound/pci/hda/hdac_if.m optional snd_hda pci
dev/sound/pci/hda/hdacc.c optional snd_hda pci
+dev/sound/pci/hdspe.c optional snd_hdspe pci
+dev/sound/pci/hdspe-pcm.c optional snd_hdspe pci
dev/sound/pcm/ac97.c optional sound
dev/sound/pcm/ac97_if.m optional sound
dev/sound/pcm/ac97_patch.c optional sound
diff --git a/sys/dev/sound/pci/hdspe-pcm.c b/sys/dev/sound/pci/hdspe-pcm.c
new file mode 100644
index 0000000..6db324c
--- /dev/null
+++ b/sys/dev/sound/pci/hdspe-pcm.c
@@ -0,0 +1,709 @@
+/*-
+ * Copyright (c) 2012 Ruslan Bukin <br@bsdpad.com>
+ * 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, 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.
+ */
+
+/*
+ * RME HDSPe driver for FreeBSD (pcm-part).
+ * Supported cards: AIO, RayDAT.
+ */
+
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/pci/hdspe.h>
+#include <dev/sound/chip.h>
+
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+
+#include <mixer_if.h>
+
+SND_DECLARE_FILE("$FreeBSD$");
+
+struct hdspe_latency {
+ uint32_t n;
+ uint32_t period;
+ float ms;
+};
+
+static struct hdspe_latency latency_map[] = {
+ { 7, 32, 0.7 },
+ { 0, 64, 1.5 },
+ { 1, 128, 3 },
+ { 2, 256, 6 },
+ { 3, 512, 12 },
+ { 4, 1024, 23 },
+ { 5, 2048, 46 },
+ { 6, 4096, 93 },
+
+ { 0, 0, 0 },
+};
+
+struct hdspe_rate {
+ uint32_t speed;
+ uint32_t reg;
+};
+
+static struct hdspe_rate rate_map[] = {
+ { 32000, (HDSPE_FREQ_32000) },
+ { 44100, (HDSPE_FREQ_44100) },
+ { 48000, (HDSPE_FREQ_48000) },
+ { 64000, (HDSPE_FREQ_32000 | HDSPE_FREQ_DOUBLE) },
+ { 88200, (HDSPE_FREQ_44100 | HDSPE_FREQ_DOUBLE) },
+ { 96000, (HDSPE_FREQ_48000 | HDSPE_FREQ_DOUBLE) },
+ { 128000, (HDSPE_FREQ_32000 | HDSPE_FREQ_QUAD) },
+ { 176400, (HDSPE_FREQ_44100 | HDSPE_FREQ_QUAD) },
+ { 192000, (HDSPE_FREQ_48000 | HDSPE_FREQ_QUAD) },
+
+ { 0, 0 },
+};
+
+
+static int
+hdspe_hw_mixer(struct sc_chinfo *ch, unsigned int dst,
+ unsigned int src, unsigned short data)
+{
+ struct sc_pcminfo *scp = ch->parent;
+ struct sc_info *sc = scp->sc;
+ int offs = 0;
+
+ if (ch->dir == PCMDIR_PLAY)
+ offs = 64;
+
+ hdspe_write_4(sc, HDSPE_MIXER_BASE +
+ ((offs + src + 128 * dst) * sizeof(uint32_t)),
+ data & 0xFFFF);
+
+ return 0;
+};
+
+static int
+hdspechan_setgain(struct sc_chinfo *ch)
+{
+
+ hdspe_hw_mixer(ch, ch->lslot, ch->lslot,
+ ch->lvol * HDSPE_MAX_GAIN / 100);
+ hdspe_hw_mixer(ch, ch->rslot, ch->rslot,
+ ch->rvol * HDSPE_MAX_GAIN / 100);
+
+ return 0;
+}
+
+static int
+hdspemixer_init(struct snd_mixer *m)
+{
+ struct sc_pcminfo *scp = mix_getdevinfo(m);
+ struct sc_info *sc = scp->sc;
+ int mask;
+
+ if (sc == NULL)
+ return -1;
+
+ mask = SOUND_MASK_PCM;
+
+ if (scp->hc->play)
+ mask |= SOUND_MASK_VOLUME;
+
+ if (scp->hc->rec)
+ mask |= SOUND_MASK_RECLEV;
+
+ snd_mtxlock(sc->lock);
+ pcm_setflags(scp->dev, pcm_getflags(scp->dev) | SD_F_SOFTPCMVOL);
+ mix_setdevs(m, mask);
+ snd_mtxunlock(sc->lock);
+
+ return 0;
+}
+
+static int
+hdspemixer_set(struct snd_mixer *m, unsigned dev,
+ unsigned left, unsigned right)
+{
+ struct sc_pcminfo *scp = mix_getdevinfo(m);
+ struct sc_chinfo *ch;
+ int i;
+
+#if 0
+ device_printf(scp->dev, "hdspemixer_set() %d %d\n",
+ left,right);
+#endif
+
+ for (i = 0; i < scp->chnum; i++) {
+ ch = &scp->chan[i];
+ if ((dev == SOUND_MIXER_VOLUME && ch->dir == PCMDIR_PLAY) ||
+ (dev == SOUND_MIXER_RECLEV && ch->dir == PCMDIR_REC)) {
+ ch->lvol = left;
+ ch->rvol = right;
+ if (ch->run)
+ hdspechan_setgain(ch);
+ }
+ }
+
+ return 0;
+}
+
+static kobj_method_t hdspemixer_methods[] = {
+ KOBJMETHOD(mixer_init, hdspemixer_init),
+ KOBJMETHOD(mixer_set, hdspemixer_set),
+ KOBJMETHOD_END
+};
+MIXER_DECLARE(hdspemixer);
+
+static void
+hdspechan_enable(struct sc_chinfo *ch, int value)
+{
+ struct sc_pcminfo *scp = ch->parent;
+ struct sc_info *sc = scp->sc;
+ int reg;
+
+ if (ch->dir == PCMDIR_PLAY)
+ reg = HDSPE_OUT_ENABLE_BASE;
+ else
+ reg = HDSPE_IN_ENABLE_BASE;
+
+ ch->run = value;
+
+ hdspe_write_1(sc, reg + (4 * ch->lslot), value);
+ hdspe_write_1(sc, reg + (4 * ch->rslot), value);
+}
+
+static int
+hdspe_running(struct sc_info *sc)
+{
+ struct sc_pcminfo *scp;
+ struct sc_chinfo *ch;
+ int i, j, devcount, err;
+ device_t *devlist;
+
+ if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0)
+ goto bad;
+
+ for (i = 0; i < devcount; i++) {
+ scp = device_get_ivars(devlist[i]);
+ for (j = 0; j < scp->chnum; j++) {
+ ch = &scp->chan[j];
+ if (ch->run)
+ goto bad;
+ }
+ }
+
+ return 0;
+bad:
+
+#if 0
+ device_printf(sc->dev,"hdspe is running\n");
+#endif
+
+ return 1;
+}
+
+static void
+hdspe_start_audio(struct sc_info *sc)
+{
+
+ sc->ctrl_register |= (HDSPE_AUDIO_INT_ENABLE | HDSPE_ENABLE);
+ hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register);
+}
+
+static void
+hdspe_stop_audio(struct sc_info *sc)
+{
+
+ if (hdspe_running(sc) == 1)
+ return;
+
+ sc->ctrl_register &= ~(HDSPE_AUDIO_INT_ENABLE | HDSPE_ENABLE);
+ hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register);
+}
+
+/* Multiplex / demultiplex: 2.0 <-> 2 x 1.0. */
+static void
+buffer_copy(struct sc_chinfo *ch)
+{
+ struct sc_pcminfo *scp = ch->parent;
+ struct sc_info *sc = scp->sc;
+ int length,src,dst;
+ int ssize, dsize;
+ int i;
+
+ length = sndbuf_getready(ch->buffer) /
+ (4 /* Bytes per sample. */ * 2 /* channels */);
+
+ if (ch->dir == PCMDIR_PLAY) {
+ src = sndbuf_getreadyptr(ch->buffer);
+ } else {
+ src = sndbuf_getfreeptr(ch->buffer);
+ }
+
+ src /= 4; /* Bytes per sample. */
+ dst = src / 2; /* Destination buffer twice smaller. */
+
+ ssize = ch->size / 4;
+ dsize = ch->size / 8;
+
+ /*
+ * Use two fragment buffer to avoid sound clipping.
+ */
+
+ for (i = 0; i < sc->period * 2 /* fragments */; i++) {
+ if (ch->dir == PCMDIR_PLAY) {
+ sc->pbuf[dst + HDSPE_CHANBUF_SAMPLES * ch->lslot] =
+ ch->data[src];
+ sc->pbuf[dst + HDSPE_CHANBUF_SAMPLES * ch->rslot] =
+ ch->data[src + 1];
+
+ } else {
+ ch->data[src] =
+ sc->rbuf[dst + HDSPE_CHANBUF_SAMPLES * ch->lslot];
+ ch->data[src+1] =
+ sc->rbuf[dst + HDSPE_CHANBUF_SAMPLES * ch->rslot];
+ }
+
+ dst+=1;
+ dst %= dsize;
+ src+=2;
+ src %= ssize;
+ }
+}
+
+static int
+clean(struct sc_chinfo *ch){
+ struct sc_pcminfo *scp = ch->parent;
+ struct sc_info *sc = scp->sc;
+ uint32_t *buf = sc->rbuf;
+
+ if (ch->dir == PCMDIR_PLAY) {
+ buf = sc->pbuf;
+ }
+
+ bzero(buf + HDSPE_CHANBUF_SAMPLES * ch->lslot, HDSPE_CHANBUF_SIZE);
+ bzero(buf + HDSPE_CHANBUF_SAMPLES * ch->rslot, HDSPE_CHANBUF_SIZE);
+
+ return 0;
+}
+
+
+/* Channel interface. */
+static void *
+hdspechan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
+ struct pcm_channel *c, int dir)
+{
+ struct sc_pcminfo *scp = devinfo;
+ struct sc_info *sc = scp->sc;
+ struct sc_chinfo *ch;
+ int num;
+
+ snd_mtxlock(sc->lock);
+ num = scp->chnum;
+
+ ch = &scp->chan[num];
+ ch->lslot = scp->hc->left;
+ ch->rslot = scp->hc->right;
+ ch->run = 0;
+ ch->lvol = 0;
+ ch->rvol = 0;
+
+ ch->size = HDSPE_CHANBUF_SIZE * 2 /* slots */;
+ ch->data = malloc(ch->size, M_HDSPE, M_NOWAIT);
+
+ ch->buffer = b;
+ ch->channel = c;
+ ch->parent = scp;
+
+ ch->dir = dir;
+
+ snd_mtxunlock(sc->lock);
+
+ if (sndbuf_setup(ch->buffer, ch->data, ch->size) != 0) {
+ device_printf(scp->dev, "Can't setup sndbuf.\n");
+ return NULL;
+ }
+
+ return ch;
+}
+
+static int
+hdspechan_trigger(kobj_t obj, void *data, int go)
+{
+ struct sc_chinfo *ch = data;
+ struct sc_pcminfo *scp = ch->parent;
+ struct sc_info *sc = scp->sc;
+
+ snd_mtxlock(sc->lock);
+ switch (go) {
+ case PCMTRIG_START:
+#if 0
+ device_printf(scp->dev, "hdspechan_trigger(): start\n");
+#endif
+ hdspechan_enable(ch, 1);
+ hdspechan_setgain(ch);
+ hdspe_start_audio(sc);
+ break;
+
+ case PCMTRIG_STOP:
+ case PCMTRIG_ABORT:
+#if 0
+ device_printf(scp->dev, "hdspechan_trigger(): stop or abort\n");
+#endif
+ clean(ch);
+ hdspechan_enable(ch, 0);
+ hdspe_stop_audio(sc);
+ break;
+
+ case PCMTRIG_EMLDMAWR:
+ case PCMTRIG_EMLDMARD:
+ if(ch->run)
+ buffer_copy(ch);
+ break;
+ }
+
+ snd_mtxunlock(sc->lock);
+
+ return 0;
+}
+
+static uint32_t
+hdspechan_getptr(kobj_t obj, void *data)
+{
+ struct sc_chinfo *ch = data;
+ struct sc_pcminfo *scp = ch->parent;
+ struct sc_info *sc = scp->sc;
+ uint32_t ret, pos;
+
+ snd_mtxlock(sc->lock);
+ ret = hdspe_read_2(sc, HDSPE_STATUS_REG);
+ snd_mtxunlock(sc->lock);
+
+ pos = ret & HDSPE_BUF_POSITION_MASK;
+ pos *= 2; /* Hardbuf twice bigger. */
+
+ return pos;
+}
+
+static int
+hdspechan_free(kobj_t obj, void *data)
+{
+ struct sc_chinfo *ch = data;
+ struct sc_pcminfo *scp = ch->parent;
+ struct sc_info *sc = scp->sc;
+
+#if 0
+ device_printf(scp->dev, "hdspechan_free()\n");
+#endif
+ snd_mtxlock(sc->lock);
+ if (ch->data != NULL) {
+ free(ch->data, M_HDSPE);
+ ch->data = NULL;
+ }
+ snd_mtxunlock(sc->lock);
+
+ return 0;
+}
+
+static int
+hdspechan_setformat(kobj_t obj, void *data, uint32_t format)
+{
+ struct sc_chinfo *ch = data;
+
+#if 0
+ struct sc_pcminfo *scp = ch->parent;
+ device_printf(scp->dev, "hdspechan_setformat(%d)\n", format);
+#endif
+
+ ch->format = format;
+
+ return 0;
+}
+
+static uint32_t
+hdspechan_setspeed(kobj_t obj, void *data, uint32_t speed)
+{
+ struct sc_chinfo *ch = data;
+ struct sc_pcminfo *scp = ch->parent;
+ struct sc_info *sc = scp->sc;
+ struct hdspe_rate *hr = NULL;
+ long long period;
+ int threshold;
+ int i;
+
+#if 0
+ device_printf(scp->dev, "hdspechan_setspeed(%d)\n", speed);
+#endif
+
+ if (hdspe_running(sc) == 1)
+ goto end;
+
+ /* First look for equal frequency. */
+ for (i = 0; rate_map[i].speed != 0; i++) {
+ if (rate_map[i].speed == speed)
+ hr = &rate_map[i];
+ }
+
+ /* If no match, just find nearest. */
+ if (hr == NULL) {
+ for (i = 0; rate_map[i].speed != 0; i++) {
+ hr = &rate_map[i];
+ threshold = hr->speed + ((rate_map[i + 1].speed != 0) ?
+ ((rate_map[i + 1].speed - hr->speed) >> 1) : 0);
+ if (speed < threshold)
+ break;
+ }
+ }
+
+ switch (sc->type) {
+ case RAYDAT:
+ case AIO:
+ period = HDSPE_FREQ_AIO;
+ break;
+ default:
+ /* Unsupported card. */
+ goto end;
+ }
+
+ /* Write frequency on the device. */
+ sc->ctrl_register &= ~HDSPE_FREQ_MASK;
+ sc->ctrl_register |= hr->reg;
+ hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register);
+
+ speed = hr->speed;
+ if (speed > 96000)
+ speed /= 4;
+ else if (speed > 48000)
+ speed /= 2;
+
+ /* Set DDS value. */
+ period /= speed;
+ hdspe_write_4(sc, HDSPE_FREQ_REG, period);
+
+ sc->speed = hr->speed;
+end:
+ return sc->speed;
+}
+
+static uint32_t
+hdspechan_setblocksize(kobj_t obj, void *data, uint32_t blocksize)
+{
+ struct sc_chinfo *ch = data;
+ struct sc_pcminfo *scp = ch->parent;
+ struct sc_info *sc = scp->sc;
+ struct hdspe_latency *hl = NULL;
+ int threshold;
+ int i;
+
+#if 0
+ device_printf(scp->dev, "hdspechan_setblocksize(%d)\n", blocksize);
+#endif
+
+ if (hdspe_running(sc) == 1)
+ goto end;
+
+ if (blocksize > HDSPE_LAT_BYTES_MAX)
+ blocksize = HDSPE_LAT_BYTES_MAX;
+ else if (blocksize < HDSPE_LAT_BYTES_MIN)
+ blocksize = HDSPE_LAT_BYTES_MIN;
+
+ blocksize /= 4 /* samples */;
+
+ /* First look for equal latency. */
+ for (i = 0; latency_map[i].period != 0; i++) {
+ if (latency_map[i].period == blocksize) {
+ hl = &latency_map[i];
+ }
+ }
+
+ /* If no match, just find nearest. */
+ if (hl == NULL) {
+ for (i = 0; latency_map[i].period != 0; i++) {
+ hl = &latency_map[i];
+ threshold = hl->period + ((latency_map[i + 1].period != 0) ?
+ ((latency_map[i + 1].period - hl->period) >> 1) : 0);
+ if (blocksize < threshold)
+ break;
+ }
+ }
+
+ snd_mtxlock(sc->lock);
+ sc->ctrl_register &= ~HDSPE_LAT_MASK;
+ sc->ctrl_register |= hdspe_encode_latency(hl->n);
+ hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register);
+ sc->period = hl->period;
+ snd_mtxunlock(sc->lock);
+
+#if 0
+ device_printf(scp->dev, "New period=%d\n", sc->period);
+#endif
+
+ sndbuf_resize(ch->buffer, (HDSPE_CHANBUF_SIZE * 2) / (sc->period * 4),
+ (sc->period * 4));
+end:
+ return sndbuf_getblksz(ch->buffer);
+}
+
+static uint32_t hdspe_rfmt[] = {
+ SND_FORMAT(AFMT_S32_LE, 2, 0),
+ 0
+};
+
+static struct pcmchan_caps hdspe_rcaps = {32000, 192000, hdspe_rfmt, 0};
+
+static uint32_t hdspe_pfmt[] = {
+ SND_FORMAT(AFMT_S32_LE, 2, 0),
+ 0
+};
+
+static struct pcmchan_caps hdspe_pcaps = {32000, 192000, hdspe_pfmt, 0};
+
+static struct pcmchan_caps *
+hdspechan_getcaps(kobj_t obj, void *data)
+{
+ struct sc_chinfo *ch = data;
+
+#if 0
+ struct sc_pcminfo *scl = ch->parent;
+ device_printf(scp->dev, "hdspechan_getcaps()\n");
+#endif
+
+ return (ch->dir == PCMDIR_PLAY) ?
+ &hdspe_pcaps : &hdspe_rcaps;
+}
+
+static kobj_method_t hdspechan_methods[] = {
+ KOBJMETHOD(channel_init, hdspechan_init),
+ KOBJMETHOD(channel_free, hdspechan_free),
+ KOBJMETHOD(channel_setformat, hdspechan_setformat),
+ KOBJMETHOD(channel_setspeed, hdspechan_setspeed),
+ KOBJMETHOD(channel_setblocksize, hdspechan_setblocksize),
+ KOBJMETHOD(channel_trigger, hdspechan_trigger),
+ KOBJMETHOD(channel_getptr, hdspechan_getptr),
+ KOBJMETHOD(channel_getcaps, hdspechan_getcaps),
+ KOBJMETHOD_END
+};
+CHANNEL_DECLARE(hdspechan);
+
+
+static int
+hdspe_pcm_probe(device_t dev)
+{
+
+#if 0
+ device_printf(dev,"hdspe_pcm_probe()\n");
+#endif
+
+ return 0;
+}
+
+static uint32_t
+hdspe_pcm_intr(struct sc_pcminfo *scp) {
+ struct sc_chinfo *ch;
+ struct sc_info *sc = scp->sc;
+ int i;
+
+ for (i = 0; i < scp->chnum; i++) {
+ ch = &scp->chan[i];
+ snd_mtxunlock(sc->lock);
+ chn_intr(ch->channel);
+ snd_mtxlock(sc->lock);
+ }
+
+ return 0;
+}
+
+static int
+hdspe_pcm_attach(device_t dev)
+{
+ struct sc_pcminfo *scp;
+ char status[SND_STATUSLEN];
+ char desc[64];
+ int i, err;
+
+ scp = device_get_ivars(dev);
+ scp->ih = &hdspe_pcm_intr;
+
+ bzero(desc, sizeof(desc));
+ snprintf(desc, sizeof(desc), "HDSPe AIO [%s]", scp->hc->descr);
+ device_set_desc_copy(dev, desc);
+
+ /*
+ * We don't register interrupt handler with snd_setup_intr
+ * in pcm device. Mark pcm device as MPSAFE manually.
+ */
+ pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE);
+
+ err = pcm_register(dev, scp, scp->hc->play, scp->hc->rec);
+ if (err) {
+ device_printf(dev, "Can't register pcm.\n");
+ return ENXIO;
+ }
+
+ scp->chnum = 0;
+ for (i = 0; i < scp->hc->play; i++) {
+ pcm_addchan(dev, PCMDIR_PLAY, &hdspechan_class, scp);
+ scp->chnum++;
+ }
+
+ for (i = 0; i < scp->hc->rec; i++) {
+ pcm_addchan(dev, PCMDIR_REC, &hdspechan_class, scp);
+ scp->chnum++;
+ }
+
+ snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld %s",
+ rman_get_start(scp->sc->cs),
+ rman_get_start(scp->sc->irq),
+ PCM_KLDSTRING(snd_hdspe));
+ pcm_setstatus(dev, status);
+
+ mixer_init(dev, &hdspemixer_class, scp);
+
+ return 0;
+}
+
+static int
+hdspe_pcm_detach(device_t dev)
+{
+ int err;
+
+ err = pcm_unregister(dev);
+ if (err) {
+ device_printf(dev, "Can't unregister device.\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static device_method_t hdspe_pcm_methods[] = {
+ DEVMETHOD(device_probe, hdspe_pcm_probe),
+ DEVMETHOD(device_attach, hdspe_pcm_attach),
+ DEVMETHOD(device_detach, hdspe_pcm_detach),
+ { 0, 0 }
+};
+
+static driver_t hdspe_pcm_driver = {
+ "pcm",
+ hdspe_pcm_methods,
+ PCM_SOFTC_SIZE,
+};
+
+DRIVER_MODULE(snd_hdspe_pcm, hdspe, hdspe_pcm_driver, pcm_devclass, 0, 0);
+MODULE_DEPEND(snd_hdspe, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
+MODULE_VERSION(snd_hdspe, 1);
diff --git a/sys/dev/sound/pci/hdspe.c b/sys/dev/sound/pci/hdspe.c
new file mode 100644
index 0000000..69c372d
--- /dev/null
+++ b/sys/dev/sound/pci/hdspe.c
@@ -0,0 +1,410 @@
+/*-
+ * Copyright (c) 2012 Ruslan Bukin <br@bsdpad.com>
+ * 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, 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.
+ */
+
+/*
+ * RME HDSPe driver for FreeBSD.
+ * Supported cards: AIO, RayDAT.
+ */
+
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/pci/hdspe.h>
+#include <dev/sound/chip.h>
+
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+
+#include <mixer_if.h>
+
+SND_DECLARE_FILE("$FreeBSD$");
+
+static struct hdspe_channel chan_map_aio[] = {
+ { 0, 1, "line", 1, 1 },
+ { 6, 7, "phone", 1, 0 },
+ { 8, 9, "aes", 1, 1 },
+ { 10, 11, "s/pdif", 1, 1 },
+ { 12, 16, "adat", 1, 1 },
+
+ /* Single or double speed. */
+ { 14, 18, "adat", 1, 1 },
+
+ /* Single speed only. */
+ { 13, 15, "adat", 1, 1 },
+ { 17, 19, "adat", 1, 1 },
+
+ { 0, 0, NULL, 0, 0 },
+};
+
+static struct hdspe_channel chan_map_rd[] = {
+ { 0, 1, "aes", 1, 1 },
+ { 2, 3, "s/pdif", 1, 1 },
+ { 4, 5, "adat", 1, 1 },
+ { 6, 7, "adat", 1, 1 },
+ { 8, 9, "adat", 1, 1 },
+ { 10, 11, "adat", 1, 1 },
+
+ /* Single or double speed. */
+ { 12, 13, "adat", 1, 1 },
+ { 14, 15, "adat", 1, 1 },
+ { 16, 17, "adat", 1, 1 },
+ { 18, 19, "adat", 1, 1 },
+
+ /* Single speed only. */
+ { 20, 21, "adat", 1, 1 },
+ { 22, 23, "adat", 1, 1 },
+ { 24, 25, "adat", 1, 1 },
+ { 26, 27, "adat", 1, 1 },
+ { 28, 29, "adat", 1, 1 },
+ { 30, 31, "adat", 1, 1 },
+ { 32, 33, "adat", 1, 1 },
+ { 34, 35, "adat", 1, 1 },
+
+ { 0, 0, NULL, 0, 0 },
+};
+
+static void
+hdspe_intr(void *p)
+{
+ struct sc_info *sc = (struct sc_info *)p;
+ struct sc_pcminfo *scp;
+ device_t *devlist;
+ int devcount, status;
+ int i, err;
+
+ snd_mtxlock(sc->lock);
+
+ status = hdspe_read_1(sc, HDSPE_STATUS_REG);
+ if (status & HDSPE_AUDIO_IRQ_PENDING) {
+ if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0)
+ return;
+
+ for (i = 0; i < devcount; i++) {
+ scp = device_get_ivars(devlist[i]);
+ if (scp->ih != NULL)
+ scp->ih(scp);
+ }
+
+ hdspe_write_1(sc, HDSPE_INTERRUPT_ACK, 0);
+ }
+
+ snd_mtxunlock(sc->lock);
+}
+
+static void
+hdspe_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error)
+{
+#if 0
+ struct sc_info *sc = (struct sc_info *)arg;
+ device_printf(sc->dev, "hdspe_dmapsetmap()\n");
+#endif
+}
+
+static int
+hdspe_alloc_resources(struct sc_info *sc)
+{
+
+ /* Allocate resource. */
+ sc->csid = PCIR_BAR(0);
+ sc->cs = bus_alloc_resource(sc->dev, SYS_RES_MEMORY,
+ &sc->csid, 0, ~0, 1, RF_ACTIVE);
+
+ if (!sc->cs) {
+ device_printf(sc->dev, "Unable to map SYS_RES_MEMORY.\n");
+ return (ENXIO);
+ }
+ sc->cst = rman_get_bustag(sc->cs);
+ sc->csh = rman_get_bushandle(sc->cs);
+
+
+ /* Allocate interrupt resource. */
+ sc->irqid = 0;
+ sc->irq = bus_alloc_resource(sc->dev, SYS_RES_IRQ, &sc->irqid,
+ 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE);
+
+ if (!sc->irq ||
+ bus_setup_intr(sc->dev, sc->irq, INTR_MPSAFE | INTR_TYPE_AV,
+ NULL, hdspe_intr, sc, &sc->ih)) {
+ device_printf(sc->dev, "Unable to alloc interrupt resource.\n");
+ return (ENXIO);
+ }
+
+ /* Allocate DMA resources. */
+ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(sc->dev),
+ /*alignment*/4,
+ /*boundary*/0,
+ /*lowaddr*/BUS_SPACE_MAXADDR_32BIT,
+ /*highaddr*/BUS_SPACE_MAXADDR,
+ /*filter*/NULL,
+ /*filterarg*/NULL,
+ /*maxsize*/2 * HDSPE_DMASEGSIZE,
+ /*nsegments*/2,
+ /*maxsegsz*/HDSPE_DMASEGSIZE,
+ /*flags*/0,
+ /*lockfunc*/busdma_lock_mutex,
+ /*lockarg*/&Giant,
+ /*dmatag*/&sc->dmat) != 0) {
+ device_printf(sc->dev, "Unable to create dma tag.\n");
+ return (ENXIO);
+ }
+
+ sc->bufsize = HDSPE_DMASEGSIZE;
+
+ /* pbuf (play buffer). */
+ if (bus_dmamem_alloc(sc->dmat, (void **)&sc->pbuf,
+ BUS_DMA_NOWAIT, &sc->pmap)) {
+ device_printf(sc->dev, "Can't alloc pbuf.\n");
+ return (ENXIO);
+ }
+
+ if (bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->bufsize,
+ hdspe_dmapsetmap, sc, 0)) {
+ device_printf(sc->dev, "Can't load pbuf.\n");
+ return (ENXIO);
+ }
+
+ /* rbuf (rec buffer). */
+ if (bus_dmamem_alloc(sc->dmat, (void **)&sc->rbuf,
+ BUS_DMA_NOWAIT, &sc->rmap)) {
+ device_printf(sc->dev, "Can't alloc rbuf.\n");
+ return (ENXIO);
+ }
+
+ if (bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->bufsize,
+ hdspe_dmapsetmap, sc, 0)) {
+ device_printf(sc->dev, "Can't load rbuf.\n");
+ return (ENXIO);
+ }
+
+ bzero(sc->pbuf, sc->bufsize);
+ bzero(sc->rbuf, sc->bufsize);
+
+ return (0);
+}
+
+static void
+hdspe_map_dmabuf(struct sc_info *sc)
+{
+ uint32_t paddr,raddr;
+ int i;
+
+ paddr = vtophys(sc->pbuf);
+ raddr = vtophys(sc->rbuf);
+
+ for (i = 0; i < HDSPE_MAX_SLOTS * 16; i++) {
+ hdspe_write_4(sc, HDSPE_PAGE_ADDR_BUF_OUT + 4 * i,
+ paddr + i * 4096);
+ hdspe_write_4(sc, HDSPE_PAGE_ADDR_BUF_IN + 4 * i,
+ raddr + i * 4096);
+ }
+}
+
+static int
+hdspe_probe(device_t dev)
+{
+ uint32_t rev;
+
+ if (pci_get_vendor(dev) == PCI_VENDOR_XILINX &&
+ pci_get_device(dev) == PCI_DEVICE_XILINX_HDSPE) {
+ rev = pci_get_revid(dev);
+ switch (rev) {
+ case PCI_REVISION_AIO:
+ device_set_desc(dev, "RME HDSPe AIO");
+ return 0;
+ case PCI_REVISION_RAYDAT:
+ device_set_desc(dev, "RME HDSPe RayDAT");
+ return 0;
+ }
+ }
+
+ return (ENXIO);
+}
+
+static int
+set_pci_config(device_t dev)
+{
+ uint32_t data;
+
+ pci_enable_busmaster(dev);
+
+ data = pci_get_revid(dev);
+ data |= PCIM_CMD_PORTEN;
+ pci_write_config(dev, PCIR_COMMAND, data, 2);
+
+ return 0;
+}
+
+static int
+hdspe_init(struct sc_info *sc)
+{
+ long long period;
+
+ /* Set defaults. */
+ sc->ctrl_register |= HDSPM_CLOCK_MODE_MASTER;
+
+ /* Set latency. */
+ sc->period = 32;
+ sc->ctrl_register = hdspe_encode_latency(7);
+
+ /* Set rate. */
+ sc->speed = HDSPE_SPEED_DEFAULT;
+ sc->ctrl_register &= ~HDSPE_FREQ_MASK;
+ sc->ctrl_register |= HDSPE_FREQ_MASK_DEFAULT;
+ hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register);
+
+ switch (sc->type) {
+ case RAYDAT:
+ case AIO:
+ period = HDSPE_FREQ_AIO;
+ break;
+ default:
+ return (ENXIO);
+ }
+
+ /* Set DDS value. */
+ period /= sc->speed;
+ hdspe_write_4(sc, HDSPE_FREQ_REG, period);
+
+ /* Other settings. */
+ sc->settings_register = 0;
+ hdspe_write_4(sc, HDSPE_SETTINGS_REG, sc->settings_register);
+
+ return 0;
+}
+
+static int
+hdspe_attach(device_t dev)
+{
+ struct sc_info *sc;
+ struct sc_pcminfo *scp;
+ struct hdspe_channel *chan_map;
+ uint32_t rev;
+ int i, err;
+
+#if 0
+ device_printf(dev, "hdspe_attach()\n");
+#endif
+
+ set_pci_config(dev);
+
+ sc = device_get_softc(dev);
+ sc->lock = snd_mtxcreate(device_get_nameunit(dev),
+ "snd_hdspe softc");
+ sc->dev = dev;
+
+ rev = pci_get_revid(dev);
+ switch (rev) {
+ case PCI_REVISION_AIO:
+ sc->type = AIO;
+ chan_map = chan_map_aio;
+ break;
+ case PCI_REVISION_RAYDAT:
+ sc->type = RAYDAT;
+ chan_map = chan_map_rd;
+ break;
+ default:
+ return ENXIO;
+ }
+
+ /* Allocate resources. */
+ err = hdspe_alloc_resources(sc);
+ if (err) {
+ device_printf(dev, "Unable to allocate system resources.\n");
+ return ENXIO;
+ }
+
+ if (hdspe_init(sc) != 0)
+ return ENXIO;
+
+ for (i = 0; i < HDSPE_MAX_CHANS && chan_map[i].descr != NULL; i++) {
+ scp = malloc(sizeof(struct sc_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO);
+ scp->hc = &chan_map[i];
+ scp->sc = sc;
+ scp->dev = device_add_child(dev, "pcm", -1);
+ device_set_ivars(scp->dev, scp);
+ }
+
+ hdspe_map_dmabuf(sc);
+
+ return 0;
+}
+
+static void
+hdspe_dmafree(struct sc_info *sc)
+{
+
+ bus_dmamap_unload(sc->dmat, sc->rmap);
+ bus_dmamap_unload(sc->dmat, sc->pmap);
+ bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap);
+ bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap);
+ sc->rmap = sc->pmap = NULL;
+ sc->rbuf = sc->pbuf = NULL;
+}
+
+static int
+hdspe_detach(device_t dev)
+{
+ struct sc_info *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+ if (sc == NULL) {
+ device_printf(dev,"Can't detach: softc is null.\n");
+ return 0;
+ }
+
+ err = device_delete_children(dev);
+ if (err)
+ return (err);
+
+ hdspe_dmafree(sc);
+
+ if (sc->ih)
+ bus_teardown_intr(dev, sc->irq, sc->ih);
+ if (sc->dmat)
+ bus_dma_tag_destroy(sc->dmat);
+ if (sc->irq)
+ bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq);
+ if (sc->cs)
+ bus_release_resource(dev, SYS_RES_MEMORY, PCIR_BAR(0), sc->cs);
+ if (sc->lock)
+ snd_mtxfree(sc->lock);
+
+ return 0;
+}
+
+static device_method_t hdspe_methods[] = {
+ DEVMETHOD(device_probe, hdspe_probe),
+ DEVMETHOD(device_attach, hdspe_attach),
+ DEVMETHOD(device_detach, hdspe_detach),
+ { 0, 0 }
+};
+
+static driver_t hdspe_driver = {
+ "hdspe",
+ hdspe_methods,
+ PCM_SOFTC_SIZE,
+};
+
+DRIVER_MODULE(snd_hdspe, pci, hdspe_driver, pcm_devclass, 0, 0);
diff --git a/sys/dev/sound/pci/hdspe.h b/sys/dev/sound/pci/hdspe.h
new file mode 100644
index 0000000..c7be775
--- /dev/null
+++ b/sys/dev/sound/pci/hdspe.h
@@ -0,0 +1,179 @@
+/*-
+ * Copyright (c) 2012 Ruslan Bukin <br@bsdpad.com>
+ * 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, 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.
+ *
+ * $FreeBSD$
+ */
+
+#define PCI_VENDOR_XILINX 0x10ee
+#define PCI_DEVICE_XILINX_HDSPE 0x3fc6 /* AIO, MADI, AES, RayDAT */
+#define PCI_CLASS_REVISION 0x08
+#define PCI_REVISION_AIO 212
+#define PCI_REVISION_RAYDAT 211
+
+#define AIO 0
+#define RAYDAT 1
+
+/* Hardware mixer */
+#define HDSPE_OUT_ENABLE_BASE 512
+#define HDSPE_IN_ENABLE_BASE 768
+#define HDSPE_MIXER_BASE 32768
+#define HDSPE_MAX_GAIN 32768
+
+/* Buffer */
+#define HDSPE_PAGE_ADDR_BUF_OUT 8192
+#define HDSPE_PAGE_ADDR_BUF_IN (HDSPE_PAGE_ADDR_BUF_OUT + 64 * 16 * 4)
+#define HDSPE_BUF_POSITION_MASK 0x000FFC0
+
+/* Frequency */
+#define HDSPE_FREQ_0 (1<<6)
+#define HDSPE_FREQ_1 (1<<7)
+#define HDSPE_FREQ_DOUBLE (1<<8)
+#define HDSPE_FREQ_QUAD (1<<31)
+
+#define HDSPE_FREQ_32000 HDSPE_FREQ_0
+#define HDSPE_FREQ_44100 HDSPE_FREQ_1
+#define HDSPE_FREQ_48000 (HDSPE_FREQ_0 | HDSPE_FREQ_1)
+#define HDSPE_FREQ_MASK (HDSPE_FREQ_0 | HDSPE_FREQ_1 | \
+ HDSPE_FREQ_DOUBLE | HDSPE_FREQ_QUAD)
+#define HDSPE_FREQ_MASK_DEFAULT HDSPE_FREQ_48000
+#define HDSPE_FREQ_REG 256
+#define HDSPE_FREQ_AIO 104857600000000ULL
+
+#define HDSPE_SPEED_DEFAULT 48000
+
+/* Latency */
+#define HDSPE_LAT_0 (1<<1)
+#define HDSPE_LAT_1 (1<<2)
+#define HDSPE_LAT_2 (1<<3)
+#define HDSPE_LAT_MASK (HDSPE_LAT_0 | HDSPE_LAT_1 | HDSPE_LAT_2)
+#define HDSPE_LAT_BYTES_MAX (4096 * 4)
+#define HDSPE_LAT_BYTES_MIN (32 * 4)
+#define hdspe_encode_latency(x) (((x)<<1) & HDSPE_LAT_MASK)
+
+/* Settings */
+#define HDSPE_SETTINGS_REG 0
+#define HDSPE_CONTROL_REG 64
+#define HDSPE_STATUS_REG 0
+#define HDSPE_ENABLE (1<<0)
+#define HDSPM_CLOCK_MODE_MASTER (1<<4)
+
+/* Interrupts */
+#define HDSPE_AUDIO_IRQ_PENDING (1<<0)
+#define HDSPE_AUDIO_INT_ENABLE (1<<5)
+#define HDSPE_INTERRUPT_ACK 96
+
+/* Channels */
+#define HDSPE_MAX_SLOTS 64 /* Mono channels */
+#define HDSPE_MAX_CHANS (HDSPE_MAX_SLOTS / 2) /* Stereo pairs */
+
+#define HDSPE_CHANBUF_SAMPLES (16 * 1024)
+#define HDSPE_CHANBUF_SIZE (4 * HDSPE_CHANBUF_SAMPLES)
+#define HDSPE_DMASEGSIZE (HDSPE_CHANBUF_SIZE * HDSPE_MAX_SLOTS)
+
+struct hdspe_channel {
+ uint32_t left;
+ uint32_t right;
+ char *descr;
+ uint32_t play;
+ uint32_t rec;
+};
+
+static MALLOC_DEFINE(M_HDSPE, "hdspe", "hdspe audio");
+
+/* Channel registers */
+struct sc_chinfo {
+ struct snd_dbuf *buffer;
+ struct pcm_channel *channel;
+ struct sc_pcminfo *parent;
+
+ /* Channel information */
+ uint32_t dir;
+ uint32_t format;
+ uint32_t lslot;
+ uint32_t rslot;
+ uint32_t lvol;
+ uint32_t rvol;
+
+ /* Buffer */
+ uint32_t *data;
+ uint32_t size;
+
+ /* Flags */
+ uint32_t run;
+};
+
+/* PCM device private data */
+struct sc_pcminfo {
+ device_t dev;
+ uint32_t (*ih) (struct sc_pcminfo *scp);
+ uint32_t chnum;
+ struct sc_chinfo chan[HDSPE_MAX_CHANS];
+ struct sc_info *sc;
+ struct hdspe_channel *hc;
+};
+
+/* HDSPe device private data */
+struct sc_info {
+ device_t dev;
+ struct mtx *lock;
+
+ uint32_t ctrl_register;
+ uint32_t settings_register;
+ uint32_t type;
+
+ /* Control/Status register */
+ struct resource *cs;
+ int csid;
+ bus_space_tag_t cst;
+ bus_space_handle_t csh;
+
+ struct resource *irq;
+ int irqid;
+ void *ih;
+ bus_dma_tag_t dmat;
+
+ /* Play/Record DMA buffers */
+ uint32_t *pbuf;
+ uint32_t *rbuf;
+ uint32_t bufsize;
+ bus_dmamap_t pmap;
+ bus_dmamap_t rmap;
+ uint32_t period;
+ uint32_t speed;
+};
+
+#define hdspe_read_1(sc, regno) \
+ bus_space_read_1((sc)->cst, (sc)->csh, (regno))
+#define hdspe_read_2(sc, regno) \
+ bus_space_read_2((sc)->cst, (sc)->csh, (regno))
+#define hdspe_read_4(sc, regno) \
+ bus_space_read_4((sc)->cst, (sc)->csh, (regno))
+
+#define hdspe_write_1(sc, regno, data) \
+ bus_space_write_1((sc)->cst, (sc)->csh, (regno), (data))
+#define hdspe_write_2(sc, regno, data) \
+ bus_space_write_2((sc)->cst, (sc)->csh, (regno), (data))
+#define hdspe_write_4(sc, regno, data) \
+ bus_space_write_4((sc)->cst, (sc)->csh, (regno), (data))
diff --git a/sys/modules/sound/driver/Makefile b/sys/modules/sound/driver/Makefile
index 80df177..481aa8e 100644
--- a/sys/modules/sound/driver/Makefile
+++ b/sys/modules/sound/driver/Makefile
@@ -6,8 +6,8 @@
# MK_SOURCELESS_UCODE option (see below).
SUBDIR= ad1816 als4000 atiixp cs4281 ${_csa} ${_ds1} emu10k1 emu10kx
-SUBDIR+= envy24 envy24ht es137x ess fm801 hda ich maestro ${_maestro3}
-SUBDIR+= neomagic sb16 sb8 sbc solo spicds t4dwave via8233
+SUBDIR+= envy24 envy24ht es137x ess fm801 hda hdspe ich maestro
+SUBDIR+= ${_maestro3} neomagic sb16 sb8 sbc solo spicds t4dwave via8233
SUBDIR+= via82c686 vibes driver uaudio
.if ${MK_SOURCELESS_UCODE} != "no"
diff --git a/sys/modules/sound/driver/hdspe/Makefile b/sys/modules/sound/driver/hdspe/Makefile
new file mode 100644
index 0000000..fcf83fe
--- /dev/null
+++ b/sys/modules/sound/driver/hdspe/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../../dev/sound/pci
+
+KMOD= snd_hdspe
+SRCS= device_if.h bus_if.h pci_if.h
+SRCS+= hdspe.c hdspe-pcm.c hdspe.h
+
+.include <bsd.kmod.mk>
OpenPOWER on IntegriCloud