diff --git a/sys/dev/sound/pci/hdspe-pcm.c b/sys/dev/sound/pci/hdspe-pcm.c index 79bfcbea3406..322fd69723c0 100644 --- a/sys/dev/sound/pci/hdspe-pcm.c +++ b/sys/dev/sound/pci/hdspe-pcm.c @@ -1,1124 +1,1128 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012-2021 Ruslan Bukin * Copyright (c) 2023-2024 Florian Walpen * 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 #include #include #include #include #define HDSPE_MATRIX_MAX 8 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 uint32_t hdspe_channel_play_ports(struct hdspe_channel *hc) { return (hc->ports & (HDSPE_CHAN_AIO_ALL | HDSPE_CHAN_RAY_ALL)); } static uint32_t hdspe_channel_rec_ports(struct hdspe_channel *hc) { return (hc->ports & (HDSPE_CHAN_AIO_ALL_REC | HDSPE_CHAN_RAY_ALL)); } static unsigned int hdspe_adat_width(uint32_t speed) { if (speed > 96000) return (2); if (speed > 48000) return (4); return (8); } static uint32_t hdspe_port_first(uint32_t ports) { return (ports & (~(ports - 1))); /* Extract first bit set. */ } static uint32_t hdspe_port_first_row(uint32_t ports) { uint32_t ends; /* Restrict ports to one set with contiguous slots. */ if (ports & HDSPE_CHAN_AIO_LINE) ports = HDSPE_CHAN_AIO_LINE; /* Gap in the AIO slots here. */ else if (ports & HDSPE_CHAN_AIO_ALL) ports &= HDSPE_CHAN_AIO_ALL; /* Rest of the AIO slots. */ else if (ports & HDSPE_CHAN_RAY_ALL) ports &= HDSPE_CHAN_RAY_ALL; /* All RayDAT slots. */ /* Ends of port rows are followed by a port which is not in the set. */ ends = ports & (~(ports >> 1)); /* First row of contiguous ports ends in the first row end. */ return (ports & (ends ^ (ends - 1))); } static unsigned int hdspe_channel_count(uint32_t ports, uint32_t adat_width) { unsigned int count = 0; if (ports & HDSPE_CHAN_AIO_ALL) { /* AIO ports. */ if (ports & HDSPE_CHAN_AIO_LINE) count += 2; + if (ports & HDSPE_CHAN_AIO_EXT) + count += 4; if (ports & HDSPE_CHAN_AIO_PHONE) count += 2; if (ports & HDSPE_CHAN_AIO_AES) count += 2; if (ports & HDSPE_CHAN_AIO_SPDIF) count += 2; if (ports & HDSPE_CHAN_AIO_ADAT) count += adat_width; } else if (ports & HDSPE_CHAN_RAY_ALL) { /* RayDAT ports. */ if (ports & HDSPE_CHAN_RAY_AES) count += 2; if (ports & HDSPE_CHAN_RAY_SPDIF) count += 2; if (ports & HDSPE_CHAN_RAY_ADAT1) count += adat_width; if (ports & HDSPE_CHAN_RAY_ADAT2) count += adat_width; if (ports & HDSPE_CHAN_RAY_ADAT3) count += adat_width; if (ports & HDSPE_CHAN_RAY_ADAT4) count += adat_width; } return (count); } static unsigned int hdspe_channel_offset(uint32_t subset, uint32_t ports, unsigned int adat_width) { uint32_t preceding; /* Make sure we have a subset of ports. */ subset &= ports; /* Include all ports preceding the first one of the subset. */ preceding = ports & (~subset & (subset - 1)); if (preceding & HDSPE_CHAN_AIO_ALL) preceding &= HDSPE_CHAN_AIO_ALL; /* Contiguous AIO slots. */ else if (preceding & HDSPE_CHAN_RAY_ALL) preceding &= HDSPE_CHAN_RAY_ALL; /* Contiguous RayDAT slots. */ return (hdspe_channel_count(preceding, adat_width)); } static unsigned int hdspe_port_slot_offset(uint32_t port, unsigned int adat_width) { /* Exctract the first port (lowest bit) if set of ports. */ switch (hdspe_port_first(port)) { /* AIO ports */ case HDSPE_CHAN_AIO_LINE: return (0); + case HDSPE_CHAN_AIO_EXT: + return (2); case HDSPE_CHAN_AIO_PHONE: return (6); case HDSPE_CHAN_AIO_AES: return (8); case HDSPE_CHAN_AIO_SPDIF: return (10); case HDSPE_CHAN_AIO_ADAT: return (12); /* RayDAT ports */ case HDSPE_CHAN_RAY_AES: return (0); case HDSPE_CHAN_RAY_SPDIF: return (2); case HDSPE_CHAN_RAY_ADAT1: return (4); case HDSPE_CHAN_RAY_ADAT2: return (4 + adat_width); case HDSPE_CHAN_RAY_ADAT3: return (4 + 2 * adat_width); case HDSPE_CHAN_RAY_ADAT4: return (4 + 3 * adat_width); default: return (0); } } static unsigned int hdspe_port_slot_width(uint32_t ports, unsigned int adat_width) { uint32_t row; /* Count number of contiguous slots from the first physical port. */ row = hdspe_port_first_row(ports); return (hdspe_channel_count(row, adat_width)); } static int hdspe_hw_mixer(struct sc_chinfo *ch, unsigned int dst, unsigned int src, unsigned short data) { struct sc_pcminfo *scp; struct sc_info *sc; int offs; scp = ch->parent; sc = scp->sc; 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) { struct sc_info *sc; uint32_t port, ports; unsigned int slot, end_slot; unsigned short volume; sc = ch->parent->sc; /* Iterate through all physical ports of the channel. */ ports = ch->ports; port = hdspe_port_first(ports); while (port != 0) { /* Get slot range of the physical port. */ slot = hdspe_port_slot_offset(port, hdspe_adat_width(sc->speed)); end_slot = slot + hdspe_port_slot_width(port, hdspe_adat_width(sc->speed)); /* Treat first slot as left channel. */ volume = ch->lvol * HDSPE_MAX_GAIN / 100; for (; slot < end_slot; slot++) { hdspe_hw_mixer(ch, slot, slot, volume); /* Subsequent slots all get the right channel volume. */ volume = ch->rvol * HDSPE_MAX_GAIN / 100; } ports &= ~port; port = hdspe_port_first(ports); } return (0); } static int hdspemixer_init(struct snd_mixer *m) { struct sc_pcminfo *scp; struct sc_info *sc; int mask; scp = mix_getdevinfo(m); sc = scp->sc; if (sc == NULL) return (-1); mask = SOUND_MASK_PCM; if (hdspe_channel_play_ports(scp->hc)) mask |= SOUND_MASK_VOLUME; if (hdspe_channel_rec_ports(scp->hc)) 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; struct sc_chinfo *ch; int i; scp = mix_getdevinfo(m); #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; struct sc_info *sc; uint32_t row, ports; int reg; unsigned int slot, end_slot; scp = ch->parent; sc = scp->sc; if (ch->dir == PCMDIR_PLAY) reg = HDSPE_OUT_ENABLE_BASE; else reg = HDSPE_IN_ENABLE_BASE; ch->run = value; /* Iterate through rows of ports with contiguous slots. */ ports = ch->ports; row = hdspe_port_first_row(ports); while (row != 0) { slot = hdspe_port_slot_offset(row, hdspe_adat_width(sc->speed)); end_slot = slot + hdspe_port_slot_width(row, hdspe_adat_width(sc->speed)); for (; slot < end_slot; slot++) { hdspe_write_1(sc, reg + (4 * slot), value); } ports &= ~row; row = hdspe_port_first_row(ports); } } static int hdspe_running(struct sc_info *sc) { struct sc_pcminfo *scp; struct sc_chinfo *ch; device_t *devlist; int devcount; int i, j; int err; 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; } } free(devlist, M_TEMP); return (0); bad: #if 0 device_printf(sc->dev, "hdspe is running\n"); #endif free(devlist, M_TEMP); 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); } static void buffer_mux_write(uint32_t *dma, uint32_t *pcm, unsigned int pos, unsigned int samples, unsigned int slots, unsigned int channels) { int slot; for (; samples > 0; samples--) { for (slot = 0; slot < slots; slot++) { dma[slot * HDSPE_CHANBUF_SAMPLES + pos] = pcm[pos * channels + slot]; } pos = (pos + 1) % HDSPE_CHANBUF_SAMPLES; } } static void buffer_mux_port(uint32_t *dma, uint32_t *pcm, uint32_t subset, uint32_t ports, unsigned int pos, unsigned int samples, unsigned int adat_width, unsigned int pcm_width) { unsigned int slot_offset, slots; unsigned int channels, chan_pos; /* Translate DMA slot offset to DMA buffer offset. */ slot_offset = hdspe_port_slot_offset(subset, adat_width); dma += slot_offset * HDSPE_CHANBUF_SAMPLES; /* Channel position of the port subset and total number of channels. */ chan_pos = hdspe_channel_offset(subset, ports, pcm_width); pcm += chan_pos; channels = hdspe_channel_count(ports, pcm_width); /* Only copy as much as supported by both hardware and pcm channel. */ slots = hdspe_port_slot_width(subset, MIN(adat_width, pcm_width)); /* Let the compiler inline and loop unroll common cases. */ if (slots == 2) buffer_mux_write(dma, pcm, pos, samples, 2, channels); else if (slots == 4) buffer_mux_write(dma, pcm, pos, samples, 4, channels); else if (slots == 8) buffer_mux_write(dma, pcm, pos, samples, 8, channels); else buffer_mux_write(dma, pcm, pos, samples, slots, channels); } static void buffer_demux_read(uint32_t *dma, uint32_t *pcm, unsigned int pos, unsigned int samples, unsigned int slots, unsigned int channels) { int slot; for (; samples > 0; samples--) { for (slot = 0; slot < slots; slot++) { pcm[pos * channels + slot] = dma[slot * HDSPE_CHANBUF_SAMPLES + pos]; } pos = (pos + 1) % HDSPE_CHANBUF_SAMPLES; } } static void buffer_demux_port(uint32_t *dma, uint32_t *pcm, uint32_t subset, uint32_t ports, unsigned int pos, unsigned int samples, unsigned int adat_width, unsigned int pcm_width) { unsigned int slot_offset, slots; unsigned int channels, chan_pos; /* Translate port slot offset to DMA buffer offset. */ slot_offset = hdspe_port_slot_offset(subset, adat_width); dma += slot_offset * HDSPE_CHANBUF_SAMPLES; /* Channel position of the port subset and total number of channels. */ chan_pos = hdspe_channel_offset(subset, ports, pcm_width); pcm += chan_pos; channels = hdspe_channel_count(ports, pcm_width); /* Only copy as much as supported by both hardware and pcm channel. */ slots = hdspe_port_slot_width(subset, MIN(adat_width, pcm_width)); /* Let the compiler inline and loop unroll common cases. */ if (slots == 2) buffer_demux_read(dma, pcm, pos, samples, 2, channels); else if (slots == 4) buffer_demux_read(dma, pcm, pos, samples, 4, channels); else if (slots == 8) buffer_demux_read(dma, pcm, pos, samples, 8, channels); else buffer_demux_read(dma, pcm, pos, samples, slots, channels); } /* Copy data between DMA and PCM buffers. */ static void buffer_copy(struct sc_chinfo *ch) { struct sc_pcminfo *scp; struct sc_info *sc; uint32_t row, ports; uint32_t dma_pos; unsigned int pos, length, offset; unsigned int n; unsigned int adat_width, pcm_width; scp = ch->parent; sc = scp->sc; n = AFMT_CHANNEL(ch->format); /* n channels */ /* Let pcm formats differ from current hardware ADAT width. */ adat_width = hdspe_adat_width(sc->speed); if (n == hdspe_channel_count(ch->ports, 2)) pcm_width = 2; else if (n == hdspe_channel_count(ch->ports, 4)) pcm_width = 4; else pcm_width = 8; /* Derive buffer position and length to be copied. */ if (ch->dir == PCMDIR_PLAY) { /* Position per channel is n times smaller than PCM. */ pos = sndbuf_getreadyptr(ch->buffer) / n; length = sndbuf_getready(ch->buffer) / n; /* Copy no more than 2 periods in advance. */ if (length > (sc->period * 4 * 2)) length = (sc->period * 4 * 2); /* Skip what was already copied last time. */ offset = (ch->position + HDSPE_CHANBUF_SIZE) - pos; offset %= HDSPE_CHANBUF_SIZE; if (offset <= length) { pos = (pos + offset) % HDSPE_CHANBUF_SIZE; length -= offset; } } else { /* Position per channel is n times smaller than PCM. */ pos = sndbuf_getfreeptr(ch->buffer) / n; /* Get DMA buffer write position. */ dma_pos = hdspe_read_2(sc, HDSPE_STATUS_REG); dma_pos &= HDSPE_BUF_POSITION_MASK; /* Copy what is newly available. */ length = (dma_pos + HDSPE_CHANBUF_SIZE) - pos; length %= HDSPE_CHANBUF_SIZE; } /* Position and length in samples (4 bytes). */ pos /= 4; length /= 4; /* Iterate through rows of ports with contiguous slots. */ ports = ch->ports; if (pcm_width == adat_width) row = hdspe_port_first_row(ports); else row = hdspe_port_first(ports); while (row != 0) { if (ch->dir == PCMDIR_PLAY) buffer_mux_port(sc->pbuf, ch->data, row, ch->ports, pos, length, adat_width, pcm_width); else buffer_demux_port(sc->rbuf, ch->data, row, ch->ports, pos, length, adat_width, pcm_width); ports &= ~row; if (pcm_width == adat_width) row = hdspe_port_first_row(ports); else row = hdspe_port_first(ports); } ch->position = ((pos + length) * 4) % HDSPE_CHANBUF_SIZE; } static int clean(struct sc_chinfo *ch) { struct sc_pcminfo *scp; struct sc_info *sc; uint32_t *buf; uint32_t row, ports; unsigned int offset, slots; scp = ch->parent; sc = scp->sc; buf = sc->rbuf; if (ch->dir == PCMDIR_PLAY) buf = sc->pbuf; /* Iterate through rows of ports with contiguous slots. */ ports = ch->ports; row = hdspe_port_first_row(ports); while (row != 0) { offset = hdspe_port_slot_offset(row, hdspe_adat_width(sc->speed)); slots = hdspe_port_slot_width(row, hdspe_adat_width(sc->speed)); bzero(buf + offset * HDSPE_CHANBUF_SAMPLES, slots * HDSPE_CHANBUF_SIZE); ports &= ~row; row = hdspe_port_first_row(ports); } ch->position = 0; return (0); } /* Channel interface. */ static int hdspechan_free(kobj_t obj, void *data) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; ch = data; scp = ch->parent; 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; } if (ch->caps != NULL) { free(ch->caps, M_HDSPE); ch->caps = NULL; } snd_mtxunlock(sc->lock); return (0); } static void * hdspechan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; int num; scp = devinfo; sc = scp->sc; snd_mtxlock(sc->lock); num = scp->chnum; ch = &scp->chan[num]; if (dir == PCMDIR_PLAY) ch->ports = hdspe_channel_play_ports(scp->hc); else ch->ports = hdspe_channel_rec_ports(scp->hc); ch->run = 0; ch->lvol = 0; ch->rvol = 0; /* Support all possible ADAT widths as channel formats. */ ch->cap_fmts[0] = SND_FORMAT(AFMT_S32_LE, hdspe_channel_count(ch->ports, 2), 0); ch->cap_fmts[1] = SND_FORMAT(AFMT_S32_LE, hdspe_channel_count(ch->ports, 4), 0); ch->cap_fmts[2] = SND_FORMAT(AFMT_S32_LE, hdspe_channel_count(ch->ports, 8), 0); ch->cap_fmts[3] = 0; ch->caps = malloc(sizeof(struct pcmchan_caps), M_HDSPE, M_NOWAIT); *(ch->caps) = (struct pcmchan_caps) {32000, 192000, ch->cap_fmts, 0}; /* Allocate maximum buffer size. */ ch->size = HDSPE_CHANBUF_SIZE * hdspe_channel_count(ch->ports, 8); ch->data = malloc(ch->size, M_HDSPE, M_NOWAIT); ch->position = 0; 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"); hdspechan_free(obj, ch); return (NULL); } return (ch); } static int hdspechan_trigger(kobj_t obj, void *data, int go) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; ch = data; scp = ch->parent; 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_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; uint32_t ret, pos; ch = data; scp = ch->parent; sc = scp->sc; snd_mtxlock(sc->lock); ret = hdspe_read_2(sc, HDSPE_STATUS_REG); snd_mtxunlock(sc->lock); pos = ret & HDSPE_BUF_POSITION_MASK; pos *= AFMT_CHANNEL(ch->format); /* Hardbuf with multiple channels. */ return (pos); } static int hdspechan_setformat(kobj_t obj, void *data, uint32_t format) { struct sc_chinfo *ch; 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_pcminfo *scp; struct hdspe_rate *hr; struct sc_chinfo *ch; struct sc_info *sc; long long period; int threshold; int i; ch = data; scp = ch->parent; sc = scp->sc; hr = NULL; #if 0 device_printf(scp->dev, "hdspechan_setspeed(%d)\n", speed); #endif if (hdspe_running(sc) == 1) goto end; if (sc->force_speed > 0) speed = sc->force_speed; /* 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 HDSPE_RAYDAT: case HDSPE_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 hdspe_latency *hl; struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; int threshold; int i; ch = data; scp = ch->parent; sc = scp->sc; hl = NULL; #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 */; if (sc->force_period > 0) blocksize = sc->force_period; /* 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 * AFMT_CHANNEL(ch->format)) / (sc->period * 4), (sc->period * 4)); end: return (sndbuf_getblksz(ch->buffer)); } static uint32_t hdspe_bkp_fmt[] = { SND_FORMAT(AFMT_S32_LE, 2, 0), 0 }; static struct pcmchan_caps hdspe_bkp_caps = {32000, 192000, hdspe_bkp_fmt, 0}; static struct pcmchan_caps * hdspechan_getcaps(kobj_t obj, void *data) { struct sc_chinfo *ch; ch = data; #if 0 struct sc_pcminfo *scl = ch->parent; device_printf(scp->dev, "hdspechan_getcaps()\n"); #endif if (ch->caps != NULL) return (ch->caps); return (&hdspe_bkp_caps); } 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; int i; sc = scp->sc; 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) { char status[SND_STATUSLEN]; struct sc_pcminfo *scp; const char *buf; uint32_t pcm_flags; int err; int play, rec; scp = device_get_ivars(dev); scp->ih = &hdspe_pcm_intr; if (scp->hc->ports & HDSPE_CHAN_AIO_ALL) buf = "AIO"; else if (scp->hc->ports & HDSPE_CHAN_RAY_ALL) buf = "RayDAT"; else buf = "?"; device_set_descf(dev, "HDSPe %s [%s]", buf, scp->hc->descr); /* * We don't register interrupt handler with snd_setup_intr * in pcm device. Mark pcm device as MPSAFE manually. */ pcm_flags = pcm_getflags(dev) | SD_F_MPSAFE; if (hdspe_channel_count(scp->hc->ports, 8) > HDSPE_MATRIX_MAX) /* Disable vchan conversion, too many channels. */ pcm_flags |= SD_F_BITPERFECT; pcm_setflags(dev, pcm_flags); play = (hdspe_channel_play_ports(scp->hc)) ? 1 : 0; rec = (hdspe_channel_rec_ports(scp->hc)) ? 1 : 0; err = pcm_register(dev, scp, play, rec); if (err) { device_printf(dev, "Can't register pcm.\n"); return (ENXIO); } scp->chnum = 0; if (play) { pcm_addchan(dev, PCMDIR_PLAY, &hdspechan_class, scp); scp->chnum++; } if (rec) { pcm_addchan(dev, PCMDIR_REC, &hdspechan_class, scp); scp->chnum++; } snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", rman_get_start(scp->sc->cs), rman_get_start(scp->sc->irq), device_get_nameunit(device_get_parent(dev))); 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, 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 index 66f5bc5c5d7a..0e25e9599328 100644 --- a/sys/dev/sound/pci/hdspe.c +++ b/sys/dev/sound/pci/hdspe.c @@ -1,684 +1,685 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012-2016 Ruslan Bukin * Copyright (c) 2023-2024 Florian Walpen * 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 #include #include #include #include #include #include static bool hdspe_unified_pcm = false; static SYSCTL_NODE(_hw, OID_AUTO, hdspe, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "PCI HDSPe"); SYSCTL_BOOL(_hw_hdspe, OID_AUTO, unified_pcm, CTLFLAG_RWTUN, &hdspe_unified_pcm, 0, "Combine physical ports in one unified pcm device"); static struct hdspe_clock_source hdspe_clock_source_table_rd[] = { { "internal", 0 << 1 | 1, HDSPE_STATUS1_CLOCK(15), 0, 0 }, { "word", 0 << 1 | 0, HDSPE_STATUS1_CLOCK( 0), 1 << 24, 1 << 25 }, { "aes", 1 << 1 | 0, HDSPE_STATUS1_CLOCK( 1), 1 << 0, 1 << 8 }, { "spdif", 2 << 1 | 0, HDSPE_STATUS1_CLOCK( 2), 1 << 1, 1 << 9 }, { "adat1", 3 << 1 | 0, HDSPE_STATUS1_CLOCK( 3), 1 << 2, 1 << 10 }, { "adat2", 4 << 1 | 0, HDSPE_STATUS1_CLOCK( 4), 1 << 3, 1 << 11 }, { "adat3", 5 << 1 | 0, HDSPE_STATUS1_CLOCK( 5), 1 << 4, 1 << 12 }, { "adat4", 6 << 1 | 0, HDSPE_STATUS1_CLOCK( 6), 1 << 5, 1 << 13 }, { "tco", 9 << 1 | 0, HDSPE_STATUS1_CLOCK( 9), 1 << 26, 1 << 27 }, { "sync_in", 10 << 1 | 0, HDSPE_STATUS1_CLOCK(10), 0, 0 }, { NULL, 0 << 1 | 0, HDSPE_STATUS1_CLOCK( 0), 0, 0 }, }; static struct hdspe_clock_source hdspe_clock_source_table_aio[] = { { "internal", 0 << 1 | 1, HDSPE_STATUS1_CLOCK(15), 0, 0 }, { "word", 0 << 1 | 0, HDSPE_STATUS1_CLOCK( 0), 1 << 24, 1 << 25 }, { "aes", 1 << 1 | 0, HDSPE_STATUS1_CLOCK( 1), 1 << 0, 1 << 8 }, { "spdif", 2 << 1 | 0, HDSPE_STATUS1_CLOCK( 2), 1 << 1, 1 << 9 }, { "adat", 3 << 1 | 0, HDSPE_STATUS1_CLOCK( 3), 1 << 2, 1 << 10 }, { "tco", 9 << 1 | 0, HDSPE_STATUS1_CLOCK( 9), 1 << 26, 1 << 27 }, { "sync_in", 10 << 1 | 0, HDSPE_STATUS1_CLOCK(10), 0, 0 }, { NULL, 0 << 1 | 0, HDSPE_STATUS1_CLOCK( 0), 0, 0 }, }; static struct hdspe_channel chan_map_aio[] = { { HDSPE_CHAN_AIO_LINE, "line" }, + { HDSPE_CHAN_AIO_EXT, "ext" }, { HDSPE_CHAN_AIO_PHONE, "phone" }, { HDSPE_CHAN_AIO_AES, "aes" }, { HDSPE_CHAN_AIO_SPDIF, "s/pdif" }, { HDSPE_CHAN_AIO_ADAT, "adat" }, { 0, NULL }, }; static struct hdspe_channel chan_map_aio_uni[] = { { HDSPE_CHAN_AIO_ALL, "all" }, { 0, NULL }, }; static struct hdspe_channel chan_map_rd[] = { { HDSPE_CHAN_RAY_AES, "aes" }, { HDSPE_CHAN_RAY_SPDIF, "s/pdif" }, { HDSPE_CHAN_RAY_ADAT1, "adat1" }, { HDSPE_CHAN_RAY_ADAT2, "adat2" }, { HDSPE_CHAN_RAY_ADAT3, "adat3" }, { HDSPE_CHAN_RAY_ADAT4, "adat4" }, { 0, NULL }, }; static struct hdspe_channel chan_map_rd_uni[] = { { HDSPE_CHAN_RAY_ALL, "all" }, { 0, NULL }, }; static void hdspe_intr(void *p) { struct sc_pcminfo *scp; struct sc_info *sc; device_t *devlist; int devcount; int status; int err; int i; sc = (struct sc_info *)p; 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); free(devlist, M_TEMP); } snd_mtxunlock(sc->lock); } static void hdspe_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { #if 0 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_any(sc->dev, SYS_RES_MEMORY, &sc->csid, 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_any(sc->dev, SYS_RES_IRQ, &sc->irqid, 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*/NULL, /*lockarg*/NULL, /*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_WAITOK, &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, BUS_DMA_NOWAIT)) { 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_WAITOK, &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, BUS_DMA_NOWAIT)) { 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_sysctl_sample_rate(SYSCTL_HANDLER_ARGS) { struct sc_info *sc = oidp->oid_arg1; int error; unsigned int speed, multiplier; speed = sc->force_speed; /* Process sysctl (unsigned) integer request. */ error = sysctl_handle_int(oidp, &speed, 0, req); if (error != 0 || req->newptr == NULL) return (error); /* Speed from 32000 to 192000, 0 falls back to pcm speed setting. */ sc->force_speed = 0; if (speed > 0) { multiplier = 1; if (speed > (96000 + 128000) / 2) multiplier = 4; else if (speed > (48000 + 64000) / 2) multiplier = 2; if (speed < ((32000 + 44100) / 2) * multiplier) sc->force_speed = 32000 * multiplier; else if (speed < ((44100 + 48000) / 2) * multiplier) sc->force_speed = 44100 * multiplier; else sc->force_speed = 48000 * multiplier; } return (0); } static int hdspe_sysctl_period(SYSCTL_HANDLER_ARGS) { struct sc_info *sc = oidp->oid_arg1; int error; unsigned int period; period = sc->force_period; /* Process sysctl (unsigned) integer request. */ error = sysctl_handle_int(oidp, &period, 0, req); if (error != 0 || req->newptr == NULL) return (error); /* Period is from 2^5 to 2^14, 0 falls back to pcm latency settings. */ sc->force_period = 0; if (period > 0) { sc->force_period = 32; while (sc->force_period < period && sc->force_period < 4096) sc->force_period <<= 1; } return (0); } static int hdspe_sysctl_clock_preference(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdspe_clock_source *clock_table, *clock; char buf[16] = "invalid"; int error; uint32_t setting; sc = oidp->oid_arg1; /* Select sync ports table for device type. */ if (sc->type == HDSPE_AIO) clock_table = hdspe_clock_source_table_aio; else if (sc->type == HDSPE_RAYDAT) clock_table = hdspe_clock_source_table_rd; else return (ENXIO); /* Extract preferred clock source from settings register. */ setting = sc->settings_register & HDSPE_SETTING_CLOCK_MASK; for (clock = clock_table; clock->name != NULL; ++clock) { if (clock->setting == setting) break; } if (clock->name != NULL) strlcpy(buf, clock->name, sizeof(buf)); /* Process sysctl string request. */ error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); /* Find clock source matching the sysctl string. */ for (clock = clock_table; clock->name != NULL; ++clock) { if (strncasecmp(buf, clock->name, sizeof(buf)) == 0) break; } /* Set preferred clock source in settings register. */ if (clock->name != NULL) { setting = clock->setting & HDSPE_SETTING_CLOCK_MASK; snd_mtxlock(sc->lock); sc->settings_register &= ~HDSPE_SETTING_CLOCK_MASK; sc->settings_register |= setting; hdspe_write_4(sc, HDSPE_SETTINGS_REG, sc->settings_register); snd_mtxunlock(sc->lock); } return (0); } static int hdspe_sysctl_clock_source(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdspe_clock_source *clock_table, *clock; char buf[16] = "invalid"; uint32_t status; sc = oidp->oid_arg1; /* Select sync ports table for device type. */ if (sc->type == HDSPE_AIO) clock_table = hdspe_clock_source_table_aio; else if (sc->type == HDSPE_RAYDAT) clock_table = hdspe_clock_source_table_rd; else return (ENXIO); /* Read current (autosync) clock source from status register. */ snd_mtxlock(sc->lock); status = hdspe_read_4(sc, HDSPE_STATUS1_REG); status &= HDSPE_STATUS1_CLOCK_MASK; snd_mtxunlock(sc->lock); /* Translate status register value to clock source. */ for (clock = clock_table; clock->name != NULL; ++clock) { /* In clock master mode, override with internal clock source. */ if (sc->settings_register & HDSPE_SETTING_MASTER) { if (clock->setting & HDSPE_SETTING_MASTER) break; } else if (clock->status == status) break; } /* Process sysctl string request. */ if (clock->name != NULL) strlcpy(buf, clock->name, sizeof(buf)); return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdspe_sysctl_clock_list(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdspe_clock_source *clock_table, *clock; char buf[256]; int n; sc = oidp->oid_arg1; n = 0; /* Select clock source table for device type. */ if (sc->type == HDSPE_AIO) clock_table = hdspe_clock_source_table_aio; else if (sc->type == HDSPE_RAYDAT) clock_table = hdspe_clock_source_table_rd; else return (ENXIO); /* List available clock sources. */ buf[0] = 0; for (clock = clock_table; clock->name != NULL; ++clock) { if (n > 0) n += strlcpy(buf + n, ",", sizeof(buf) - n); n += strlcpy(buf + n, clock->name, sizeof(buf) - n); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdspe_sysctl_sync_status(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdspe_clock_source *clock_table, *clock; char buf[256]; char *state; int n; uint32_t status; sc = oidp->oid_arg1; n = 0; /* Select sync ports table for device type. */ if (sc->type == HDSPE_AIO) clock_table = hdspe_clock_source_table_aio; else if (sc->type == HDSPE_RAYDAT) clock_table = hdspe_clock_source_table_rd; else return (ENXIO); /* Read current lock and sync bits from status register. */ snd_mtxlock(sc->lock); status = hdspe_read_4(sc, HDSPE_STATUS1_REG); snd_mtxunlock(sc->lock); /* List clock sources with lock and sync state. */ for (clock = clock_table; clock->name != NULL; ++clock) { if (clock->sync_bit != 0) { if (n > 0) n += strlcpy(buf + n, ",", sizeof(buf) - n); state = "none"; if ((clock->sync_bit & status) != 0) state = "sync"; else if ((clock->lock_bit & status) != 0) state = "lock"; n += snprintf(buf + n, sizeof(buf) - n, "%s(%s)", clock->name, state); } } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdspe_probe(device_t dev) { uint32_t rev; if ((pci_get_vendor(dev) == PCI_VENDOR_XILINX || pci_get_vendor(dev) == PCI_VENDOR_RME) && 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 hdspe_init(struct sc_info *sc) { long long period; /* Set latency. */ sc->period = 32; /* * The pcm channel latency settings propagate unreliable blocksizes, * different for recording and playback, and skewed due to rounding * and total buffer size limits. * Force period to a consistent default until these issues are fixed. */ sc->force_period = 256; sc->ctrl_register = hdspe_encode_latency(7); /* Set rate. */ sc->speed = HDSPE_SPEED_DEFAULT; sc->force_speed = 0; 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 HDSPE_RAYDAT: case HDSPE_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 hdspe_channel *chan_map; struct sc_pcminfo *scp; struct sc_info *sc; uint32_t rev; int i, err; #if 0 device_printf(dev, "hdspe_attach()\n"); #endif sc = device_get_softc(dev); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_hdspe softc"); sc->dev = dev; pci_enable_busmaster(dev); rev = pci_get_revid(dev); switch (rev) { case PCI_REVISION_AIO: sc->type = HDSPE_AIO; chan_map = hdspe_unified_pcm ? chan_map_aio_uni : chan_map_aio; break; case PCI_REVISION_RAYDAT: sc->type = HDSPE_RAYDAT; chan_map = hdspe_unified_pcm ? chan_map_rd_uni : 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", DEVICE_UNIT_ANY); device_set_ivars(scp->dev, scp); } hdspe_map_dmabuf(sc); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sync_status", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_sync_status, "A", "List clock source signal lock and sync status"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clock_source", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_clock_source, "A", "Currently effective clock source"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clock_preference", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_clock_preference, "A", "Set 'internal' (master) or preferred autosync clock source"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clock_list", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_clock_list, "A", "List of supported clock sources"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "period", CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_period, "A", "Force period of samples per interrupt (32, 64, ... 4096)"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sample_rate", CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_sample_rate, "A", "Force sample rate (32000, 44100, 48000, ... 192000)"); return (bus_generic_attach(dev)); } 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->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, 0, 0); diff --git a/sys/dev/sound/pci/hdspe.h b/sys/dev/sound/pci/hdspe.h index daffeb4ddebc..2f0bdddd930f 100644 --- a/sys/dev/sound/pci/hdspe.h +++ b/sys/dev/sound/pci/hdspe.h @@ -1,248 +1,250 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012-2016 Ruslan Bukin * Copyright (c) 2023-2024 Florian Walpen * 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. */ #define PCI_VENDOR_XILINX 0x10ee #define PCI_VENDOR_RME 0x1d18 /* Newer firmware versions. */ #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 HDSPE_AIO 0 #define HDSPE_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) /* Gain */ #define HDSP_ADGain0 (1 << 25) #define HDSP_ADGain1 (1 << 26) #define HDSP_DAGain0 (1 << 27) #define HDSP_DAGain1 (1 << 28) #define HDSP_PhoneGain0 (1 << 29) #define HDSP_PhoneGain1 (1 << 30) #define HDSP_ADGainMask (HDSP_ADGain0 | HDSP_ADGain1) #define HDSP_ADGainMinus10dBV (HDSP_ADGainMask) #define HDSP_ADGainPlus4dBu (HDSP_ADGain0) #define HDSP_ADGainLowGain 0 #define HDSP_DAGainMask (HDSP_DAGain0 | HDSP_DAGain1) #define HDSP_DAGainHighGain (HDSP_DAGainMask) #define HDSP_DAGainPlus4dBu (HDSP_DAGain0) #define HDSP_DAGainMinus10dBV 0 #define HDSP_PhoneGainMask (HDSP_PhoneGain0|HDSP_PhoneGain1) #define HDSP_PhoneGain0dB HDSP_PhoneGainMask #define HDSP_PhoneGainMinus6dB (HDSP_PhoneGain0) #define HDSP_PhoneGainMinus12dB 0 /* Settings */ #define HDSPE_SETTINGS_REG 0 #define HDSPE_CONTROL_REG 64 #define HDSPE_STATUS_REG 0 #define HDSPE_STATUS1_REG 64 #define HDSPE_STATUS2_REG 192 #define HDSPE_ENABLE (1 << 0) /* 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) #define HDSPE_CHAN_AIO_LINE (1 << 0) -#define HDSPE_CHAN_AIO_PHONE (1 << 1) -#define HDSPE_CHAN_AIO_AES (1 << 2) -#define HDSPE_CHAN_AIO_SPDIF (1 << 3) -#define HDSPE_CHAN_AIO_ADAT (1 << 4) +#define HDSPE_CHAN_AIO_EXT (1 << 1) +#define HDSPE_CHAN_AIO_PHONE (1 << 2) +#define HDSPE_CHAN_AIO_AES (1 << 3) +#define HDSPE_CHAN_AIO_SPDIF (1 << 4) +#define HDSPE_CHAN_AIO_ADAT (1 << 5) #define HDSPE_CHAN_AIO_ALL_REC (HDSPE_CHAN_AIO_LINE | \ + HDSPE_CHAN_AIO_EXT | \ HDSPE_CHAN_AIO_AES | \ HDSPE_CHAN_AIO_SPDIF | \ HDSPE_CHAN_AIO_ADAT) #define HDSPE_CHAN_AIO_ALL (HDSPE_CHAN_AIO_ALL_REC | \ HDSPE_CHAN_AIO_PHONE) \ -#define HDSPE_CHAN_RAY_AES (1 << 5) -#define HDSPE_CHAN_RAY_SPDIF (1 << 6) -#define HDSPE_CHAN_RAY_ADAT1 (1 << 7) -#define HDSPE_CHAN_RAY_ADAT2 (1 << 8) -#define HDSPE_CHAN_RAY_ADAT3 (1 << 9) -#define HDSPE_CHAN_RAY_ADAT4 (1 << 10) +#define HDSPE_CHAN_RAY_AES (1 << 6) +#define HDSPE_CHAN_RAY_SPDIF (1 << 7) +#define HDSPE_CHAN_RAY_ADAT1 (1 << 8) +#define HDSPE_CHAN_RAY_ADAT2 (1 << 9) +#define HDSPE_CHAN_RAY_ADAT3 (1 << 10) +#define HDSPE_CHAN_RAY_ADAT4 (1 << 11) #define HDSPE_CHAN_RAY_ALL (HDSPE_CHAN_RAY_AES | \ HDSPE_CHAN_RAY_SPDIF | \ HDSPE_CHAN_RAY_ADAT1 | \ HDSPE_CHAN_RAY_ADAT2 | \ HDSPE_CHAN_RAY_ADAT3 | \ HDSPE_CHAN_RAY_ADAT4) struct hdspe_channel { uint32_t ports; char *descr; }; /* Clock sources */ #define HDSPE_SETTING_MASTER (1 << 0) #define HDSPE_SETTING_CLOCK_MASK 0x1f #define HDSPE_STATUS1_CLOCK_SHIFT 28 #define HDSPE_STATUS1_CLOCK_MASK (0x0f << HDSPE_STATUS1_CLOCK_SHIFT) #define HDSPE_STATUS1_CLOCK(n) (((n) << HDSPE_STATUS1_CLOCK_SHIFT) & \ HDSPE_STATUS1_CLOCK_MASK) struct hdspe_clock_source { char *name; uint32_t setting; uint32_t status; uint32_t lock_bit; uint32_t sync_bit; }; 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 */ struct pcmchan_caps *caps; uint32_t cap_fmts[4]; uint32_t dir; uint32_t format; uint32_t ports; uint32_t lvol; uint32_t rvol; /* Buffer */ uint32_t *data; uint32_t size; uint32_t position; /* 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; uint32_t force_period; uint32_t force_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))