diff options
author | mav <mav@FreeBSD.org> | 2012-03-01 13:10:18 +0000 |
---|---|---|
committer | mav <mav@FreeBSD.org> | 2012-03-01 13:10:18 +0000 |
commit | d844e42d76db648c162bab17d351a353b5793366 (patch) | |
tree | fbf090c92a3e174411a3b0627fb5925b6bfd80a3 /sys/dev/sound | |
parent | 3e4d13da1e3c73a8b50ee86331dc86105fccc5fa (diff) | |
download | FreeBSD-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/dev/sound')
-rw-r--r-- | sys/dev/sound/pci/hdspe-pcm.c | 709 | ||||
-rw-r--r-- | sys/dev/sound/pci/hdspe.c | 410 | ||||
-rw-r--r-- | sys/dev/sound/pci/hdspe.h | 179 |
3 files changed, 1298 insertions, 0 deletions
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)) |