aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--share/man/man4/Makefile1
-rw-r--r--share/man/man4/pcm.43
-rw-r--r--share/man/man4/snd_hdsp.4154
-rw-r--r--sys/conf/NOTES2
-rw-r--r--sys/conf/files2
-rw-r--r--sys/dev/sound/driver.c1
-rw-r--r--sys/dev/sound/pci/hdsp-pcm.c1134
-rw-r--r--sys/dev/sound/pci/hdsp.c793
-rw-r--r--sys/dev/sound/pci/hdsp.h257
-rw-r--r--sys/modules/sound/driver/Makefile2
-rw-r--r--sys/modules/sound/driver/hdsp/Makefile7
11 files changed, 2355 insertions, 1 deletions
diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index 595da069f73c..c5ba7e46deb8 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -519,6 +519,7 @@ MAN= aac.4 \
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
index e406bd2c8343..5fcaca7220b9 100644
--- a/share/man/man4/pcm.4
+++ b/share/man/man4/pcm.4
@@ -101,6 +101,8 @@ The following bridge device drivers are available:
.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 @@ A device node is not created properly.
.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
index 000000000000..23eb98a3ccc2
--- /dev/null
+++ b/share/man/man4/snd_hdsp.4
@@ -0,0 +1,154 @@
+.\" Copyright (c) 2012 Ruslan Bukin <br@bsdpad.com>
+.\" Copyright (c) 2024 Florian Walpen <dev@submerge.ch>
+.\" 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 <br@bsdpad.com> .
+All adaptation to HDSP cards by
+.An Florian Walpen <dev@submerge.ch> .
diff --git a/sys/conf/NOTES b/sys/conf/NOTES
index fec65452ba0f..bcaac1c85899 100644
--- a/sys/conf/NOTES
+++ b/sys/conf/NOTES
@@ -2093,6 +2093,7 @@ device sound
# 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_envy24ht
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
index 4858b5868994..7fd79bb0345d 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -3115,6 +3115,8 @@ dev/sound/pci/hda/hdaa_patches.c optional snd_hda pci
dev/sound/pci/hda/hdac.c optional snd_hda pci
dev/sound/pci/hda/hdac_if.m optional snd_hda pci
dev/sound/pci/hda/hdacc.c optional snd_hda pci
+dev/sound/pci/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
index 927941ab3d01..6bfe6c51fa3e 100644
--- a/sys/dev/sound/driver.c
+++ b/sys/dev/sound/driver.c
@@ -67,6 +67,7 @@ MODULE_DEPEND(snd_driver, snd_envy24ht, 1, 1, 1);
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
index 000000000000..9ba0e5e345d0
--- /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 <br@bsdpad.com>
+ * Copyright (c) 2023-2024 Florian Walpen <dev@submerge.ch>
+ * 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 <sys/libkern.h>
+
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/pci/hdsp.h>
+
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+
+#include <mixer_if.h>
+
+#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.c b/sys/dev/sound/pci/hdsp.c
new file mode 100644
index 000000000000..769615cb1f4b
--- /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 <br@bsdpad.com>
+ * Copyright (c) 2023-2024 Florian Walpen <dev@submerge.ch>
+ * 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 <sys/types.h>
+#include <sys/sysctl.h>
+
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/pci/hdsp.h>
+
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+
+#include <mixer_if.h>
+
+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/dev/sound/pci/hdsp.h b/sys/dev/sound/pci/hdsp.h
new file mode 100644
index 000000000000..5ff622892c52
--- /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 <br@bsdpad.com>
+ * Copyright (c) 2023-2024 Florian Walpen <dev@submerge.ch>
+ * 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/modules/sound/driver/Makefile b/sys/modules/sound/driver/Makefile
index af5e3259aa00..2391d1c5a8a4 100644
--- a/sys/modules/sound/driver/Makefile
+++ b/sys/modules/sound/driver/Makefile
@@ -6,7 +6,7 @@ SYSDIR?=${SRCTOP}/sys
# 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
index 000000000000..d91341fafde8
--- /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 <bsd.kmod.mk>