diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -519,6 +519,7 @@ snd_es137x.4 \ snd_fm801.4 \ snd_hda.4 \ + snd_hdsp.4 \ snd_hdspe.4 \ snd_ich.4 \ snd_maestro3.4 \ diff --git a/share/man/man4/pcm.4 b/share/man/man4/pcm.4 --- a/share/man/man4/pcm.4 +++ b/share/man/man4/pcm.4 @@ -101,6 +101,8 @@ .It .Xr snd_hda 4 (enabled by default on amd64, i386) .It +.Xr snd_hdsp 4 +.It .Xr snd_hdspe 4 .It .Xr snd_ich 4 (enabled by default on amd64, i386) @@ -609,6 +611,7 @@ .Xr snd_es137x 4 , .Xr snd_fm801 4 , .Xr snd_hda 4 , +.Xr snd_hdsp 4 , .Xr snd_hdspe 4 , .Xr snd_ich 4 , .Xr snd_maestro3 4 , diff --git a/share/man/man4/snd_hdsp.4 b/share/man/man4/snd_hdsp.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/snd_hdsp.4 @@ -0,0 +1,154 @@ +.\" Copyright (c) 2012 Ruslan Bukin +.\" Copyright (c) 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. +.\" +.Dd May 1, 2024 +.Dt SND_HDSP 4 +.Os +.Sh NAME +.Nm snd_hdsp +.Nd "RME HDSP bridge device driver" +.Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device sound" +.Cd "device snd_hdsp" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_hdsp_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +bridge driver allows the generic audio driver +.Xr sound 4 +to attach to RME HDSP audio devices. +.Sh HARDWARE +The +.Nm +driver supports the following audio devices: +.Pp +.Bl -bullet -compact +.It +RME HDSP 9632 +.It +RME HDSP 9652 +.El +.Pp +By default, each +.Xr pcm 4 +device corresponds to a physical port on the sound card. +For ADAT ports, 8 channel, 4 channel and 2 channel formats are supported. +The effective number of ADAT channels is 8 channels at single speed +(32kHz-48kHz) and 4 channels at double speed (64kHz-96kHz). +Only the HDSP 9632 can operate at quad speed (128kHz-192kHz), ADAT is +disabled in this mode. +Depending on sample rate and channel format selected, not all pcm channels can +be mapped to ADAT channels and vice versa. +.Sh LOADER TUNABLES +These settings can be entered at the +.Xr loader 8 +prompt or in +.Xr loader.conf 5 . +.Bl -tag -width indent +.It Va hw.hdsp.unified_pcm +If set to 1, all physical ports are combined into one unified pcm device. +When opened in multi-channel audio software, this makes all ports available +at the same time, and fully synchronized. +For resulting channel numbers consult the following table: +.El +.Bl -column "Sound Card" "Single Speed" "Double Speed" "Quad Speed" +.Sy "Sound Card" Ta Sy "Single Speed" Ta Sy "Double Speed" Ta Sy "Quad Speed" +.It "" Ta "Play | Rec" Ta "Play | Rec" Ta "Play | Rec" +.It HDSP 9632 Ta " 12 | 12" Ta " 8 | 8" Ta " 4 | 4" +.It HDSP 9652 Ta " 26 | 26" Ta " 14 | 14" Ta " - | -" +.El +.Sh SYSCTL TUNABLES +These settings and informational values can be accessed at runtime with the +.Xr sysctl 8 +command. +If multiple RME HDSP sound cards are installed, each device has a separate +configuration. +To adjust the following sysctl identifiers for a specific sound card, insert +the respective device number in place of +.Ql 0 . +.Bl -tag -width indent +.It Va dev.hdsp.0.sample_rate +Set a fixed sample rate from 32000, 44100, 48000, up to 192000. +This is usually required for digital connections (AES, S/PDIF, ADAT). +The default value of 0 adjusts the sample rate according to pcm device settings. +.It Va dev.hdsp.0.period +The number of samples processed per interrupt, from 32, 64, 128, up to 4096. +Setting a lower value here results in less latency, but increases system load +due to frequent interrupt processing. +Extreme values may cause audio gaps and glitches. +.It Va dev.hdsp.0.clock_list +Lists possible clock sources to sync with, depending on the hardware model. +This includes internal and external master clocks as well as incoming digital +audio signals like AES, S/PDIF and ADAT. +.It Va dev.hdsp.0.clock_preference +Select a preferred clock source from the clock list. +HDSP cards will sync to this clock source when available, but fall back to +auto-sync with any other digital clock signal they receive. +Set this to +.Ql internal +if the HDSP card should act as master clock. +.It Va dev.hdsp.0.clock_source +Shows the actual clock source in use (read only). +This differs from what is set as clock preference when in auto-sync mode. +.It Va dev.hdsp.0.sync_status +Display the current sync status of all external clock sources. +Status indications are +.Ql none +for no signal at all, +.Ql lock +for when a valid signal is present, and +.Ql sync +for accurately synchronized signals (required for recording digital +audio). +.El +.Pp +Where appropriate these sysctl values are modeled after official RME software on +other platforms, and adopt their terminology. +Consult the RME user manuals for additional information. +.Sh SEE ALSO +.Xr sound 4 +.Sh HISTORY +The +.Nm +device driver first appeared in +.Fx 15.0 . +.Sh AUTHORS +.An -nosplit +Based on +.Xr snd_hdspe 4 +originally written by +.An Ruslan Bukin . +All adaptation to HDSP cards by +.An Florian Walpen . diff --git a/sys/conf/NOTES b/sys/conf/NOTES --- a/sys/conf/NOTES +++ b/sys/conf/NOTES @@ -2093,6 +2093,7 @@ # snd_fm801: Forte Media FM801 PCI. # snd_hda: Intel High Definition Audio (Controller) and # compatible. +# snd_hdsp: RME HDSP 9632 and HDSP 9652 # 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 @@ -2120,6 +2121,7 @@ device snd_es137x device snd_fm801 device snd_hda +device snd_hdsp device snd_hdspe device snd_ich device snd_maestro3 diff --git a/sys/conf/files b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -3115,6 +3115,8 @@ 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/hdsp.c optional snd_hdsp pci +dev/sound/pci/hdsp-pcm.c optional snd_hdsp 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 diff --git a/sys/dev/sound/driver.c b/sys/dev/sound/driver.c --- a/sys/dev/sound/driver.c +++ b/sys/dev/sound/driver.c @@ -67,6 +67,7 @@ MODULE_DEPEND(snd_driver, snd_es137x, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_fm801, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_hda, 1, 1, 1); +MODULE_DEPEND(snd_driver, snd_hdsp, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_hdspe, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_ich, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_maestro3, 1, 1, 1); diff --git a/sys/dev/sound/pci/hdsp-pcm.c b/sys/dev/sound/pci/hdsp-pcm.c new file mode 100644 --- /dev/null +++ b/sys/dev/sound/pci/hdsp-pcm.c @@ -0,0 +1,1134 @@ +/*- + * 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 HDSP driver for FreeBSD (pcm-part). + * Supported cards: HDSP 9632, HDSP 9652. + */ + +#include + +#include +#include + +#include +#include + +#include + +#define HDSP_MATRIX_MAX 8 + +struct hdsp_latency { + uint32_t n; + uint32_t period; + float ms; +}; + +static struct hdsp_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 hdsp_rate { + uint32_t speed; + uint32_t reg; +}; + +static struct hdsp_rate rate_map[] = { + { 32000, (HDSP_FREQ_32000) }, + { 44100, (HDSP_FREQ_44100) }, + { 48000, (HDSP_FREQ_48000) }, + { 64000, (HDSP_FREQ_32000 | HDSP_FREQ_DOUBLE) }, + { 88200, (HDSP_FREQ_44100 | HDSP_FREQ_DOUBLE) }, + { 96000, (HDSP_FREQ_48000 | HDSP_FREQ_DOUBLE) }, + { 128000, (HDSP_FREQ_32000 | HDSP_FREQ_QUAD) }, + { 176400, (HDSP_FREQ_44100 | HDSP_FREQ_QUAD) }, + { 192000, (HDSP_FREQ_48000 | HDSP_FREQ_QUAD) }, + + { 0, 0 }, +}; + +static uint32_t +hdsp_adat_slot_map(uint32_t speed) +{ + /* ADAT slot bitmap depends on sample rate. */ + if (speed <= 48000) + return (0x000000ff); /* 8 channels single speed. */ + else if (speed <= 96000) + return (0x000000aa); /* 4 channels (1,3,5,7) double speed. */ + else + return (0x00000000); /* ADAT disabled at quad speed. */ +} + +static uint32_t +hdsp_port_slot_map(uint32_t ports, uint32_t speed) +{ + uint32_t slot_map = 0; + + if (ports & HDSP_CHAN_9632_ALL) { + /* Map HDSP 9632 ports to slot bitmap. */ + if (ports & HDSP_CHAN_9632_ADAT) + slot_map |= (hdsp_adat_slot_map(speed) << 0); + if (ports & HDSP_CHAN_9632_SPDIF) + slot_map |= (0x03 << 8); /* 2 channels SPDIF. */ + if (ports & HDSP_CHAN_9632_LINE) + slot_map |= (0x03 << 10); /* 2 channels line. */ + if (ports & HDSP_CHAN_9632_EXT_BOARD) + slot_map |= (0x0f << 12); /* 4 channels extension. */ + } else if ((ports & HDSP_CHAN_9652_ALL) && (speed <= 96000)) { + /* Map HDSP 9652 ports to slot bitmap, no quad speed. */ + if (ports & HDSP_CHAN_9652_ADAT1) + slot_map |= (hdsp_adat_slot_map(speed) << 0); + if (ports & HDSP_CHAN_9652_ADAT2) + slot_map |= (hdsp_adat_slot_map(speed) << 8); + if (ports & HDSP_CHAN_9652_ADAT3) + slot_map |= (hdsp_adat_slot_map(speed) << 16); + if (ports & HDSP_CHAN_9652_SPDIF) + slot_map |= (0x03 << 24); /* 2 channels SPDIF. */ + } + + return (slot_map); +} + +static uint32_t +hdsp_slot_first(uint32_t slots) +{ + return (slots & (~(slots - 1))); /* Extract first bit set. */ +} + +static uint32_t +hdsp_slot_first_row(uint32_t slots) +{ + uint32_t ends; + + /* Ends of slot rows are followed by a slot which is not in the set. */ + ends = slots & (~(slots >> 1)); + /* First row of contiguous slots ends in the first row end. */ + return (slots & (ends ^ (ends - 1))); +} + +static uint32_t +hdsp_slot_first_n(uint32_t slots, unsigned int n) +{ + /* Clear all but the first n slots. */ + for (uint32_t slot = 1; slot != 0; slot <<= 1) { + if ((slots & slot) && n > 0) + --n; + else + slots &= ~slot; + } + return (slots); +} + +static unsigned int +hdsp_slot_count(uint32_t slots) +{ + return (bitcount32(slots)); +} + +static unsigned int +hdsp_slot_offset(uint32_t slots) +{ + return (hdsp_slot_count(hdsp_slot_first(slots) - 1)); +} + +static unsigned int +hdsp_slot_channel_offset(uint32_t subset, uint32_t slots) +{ + uint32_t preceding; + + /* Make sure we have a subset of slots. */ + subset &= slots; + /* Include all slots preceding the first one of the subset. */ + preceding = slots & (hdsp_slot_first(subset) - 1); + + return (hdsp_slot_count(preceding)); +} + +static uint32_t +hdsp_port_first(uint32_t ports) +{ + return (ports & (~(ports - 1))); /* Extract first bit set. */ +} + +static unsigned int +hdsp_port_slot_count(uint32_t ports, uint32_t speed) +{ + return (hdsp_slot_count(hdsp_port_slot_map(ports, speed))); +} + +static unsigned int +hdsp_port_slot_count_max(uint32_t ports) +{ + return (hdsp_slot_count(hdsp_port_slot_map(ports, 48000))); +} + +static uint32_t +hdsp_channel_play_ports(struct hdsp_channel *hc) +{ + return (hc->ports & (HDSP_CHAN_9632_ALL | HDSP_CHAN_9652_ALL)); +} + +static uint32_t +hdsp_channel_rec_ports(struct hdsp_channel *hc) +{ + return (hc->ports & (HDSP_CHAN_9632_ALL | HDSP_CHAN_9652_ALL)); +} + +static int +hdsp_hw_mixer(struct sc_chinfo *ch, unsigned int dst, + unsigned int src, unsigned short data) +{ + struct sc_pcminfo *scp; + struct sc_info *sc; + uint32_t value; + int offset; + + scp = ch->parent; + sc = scp->sc; + + offset = 0; + value = (HDSP_MIN_GAIN << 16) | (uint16_t) data; + + if (ch->dir != PCMDIR_PLAY) + return (0); + + switch (sc->type) { + case HDSP_9632: + /* Mixer is 2 rows of sources (inputs, playback) per output. */ + offset = dst * (2 * HDSP_MIX_SLOTS_9632); + /* Source index in the second row (playback). */ + offset += HDSP_MIX_SLOTS_9632 + src; + break; + case HDSP_9652: + /* Mixer is 2 rows of sources (inputs, playback) per output. */ + offset = dst * (2 * HDSP_MIX_SLOTS_9652); + /* Source index in the second row (playback). */ + offset += HDSP_MIX_SLOTS_9652 + src; + break; + default: + return (0); + } + + /* + * We have to write mixer matrix values in pairs, with the second + * (odd) value in the upper 16 bits of the 32 bit value. + * Make value offset even and shift value accordingly. + * Assume the paired value to be silenced, since we only set gain + * on the diagonal where src and dst are the same. + */ + if (offset % 2) { + offset -= 1; + value = (value << 16) | HDSP_MIN_GAIN; + } + + hdsp_write_4(sc, HDSP_MIXER_BASE + offset * sizeof(uint16_t), value); + + return (0); +}; + +static int +hdspchan_setgain(struct sc_chinfo *ch) +{ + uint32_t port, ports; + uint32_t slot, slots; + unsigned int offset; + unsigned short volume; + + /* Iterate through all physical ports of the channel. */ + ports = ch->ports; + port = hdsp_port_first(ports); + while (port != 0) { + /* + * Get slot map from physical port. + * Unlike DMA buffers, the hardware mixer's channel mapping + * does not change with double or quad speed sample rates. + */ + slots = hdsp_port_slot_map(port, 48000); + slot = hdsp_slot_first(slots); + + /* Treat first slot as left channel. */ + volume = ch->lvol * HDSP_MAX_GAIN / 100; + while (slot != 0) { + offset = hdsp_slot_offset(slot); + hdsp_hw_mixer(ch, offset, offset, volume); + + slots &= ~slot; + slot = hdsp_slot_first(slots); + + /* Subsequent slots all get the right channel volume. */ + volume = ch->rvol * HDSP_MAX_GAIN / 100; + } + + ports &= ~port; + port = hdsp_port_first(ports); + } + + return (0); +} + +static int +hdspmixer_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 (hdsp_channel_play_ports(scp->hc)) + mask |= SOUND_MASK_VOLUME; + + if (hdsp_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 +hdspmixer_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, "hdspmixer_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) + hdspchan_setgain(ch); + } + } + + return (0); +} + +static kobj_method_t hdspmixer_methods[] = { + KOBJMETHOD(mixer_init, hdspmixer_init), + KOBJMETHOD(mixer_set, hdspmixer_set), + KOBJMETHOD_END +}; +MIXER_DECLARE(hdspmixer); + +static void +hdspchan_enable(struct sc_chinfo *ch, int value) +{ + struct sc_pcminfo *scp; + struct sc_info *sc; + uint32_t slot, slots; + unsigned int offset; + int reg; + + scp = ch->parent; + sc = scp->sc; + + if (ch->dir == PCMDIR_PLAY) + reg = HDSP_OUT_ENABLE_BASE; + else + reg = HDSP_IN_ENABLE_BASE; + + ch->run = value; + + /* Iterate through all slots of the channel's physical ports. */ + slots = hdsp_port_slot_map(ch->ports, sc->speed); + slot = hdsp_slot_first(slots); + while (slot != 0) { + /* Set register to enable or disable slot. */ + offset = hdsp_slot_offset(slot); + hdsp_write_1(sc, reg + (4 * offset), value); + + slots &= ~slot; + slot = hdsp_slot_first(slots); + } +} + +static int +hdsp_running(struct sc_info *sc) +{ + struct sc_pcminfo *scp; + struct sc_chinfo *ch; + device_t *devlist; + int devcount; + int i, j; + int running; + + running = 0; + + devlist = NULL; + devcount = 0; + + if (device_get_children(sc->dev, &devlist, &devcount) != 0) + running = 1; /* On error, avoid channel config changes. */ + + for (i = 0; running == 0 && i < devcount; i++) { + scp = device_get_ivars(devlist[i]); + for (j = 0; j < scp->chnum; j++) { + ch = &scp->chan[j]; + if (ch->run) { + running = 1; + break; + } + } + } + +#if 0 + if (running == 1) + device_printf(sc->dev, "hdsp is running\n"); +#endif + + free(devlist, M_TEMP); + + return (running); +} + +static void +hdsp_start_audio(struct sc_info *sc) +{ + + sc->ctrl_register |= (HDSP_AUDIO_INT_ENABLE | HDSP_ENABLE); + hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); +} + +static void +hdsp_stop_audio(struct sc_info *sc) +{ + + if (hdsp_running(sc) == 1) + return; + + sc->ctrl_register &= ~(HDSP_AUDIO_INT_ENABLE | HDSP_ENABLE); + hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); +} + +static void +buffer_mux_write(uint32_t *dma, uint32_t *pcm, unsigned int pos, + unsigned int pos_end, unsigned int width, unsigned int channels) +{ + unsigned int slot; + + for (; pos < pos_end; ++pos) { + for (slot = 0; slot < width; slot++) { + dma[slot * HDSP_CHANBUF_SAMPLES + pos] = + pcm[pos * channels + slot]; + } + } +} + +static void +buffer_mux_port(uint32_t *dma, uint32_t *pcm, uint32_t subset, uint32_t slots, + unsigned int pos, unsigned int samples, unsigned int channels) +{ + unsigned int slot_offset, width; + unsigned int chan_pos; + + /* Translate DMA slot offset to DMA buffer offset. */ + slot_offset = hdsp_slot_offset(subset); + dma += slot_offset * HDSP_CHANBUF_SAMPLES; + + /* Channel position of the slot subset. */ + chan_pos = hdsp_slot_channel_offset(subset, slots); + pcm += chan_pos; + + /* Only copy channels supported by both hardware and pcm format. */ + width = hdsp_slot_count(subset); + + /* Let the compiler inline and loop unroll common cases. */ + if (width == 1) + buffer_mux_write(dma, pcm, pos, pos + samples, 1, channels); + else if (width == 2) + buffer_mux_write(dma, pcm, pos, pos + samples, 2, channels); + else if (width == 4) + buffer_mux_write(dma, pcm, pos, pos + samples, 4, channels); + else if (width == 8) + buffer_mux_write(dma, pcm, pos, pos + samples, 8, channels); + else + buffer_mux_write(dma, pcm, pos, pos + samples, width, channels); +} + +static void +buffer_demux_read(uint32_t *dma, uint32_t *pcm, unsigned int pos, + unsigned int pos_end, unsigned int width, unsigned int channels) +{ + unsigned int slot; + + for (; pos < pos_end; ++pos) { + for (slot = 0; slot < width; slot++) { + pcm[pos * channels + slot] = + dma[slot * HDSP_CHANBUF_SAMPLES + pos]; + } + } +} + +static void +buffer_demux_port(uint32_t *dma, uint32_t *pcm, uint32_t subset, uint32_t slots, + unsigned int pos, unsigned int samples, unsigned int channels) +{ + unsigned int slot_offset, width; + unsigned int chan_pos; + + /* Translate DMA slot offset to DMA buffer offset. */ + slot_offset = hdsp_slot_offset(subset); + dma += slot_offset * HDSP_CHANBUF_SAMPLES; + + /* Channel position of the slot subset. */ + chan_pos = hdsp_slot_channel_offset(subset, slots); + pcm += chan_pos; + + /* Only copy channels supported by both hardware and pcm format. */ + width = hdsp_slot_count(subset); + + /* Let the compiler inline and loop unroll common cases. */ + if (width == 1) + buffer_demux_read(dma, pcm, pos, pos + samples, 1, channels); + else if (width == 2) + buffer_demux_read(dma, pcm, pos, pos + samples, 2, channels); + else if (width == 4) + buffer_demux_read(dma, pcm, pos, pos + samples, 4, channels); + else if (width == 8) + buffer_demux_read(dma, pcm, pos, pos + samples, 8, channels); + else + buffer_demux_read(dma, pcm, pos, pos + samples, width, 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, slots; + uint32_t dma_pos; + unsigned int pos, length, remainder, offset, buffer_size; + unsigned int channels; + + scp = ch->parent; + sc = scp->sc; + + channels = AFMT_CHANNEL(ch->format); /* Number of PCM channels. */ + + /* HDSP cards read / write a double buffer, twice the latency period. */ + buffer_size = 2 * sc->period * sizeof(uint32_t); + + /* Derive buffer position and length to be copied. */ + if (ch->dir == PCMDIR_PLAY) { + /* Buffer position scaled down to a single channel. */ + pos = sndbuf_getreadyptr(ch->buffer) / channels; + length = sndbuf_getready(ch->buffer) / channels; + /* Copy no more than 2 periods in advance. */ + if (length > buffer_size) + length = buffer_size; + /* Skip what was already copied last time. */ + offset = (ch->position + buffer_size) - pos; + offset %= buffer_size; + if (offset <= length) { + pos = (pos + offset) % buffer_size; + length -= offset; + } + } else { + /* Buffer position scaled down to a single channel. */ + pos = sndbuf_getfreeptr(ch->buffer) / channels; + /* Get DMA buffer write position. */ + dma_pos = hdsp_read_2(sc, HDSP_STATUS_REG); + dma_pos &= HDSP_BUF_POSITION_MASK; + dma_pos %= buffer_size; + /* Copy what is newly available. */ + length = (dma_pos + buffer_size) - pos; + length %= buffer_size; + } + + /* Position and length in samples (4 bytes). */ + pos /= 4; + length /= 4; + buffer_size /= sizeof(uint32_t); + + /* Split copy length to wrap around at buffer end. */ + remainder = 0; + if (pos + length > buffer_size) + remainder = (pos + length) - buffer_size; + + /* Iterate through rows of contiguous slots. */ + slots = hdsp_port_slot_map(ch->ports, sc->speed); + slots = hdsp_slot_first_n(slots, channels); + row = hdsp_slot_first_row(slots); + + while (row != 0) { + if (ch->dir == PCMDIR_PLAY) { + buffer_mux_port(sc->pbuf, ch->data, row, slots, pos, + length - remainder, channels); + buffer_mux_port(sc->pbuf, ch->data, row, slots, 0, + remainder, channels); + } else { + buffer_demux_port(sc->rbuf, ch->data, row, slots, pos, + length - remainder, channels); + buffer_demux_port(sc->rbuf, ch->data, row, slots, 0, + remainder, channels); + } + + slots &= ~row; + row = hdsp_slot_first_row(slots); + } + + ch->position = ((pos + length) * 4) % buffer_size; +} + +static int +clean(struct sc_chinfo *ch) +{ + struct sc_pcminfo *scp; + struct sc_info *sc; + uint32_t *buf; + uint32_t slot, slots; + unsigned int offset; + + scp = ch->parent; + sc = scp->sc; + buf = sc->rbuf; + + if (ch->dir == PCMDIR_PLAY) + buf = sc->pbuf; + + /* Iterate through all of the channel's slots. */ + slots = hdsp_port_slot_map(ch->ports, sc->speed); + slot = hdsp_slot_first(slots); + while (slot != 0) { + /* Clear the slot's buffer. */ + offset = hdsp_slot_offset(slot); + bzero(buf + offset * HDSP_CHANBUF_SAMPLES, HDSP_CHANBUF_SIZE); + + slots &= ~slot; + slot = hdsp_slot_first(slots); + } + + ch->position = 0; + + return (0); +} + +/* Channel interface. */ +static void * +hdspchan_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 = hdsp_channel_play_ports(scp->hc); + else + ch->ports = hdsp_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, hdsp_port_slot_count(ch->ports, 48000), 0); + ch->cap_fmts[1] = + SND_FORMAT(AFMT_S32_LE, hdsp_port_slot_count(ch->ports, 96000), 0); + ch->cap_fmts[2] = + SND_FORMAT(AFMT_S32_LE, hdsp_port_slot_count(ch->ports, 192000), 0); + ch->cap_fmts[3] = 0; + + ch->caps = malloc(sizeof(struct pcmchan_caps), M_HDSP, M_NOWAIT); + *(ch->caps) = (struct pcmchan_caps) {32000, 192000, ch->cap_fmts, 0}; + + /* HDSP 9652 does not support quad speed sample rates. */ + if (sc->type == HDSP_9652) { + ch->cap_fmts[2] = SND_FORMAT(AFMT_S32_LE, 2, 0); + ch->caps->maxspeed = 96000; + } + + /* Allocate maximum buffer size. */ + ch->size = HDSP_CHANBUF_SIZE * hdsp_port_slot_count_max(ch->ports); + ch->data = malloc(ch->size, M_HDSP, 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"); + return (NULL); + } + + return (ch); +} + +static int +hdspchan_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, "hdspchan_trigger(): start\n"); +#endif + hdspchan_enable(ch, 1); + hdspchan_setgain(ch); + hdsp_start_audio(sc); + break; + + case PCMTRIG_STOP: + case PCMTRIG_ABORT: +#if 0 + device_printf(scp->dev, "hdspchan_trigger(): stop or abort\n"); +#endif + clean(ch); + hdspchan_enable(ch, 0); + hdsp_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 +hdspchan_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 = hdsp_read_2(sc, HDSP_STATUS_REG); + snd_mtxunlock(sc->lock); + + pos = ret & HDSP_BUF_POSITION_MASK; + pos %= (2 * sc->period * sizeof(uint32_t)); /* Double buffer. */ + pos *= AFMT_CHANNEL(ch->format); /* Hardbuf with multiple channels. */ + + return (pos); +} + +static int +hdspchan_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, "hdspchan_free()\n"); +#endif + + snd_mtxlock(sc->lock); + if (ch->data != NULL) { + free(ch->data, M_HDSP); + ch->data = NULL; + } + if (ch->caps != NULL) { + free(ch->caps, M_HDSP); + ch->caps = NULL; + } + snd_mtxunlock(sc->lock); + + return (0); +} + +static int +hdspchan_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, "hdspchan_setformat(%d)\n", format); +#endif + + ch->format = format; + + return (0); +} + +static uint32_t +hdspchan_setspeed(kobj_t obj, void *data, uint32_t speed) +{ + struct sc_pcminfo *scp; + struct hdsp_rate *hr; + struct sc_chinfo *ch; + struct sc_info *sc; + int threshold; + int i; + + ch = data; + scp = ch->parent; + sc = scp->sc; + hr = NULL; + +#if 0 + device_printf(scp->dev, "hdspchan_setspeed(%d)\n", speed); +#endif + + if (hdsp_running(sc) == 1) + goto end; + + /* HDSP 9652 only supports sample rates up to 96kHz. */ + if (sc->type == HDSP_9652 && speed > 96000) + speed = 96000; + + 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; + } + } + + /* Write frequency on the device. */ + sc->ctrl_register &= ~HDSP_FREQ_MASK; + sc->ctrl_register |= hr->reg; + hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); + + if (sc->type == HDSP_9632) { + /* Set DDS value. */ + hdsp_write_4(sc, HDSP_FREQ_REG, hdsp_freq_reg_value(hr->speed)); + } + + sc->speed = hr->speed; +end: + + return (sc->speed); +} + +static uint32_t +hdspchan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) +{ + struct hdsp_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, "hdspchan_setblocksize(%d)\n", blocksize); +#endif + + if (hdsp_running(sc) == 1) + goto end; + + if (blocksize > HDSP_LAT_BYTES_MAX) + blocksize = HDSP_LAT_BYTES_MAX; + else if (blocksize < HDSP_LAT_BYTES_MIN) + blocksize = HDSP_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 &= ~HDSP_LAT_MASK; + sc->ctrl_register |= hdsp_encode_latency(hl->n); + hdsp_write_4(sc, HDSP_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, 2, + (sc->period * AFMT_CHANNEL(ch->format) * sizeof(uint32_t))); + + /* Reset pointer, rewrite frequency (same register) for 9632. */ + hdsp_write_4(sc, HDSP_RESET_POINTER, 0); + if (sc->type == HDSP_9632) + hdsp_write_4(sc, HDSP_FREQ_REG, hdsp_freq_reg_value(sc->speed)); +end: + + return (sndbuf_getblksz(ch->buffer)); +} + +static uint32_t hdsp_bkp_fmt[] = { + SND_FORMAT(AFMT_S32_LE, 2, 0), + 0 +}; + +/* Capabilities fallback, no quad speed for HDSP 9652 compatibility. */ +static struct pcmchan_caps hdsp_bkp_caps = {32000, 96000, hdsp_bkp_fmt, 0}; + +static struct pcmchan_caps * +hdspchan_getcaps(kobj_t obj, void *data) +{ + struct sc_chinfo *ch; + + ch = data; + +#if 0 + device_printf(ch->parent->dev, "hdspchan_getcaps()\n"); +#endif + + if (ch->caps != NULL) + return (ch->caps); + + return (&hdsp_bkp_caps); +} + +static kobj_method_t hdspchan_methods[] = { + KOBJMETHOD(channel_init, hdspchan_init), + KOBJMETHOD(channel_free, hdspchan_free), + KOBJMETHOD(channel_setformat, hdspchan_setformat), + KOBJMETHOD(channel_setspeed, hdspchan_setspeed), + KOBJMETHOD(channel_setblocksize, hdspchan_setblocksize), + KOBJMETHOD(channel_trigger, hdspchan_trigger), + KOBJMETHOD(channel_getptr, hdspchan_getptr), + KOBJMETHOD(channel_getcaps, hdspchan_getcaps), + KOBJMETHOD_END +}; +CHANNEL_DECLARE(hdspchan); + +static int +hdsp_pcm_probe(device_t dev) +{ + +#if 0 + device_printf(dev,"hdsp_pcm_probe()\n"); +#endif + + return (0); +} + +static uint32_t +hdsp_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 +hdsp_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 = &hdsp_pcm_intr; + + if (scp->hc->ports & HDSP_CHAN_9632_ALL) + buf = "9632"; + else if (scp->hc->ports & HDSP_CHAN_9652_ALL) + buf = "9652"; + else + buf = "?"; + device_set_descf(dev, "HDSP %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 (hdsp_port_slot_count_max(scp->hc->ports) > HDSP_MATRIX_MAX) + /* Disable vchan conversion, too many channels. */ + pcm_flags |= SD_F_BITPERFECT; + pcm_setflags(dev, pcm_flags); + + play = (hdsp_channel_play_ports(scp->hc)) ? 1 : 0; + rec = (hdsp_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, &hdspchan_class, scp); + scp->chnum++; + } + + if (rec) { + pcm_addchan(dev, PCMDIR_REC, &hdspchan_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, &hdspmixer_class, scp); + + return (0); +} + +static int +hdsp_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 hdsp_pcm_methods[] = { + DEVMETHOD(device_probe, hdsp_pcm_probe), + DEVMETHOD(device_attach, hdsp_pcm_attach), + DEVMETHOD(device_detach, hdsp_pcm_detach), + { 0, 0 } +}; + +static driver_t hdsp_pcm_driver = { + "pcm", + hdsp_pcm_methods, + PCM_SOFTC_SIZE, +}; + +DRIVER_MODULE(snd_hdsp_pcm, hdsp, hdsp_pcm_driver, 0, 0); +MODULE_DEPEND(snd_hdsp, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_VERSION(snd_hdsp, 1); diff --git a/sys/dev/sound/pci/hdsp.h b/sys/dev/sound/pci/hdsp.h new file mode 100644 --- /dev/null +++ b/sys/dev/sound/pci/hdsp.h @@ -0,0 +1,257 @@ +/*- + * 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_DEVICE_XILINX_HDSP 0x3fc5 /* HDSP 9652 */ +#define PCI_REVISION_9632 0x9b +#define PCI_REVISION_9652 0x6c + +#define HDSP_9632 0 +#define HDSP_9652 1 + +/* Hardware mixer */ +#define HDSP_OUT_ENABLE_BASE 128 +#define HDSP_IN_ENABLE_BASE 384 +#define HDSP_MIXER_BASE 4096 +#define HDSP_MAX_GAIN 32768 +#define HDSP_MIN_GAIN 0 +#define HDSP_MIX_SLOTS_9632 16 +#define HDSP_MIX_SLOTS_9652 26 +#define HDSP_CONTROL2_9652_MIXER (1 << 11) + +/* Buffer */ +#define HDSP_PAGE_ADDR_BUF_OUT 32 +#define HDSP_PAGE_ADDR_BUF_IN 36 +#define HDSP_BUF_POSITION_MASK 0x000FFC0 + +/* Frequency */ +#define HDSP_FREQ_0 (1 << 6) +#define HDSP_FREQ_1 (1 << 7) +#define HDSP_FREQ_DOUBLE (1 << 8) +#define HDSP_FREQ_QUAD (1 << 31) + +#define HDSP_FREQ_32000 HDSP_FREQ_0 +#define HDSP_FREQ_44100 HDSP_FREQ_1 +#define HDSP_FREQ_48000 (HDSP_FREQ_0 | HDSP_FREQ_1) +#define HDSP_FREQ_MASK (HDSP_FREQ_0 | HDSP_FREQ_1 | \ + HDSP_FREQ_DOUBLE | HDSP_FREQ_QUAD) +#define HDSP_FREQ_MASK_DEFAULT HDSP_FREQ_48000 +#define HDSP_FREQ_REG 0 +#define HDSP_FREQ_9632 104857600000000ULL +#define hdsp_freq_multiplier(s) (((s) > 96000) ? 4 : \ + (((s) > 48000) ? 2 : 1)) +#define hdsp_freq_single(s) ((s) / hdsp_freq_multiplier(s)) +#define hdsp_freq_reg_value(s) (HDSP_FREQ_9632 / hdsp_freq_single(s)) + +#define HDSP_SPEED_DEFAULT 48000 + +/* Latency */ +#define HDSP_LAT_0 (1 << 1) +#define HDSP_LAT_1 (1 << 2) +#define HDSP_LAT_2 (1 << 3) +#define HDSP_LAT_MASK (HDSP_LAT_0 | HDSP_LAT_1 | HDSP_LAT_2) +#define HDSP_LAT_BYTES_MAX (4096 * 4) +#define HDSP_LAT_BYTES_MIN (32 * 4) +#define hdsp_encode_latency(x) (((x)<<1) & HDSP_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 HDSP_RESET_POINTER 0 +#define HDSP_CONTROL_REG 64 +#define HDSP_CONTROL2_REG 256 +#define HDSP_STATUS_REG 0 +#define HDSP_STATUS2_REG 192 + +#define HDSP_ENABLE (1 << 0) +#define HDSP_CONTROL_SPDIF_COAX (1 << 14) +#define HDSP_CONTROL_LINE_OUT (1 << 24) + +/* Interrupts */ +#define HDSP_AUDIO_IRQ_PENDING (1 << 0) +#define HDSP_AUDIO_INT_ENABLE (1 << 5) +#define HDSP_INTERRUPT_ACK 96 + +/* Channels */ +#define HDSP_MAX_SLOTS 64 /* Mono channels */ +#define HDSP_MAX_CHANS (HDSP_MAX_SLOTS / 2) /* Stereo pairs */ + +#define HDSP_CHANBUF_SAMPLES (16 * 1024) +#define HDSP_CHANBUF_SIZE (4 * HDSP_CHANBUF_SAMPLES) +#define HDSP_DMASEGSIZE (HDSP_CHANBUF_SIZE * HDSP_MAX_SLOTS) + +#define HDSP_CHAN_9632_ADAT (1 << 0) +#define HDSP_CHAN_9632_SPDIF (1 << 1) +#define HDSP_CHAN_9632_LINE (1 << 2) +#define HDSP_CHAN_9632_ALL (HDSP_CHAN_9632_ADAT | \ + HDSP_CHAN_9632_SPDIF | \ + HDSP_CHAN_9632_LINE) +#define HDSP_CHAN_9632_EXT_BOARD (1 << 3) + +#define HDSP_CHAN_9652_ADAT1 (1 << 5) +#define HDSP_CHAN_9652_ADAT2 (1 << 6) +#define HDSP_CHAN_9652_ADAT3 (1 << 7) +#define HDSP_CHAN_9652_ADAT_ALL (HDSP_CHAN_9652_ADAT1 | \ + HDSP_CHAN_9652_ADAT2 | \ + HDSP_CHAN_9652_ADAT3) +#define HDSP_CHAN_9652_SPDIF (1 << 8) +#define HDSP_CHAN_9652_ALL (HDSP_CHAN_9652_ADAT_ALL | \ + HDSP_CHAN_9652_SPDIF) + +struct hdsp_channel { + uint32_t ports; + char *descr; +}; + +enum hdsp_clock_type { + HDSP_CLOCK_INTERNAL, + HDSP_CLOCK_ADAT1, + HDSP_CLOCK_ADAT2, + HDSP_CLOCK_ADAT3, + HDSP_CLOCK_SPDIF, + HDSP_CLOCK_WORD, + HDSP_CLOCK_ADAT_SYNC +}; + +/* Preferred clock source. */ +#define HDSP_CONTROL_MASTER (1 << 4) +#define HDSP_CONTROL_CLOCK_MASK (HDSP_CONTROL_MASTER | (1 << 13) | \ + (1 << 16) | (1 << 17)) +#define HDSP_CONTROL_CLOCK(n) (((n & 0x04) << 11) | ((n & 0x03) << 16)) + +/* Autosync selected clock source. */ +#define HDSP_STATUS2_CLOCK(n) ((n & 0x07) << 8) +#define HDSP_STATUS2_CLOCK_MASK HDSP_STATUS2_CLOCK(0x07); + +struct hdsp_clock_source { + char *name; + enum hdsp_clock_type type; +}; + +static MALLOC_DEFINE(M_HDSP, "hdsp", "hdsp 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[HDSP_MAX_CHANS]; + struct sc_info *sc; + struct hdsp_channel *hc; +}; + +/* HDSP device private data */ +struct sc_info { + device_t dev; + struct mtx *lock; + + uint32_t ctrl_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 hdsp_read_1(sc, regno) \ + bus_space_read_1((sc)->cst, (sc)->csh, (regno)) +#define hdsp_read_2(sc, regno) \ + bus_space_read_2((sc)->cst, (sc)->csh, (regno)) +#define hdsp_read_4(sc, regno) \ + bus_space_read_4((sc)->cst, (sc)->csh, (regno)) + +#define hdsp_write_1(sc, regno, data) \ + bus_space_write_1((sc)->cst, (sc)->csh, (regno), (data)) +#define hdsp_write_2(sc, regno, data) \ + bus_space_write_2((sc)->cst, (sc)->csh, (regno), (data)) +#define hdsp_write_4(sc, regno, data) \ + bus_space_write_4((sc)->cst, (sc)->csh, (regno), (data)) diff --git a/sys/dev/sound/pci/hdsp.c b/sys/dev/sound/pci/hdsp.c new file mode 100644 --- /dev/null +++ b/sys/dev/sound/pci/hdsp.c @@ -0,0 +1,793 @@ +/*- + * 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 HDSP driver for FreeBSD. + * Supported cards: HDSP 9632, HDSP 9652. + */ + +#include +#include + +#include +#include + +#include +#include + +#include + +static bool hdsp_unified_pcm = false; + +static SYSCTL_NODE(_hw, OID_AUTO, hdsp, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "PCI HDSP"); + +SYSCTL_BOOL(_hw_hdsp, OID_AUTO, unified_pcm, CTLFLAG_RWTUN, + &hdsp_unified_pcm, 0, "Combine physical ports in one unified pcm device"); + +static struct hdsp_clock_source hdsp_clock_source_table_9632[] = { + { "internal", HDSP_CLOCK_INTERNAL }, + { "adat", HDSP_CLOCK_ADAT1 }, + { "spdif", HDSP_CLOCK_SPDIF }, + { "word", HDSP_CLOCK_WORD }, + { NULL, HDSP_CLOCK_INTERNAL } +}; + +static struct hdsp_clock_source hdsp_clock_source_table_9652[] = { + { "internal", HDSP_CLOCK_INTERNAL }, + { "adat1", HDSP_CLOCK_ADAT1 }, + { "adat2", HDSP_CLOCK_ADAT2 }, + { "adat3", HDSP_CLOCK_ADAT3 }, + { "spdif", HDSP_CLOCK_SPDIF }, + { "word", HDSP_CLOCK_WORD }, + { "adat_sync", HDSP_CLOCK_ADAT_SYNC }, + { NULL, HDSP_CLOCK_INTERNAL } +}; + +static struct hdsp_channel chan_map_9632[] = { + { HDSP_CHAN_9632_ADAT, "adat" }, + { HDSP_CHAN_9632_SPDIF, "s/pdif" }, + { HDSP_CHAN_9632_LINE, "line" }, + { 0, NULL }, +}; + +static struct hdsp_channel chan_map_9632_uni[] = { + { HDSP_CHAN_9632_ALL, "all" }, + { 0, NULL }, +}; + +static struct hdsp_channel chan_map_9652[] = { + { HDSP_CHAN_9652_ADAT1, "adat1" }, + { HDSP_CHAN_9652_ADAT2, "adat2" }, + { HDSP_CHAN_9652_ADAT3, "adat3" }, + { HDSP_CHAN_9652_SPDIF, "s/pdif" }, + { 0, NULL }, +}; + +static struct hdsp_channel chan_map_9652_uni[] = { + { HDSP_CHAN_9652_ALL, "all" }, + { 0, NULL }, +}; + +static void +hdsp_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 = hdsp_read_1(sc, HDSP_STATUS_REG); + if (status & HDSP_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); + } + + hdsp_write_1(sc, HDSP_INTERRUPT_ACK, 0); + free(devlist, M_TEMP); + } + + snd_mtxunlock(sc->lock); +} + +static void +hdsp_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ +#if 0 + device_printf(sc->dev, "hdsp_dmapsetmap()\n"); +#endif +} + +static int +hdsp_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, hdsp_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 * HDSP_DMASEGSIZE, + /*nsegments*/2, + /*maxsegsz*/HDSP_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 = HDSP_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, + hdsp_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, + hdsp_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 +hdsp_map_dmabuf(struct sc_info *sc) +{ + uint32_t paddr, raddr; + + paddr = vtophys(sc->pbuf); + raddr = vtophys(sc->rbuf); + + hdsp_write_4(sc, HDSP_PAGE_ADDR_BUF_OUT, paddr); + hdsp_write_4(sc, HDSP_PAGE_ADDR_BUF_IN, raddr); +} + +static int +hdsp_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) && sc->type == HDSP_9632) + 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 +hdsp_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 uint32_t +hdsp_control_clock_preference(enum hdsp_clock_type type) +{ + switch (type) { + case HDSP_CLOCK_INTERNAL: + return (HDSP_CONTROL_MASTER); + case HDSP_CLOCK_ADAT1: + return (HDSP_CONTROL_CLOCK(0)); + case HDSP_CLOCK_ADAT2: + return (HDSP_CONTROL_CLOCK(1)); + case HDSP_CLOCK_ADAT3: + return (HDSP_CONTROL_CLOCK(2)); + case HDSP_CLOCK_SPDIF: + return (HDSP_CONTROL_CLOCK(3)); + case HDSP_CLOCK_WORD: + return (HDSP_CONTROL_CLOCK(4)); + case HDSP_CLOCK_ADAT_SYNC: + return (HDSP_CONTROL_CLOCK(5)); + default: + return (HDSP_CONTROL_MASTER); + } +} + +static int +hdsp_sysctl_clock_preference(SYSCTL_HANDLER_ARGS) +{ + struct sc_info *sc; + struct hdsp_clock_source *clock_table, *clock; + char buf[16] = "invalid"; + int error; + uint32_t control; + + sc = oidp->oid_arg1; + + /* Select sync ports table for device type. */ + if (sc->type == HDSP_9632) + clock_table = hdsp_clock_source_table_9632; + else if (sc->type == HDSP_9652) + clock_table = hdsp_clock_source_table_9652; + else + return (ENXIO); + + /* Extract preferred clock source from control register. */ + control = sc->ctrl_register & HDSP_CONTROL_CLOCK_MASK; + for (clock = clock_table; clock->name != NULL; ++clock) { + if (hdsp_control_clock_preference(clock->type) == control) + 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 control register. */ + if (clock->name != NULL) { + control = hdsp_control_clock_preference(clock->type); + control &= HDSP_CONTROL_CLOCK_MASK; + snd_mtxlock(sc->lock); + sc->ctrl_register &= ~HDSP_CONTROL_CLOCK_MASK; + sc->ctrl_register |= control; + hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); + snd_mtxunlock(sc->lock); + } + return (0); +} + +static uint32_t +hdsp_status2_clock_source(enum hdsp_clock_type type) +{ + switch (type) { + case HDSP_CLOCK_INTERNAL: + return (0); + case HDSP_CLOCK_ADAT1: + return (HDSP_STATUS2_CLOCK(0)); + case HDSP_CLOCK_ADAT2: + return (HDSP_STATUS2_CLOCK(1)); + case HDSP_CLOCK_ADAT3: + return (HDSP_STATUS2_CLOCK(2)); + case HDSP_CLOCK_SPDIF: + return (HDSP_STATUS2_CLOCK(3)); + case HDSP_CLOCK_WORD: + return (HDSP_STATUS2_CLOCK(4)); + case HDSP_CLOCK_ADAT_SYNC: + return (HDSP_STATUS2_CLOCK(5)); + default: + return (0); + } +} + +static int +hdsp_sysctl_clock_source(SYSCTL_HANDLER_ARGS) +{ + struct sc_info *sc; + struct hdsp_clock_source *clock_table, *clock; + char buf[16] = "invalid"; + uint32_t status2; + + sc = oidp->oid_arg1; + + /* Select sync ports table for device type. */ + if (sc->type == HDSP_9632) + clock_table = hdsp_clock_source_table_9632; + else if (sc->type == HDSP_9652) + clock_table = hdsp_clock_source_table_9652; + else + return (ENXIO); + + /* Read current (autosync) clock source from status2 register. */ + snd_mtxlock(sc->lock); + status2 = hdsp_read_4(sc, HDSP_STATUS2_REG); + status2 &= HDSP_STATUS2_CLOCK_MASK; + snd_mtxunlock(sc->lock); + + /* Translate status2 register value to clock source. */ + for (clock = clock_table; clock->name != NULL; ++clock) { + /* In clock master mode, override with internal clock source. */ + if (sc->ctrl_register & HDSP_CONTROL_MASTER) { + if (clock->type == HDSP_CLOCK_INTERNAL) + break; + } else if (hdsp_status2_clock_source(clock->type) == status2) + 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 +hdsp_sysctl_clock_list(SYSCTL_HANDLER_ARGS) +{ + struct sc_info *sc; + struct hdsp_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 == HDSP_9632) + clock_table = hdsp_clock_source_table_9632; + else if (sc->type == HDSP_9652) + clock_table = hdsp_clock_source_table_9652; + 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 bool +hdsp_clock_source_locked(enum hdsp_clock_type type, uint32_t status, + uint32_t status2) +{ + switch (type) { + case HDSP_CLOCK_INTERNAL: + return (true); + case HDSP_CLOCK_ADAT1: + return ((status >> 3) & 0x01); + case HDSP_CLOCK_ADAT2: + return ((status >> 2) & 0x01); + case HDSP_CLOCK_ADAT3: + return ((status >> 1) & 0x01); + case HDSP_CLOCK_SPDIF: + return (!((status >> 25) & 0x01)); + case HDSP_CLOCK_WORD: + return ((status2 >> 3) & 0x01); + case HDSP_CLOCK_ADAT_SYNC: + return ((status >> 5) & 0x01); + default: + return (false); + } +} + +static bool +hdsp_clock_source_synced(enum hdsp_clock_type type, uint32_t status, + uint32_t status2) +{ + switch (type) { + case HDSP_CLOCK_INTERNAL: + return (true); + case HDSP_CLOCK_ADAT1: + return ((status >> 18) & 0x01); + case HDSP_CLOCK_ADAT2: + return ((status >> 17) & 0x01); + case HDSP_CLOCK_ADAT3: + return ((status >> 16) & 0x01); + case HDSP_CLOCK_SPDIF: + return (((status >> 4) & 0x01) && !((status >> 25) & 0x01)); + case HDSP_CLOCK_WORD: + return ((status2 >> 4) & 0x01); + case HDSP_CLOCK_ADAT_SYNC: + return ((status >> 27) & 0x01); + default: + return (false); + } +} + +static int +hdsp_sysctl_sync_status(SYSCTL_HANDLER_ARGS) +{ + struct sc_info *sc; + struct hdsp_clock_source *clock_table, *clock; + char buf[256]; + char *state; + int n; + uint32_t status, status2; + + sc = oidp->oid_arg1; + n = 0; + + /* Select sync ports table for device type. */ + if (sc->type == HDSP_9632) + clock_table = hdsp_clock_source_table_9632; + else if (sc->type == HDSP_9652) + clock_table = hdsp_clock_source_table_9652; + else + return (ENXIO); + + /* Read current lock and sync bits from status registers. */ + snd_mtxlock(sc->lock); + status = hdsp_read_4(sc, HDSP_STATUS_REG); + status2 = hdsp_read_4(sc, HDSP_STATUS2_REG); + snd_mtxunlock(sc->lock); + + /* List clock sources with lock and sync state. */ + for (clock = clock_table; clock->name != NULL; ++clock) { + if (clock->type == HDSP_CLOCK_INTERNAL) + continue; + if (n > 0) + n += strlcpy(buf + n, ",", sizeof(buf) - n); + state = "none"; + if (hdsp_clock_source_locked(clock->type, status, status2)) { + if (hdsp_clock_source_synced(clock->type, status, + status2)) + state = "sync"; + else + 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 +hdsp_probe(device_t dev) +{ + uint32_t rev; + + if (pci_get_vendor(dev) == PCI_VENDOR_XILINX && + pci_get_device(dev) == PCI_DEVICE_XILINX_HDSP) { + rev = pci_get_revid(dev); + switch (rev) { + case PCI_REVISION_9632: + device_set_desc(dev, "RME HDSP 9632"); + return (0); + case PCI_REVISION_9652: + device_set_desc(dev, "RME HDSP 9652"); + return (0); + } + } + + return (ENXIO); +} + +static int +hdsp_init(struct sc_info *sc) +{ + unsigned mixer_controls; + + /* Set latency. */ + sc->period = 256; + /* + * 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 = hdsp_encode_latency(2); + + /* Set rate. */ + sc->speed = HDSP_SPEED_DEFAULT; + sc->force_speed = 0; + sc->ctrl_register &= ~HDSP_FREQ_MASK; + sc->ctrl_register |= HDSP_FREQ_MASK_DEFAULT; + + /* Set internal clock source (master). */ + sc->ctrl_register &= ~HDSP_CONTROL_CLOCK_MASK; + sc->ctrl_register |= HDSP_CONTROL_MASTER; + + /* SPDIF from coax in, line out. */ + sc->ctrl_register &= ~HDSP_CONTROL_SPDIF_COAX; + sc->ctrl_register |= HDSP_CONTROL_SPDIF_COAX; + sc->ctrl_register &= ~HDSP_CONTROL_LINE_OUT; + sc->ctrl_register |= HDSP_CONTROL_LINE_OUT; + + hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); + + if (sc->type == HDSP_9652) + hdsp_write_4(sc, HDSP_CONTROL2_REG, HDSP_CONTROL2_9652_MIXER); + else + hdsp_write_4(sc, HDSP_CONTROL2_REG, 0); + + switch (sc->type) { + case HDSP_9632: + /* Mixer matrix is 2 source rows (input, playback) per output. */ + mixer_controls = 2 * HDSP_MIX_SLOTS_9632 * HDSP_MIX_SLOTS_9632; + break; + case HDSP_9652: + /* Mixer matrix is 2 source rows (input, playback) per output. */ + mixer_controls = 2 * HDSP_MIX_SLOTS_9652 * HDSP_MIX_SLOTS_9652; + break; + default: + return (ENXIO); + } + + /* Initialize mixer matrix by silencing all controls. */ + for (unsigned offset = 0; offset < mixer_controls * 2; offset += 4) { + /* Only accepts 4 byte values, pairs of 16 bit volume controls. */ + hdsp_write_4(sc, HDSP_MIXER_BASE + offset, + (HDSP_MIN_GAIN << 16) | HDSP_MIN_GAIN); + } + + /* Reset pointer, rewrite frequency (same register) for 9632. */ + hdsp_write_4(sc, HDSP_RESET_POINTER, 0); + if (sc->type == HDSP_9632) { + /* Set DDS value. */ + hdsp_write_4(sc, HDSP_FREQ_REG, hdsp_freq_reg_value(sc->speed)); + } + + return (0); +} + +static int +hdsp_attach(device_t dev) +{ + struct hdsp_channel *chan_map; + struct sc_pcminfo *scp; + struct sc_info *sc; + uint32_t rev; + int i, err; + +#if 0 + device_printf(dev, "hdsp_attach()\n"); +#endif + + sc = device_get_softc(dev); + sc->lock = snd_mtxcreate(device_get_nameunit(dev), + "snd_hdsp softc"); + sc->dev = dev; + + pci_enable_busmaster(dev); + rev = pci_get_revid(dev); + switch (rev) { + case PCI_REVISION_9632: + sc->type = HDSP_9632; + chan_map = hdsp_unified_pcm ? chan_map_9632_uni : chan_map_9632; + break; + case PCI_REVISION_9652: + sc->type = HDSP_9652; + chan_map = hdsp_unified_pcm ? chan_map_9652_uni : chan_map_9652; + break; + default: + return (ENXIO); + } + + /* Allocate resources. */ + err = hdsp_alloc_resources(sc); + if (err) { + device_printf(dev, "Unable to allocate system resources.\n"); + return (ENXIO); + } + + if (hdsp_init(sc) != 0) + return (ENXIO); + + for (i = 0; i < HDSP_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); + } + + hdsp_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, hdsp_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, hdsp_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, hdsp_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, hdsp_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, hdsp_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, hdsp_sysctl_sample_rate, "A", + "Force sample rate (32000, 44100, 48000, ... 192000)"); + + return (bus_generic_attach(dev)); +} + +static void +hdsp_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 +hdsp_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); + + hdsp_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 hdsp_methods[] = { + DEVMETHOD(device_probe, hdsp_probe), + DEVMETHOD(device_attach, hdsp_attach), + DEVMETHOD(device_detach, hdsp_detach), + { 0, 0 } +}; + +static driver_t hdsp_driver = { + "hdsp", + hdsp_methods, + PCM_SOFTC_SIZE, +}; + +DRIVER_MODULE(snd_hdsp, pci, hdsp_driver, 0, 0); diff --git a/sys/modules/sound/driver/Makefile b/sys/modules/sound/driver/Makefile --- a/sys/modules/sound/driver/Makefile +++ b/sys/modules/sound/driver/Makefile @@ -6,7 +6,7 @@ # MK_SOURCELESS_UCODE option (see below). SUBDIR= als4000 atiixp cs4281 ${_csa} emu10k1 emu10kx -SUBDIR+= envy24 envy24ht es137x fm801 hda hdspe ich +SUBDIR+= envy24 envy24ht es137x fm801 hda hdsp hdspe ich SUBDIR+= ${_maestro3} neomagic solo spicds t4dwave via8233 SUBDIR+= via82c686 vibes driver uaudio diff --git a/sys/modules/sound/driver/hdsp/Makefile b/sys/modules/sound/driver/hdsp/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/sound/driver/hdsp/Makefile @@ -0,0 +1,7 @@ +.PATH: ${SRCTOP}/sys/dev/sound/pci + +KMOD= snd_hdsp +SRCS= device_if.h bus_if.h pci_if.h +SRCS+= hdsp.c hdsp-pcm.c hdsp.h + +.include