diff --git a/share/man/man4/snd_hdspe.4 b/share/man/man4/snd_hdspe.4 --- a/share/man/man4/snd_hdspe.4 +++ b/share/man/man4/snd_hdspe.4 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd February 13, 2012 +.Dd December 30, 2023 .Dt SND_HDSPE 4 .Os .Sh NAME @@ -59,6 +59,45 @@ .It RME HDSPe RayDAT .El +.Sh SYSCTL TUNABLES +These settings and informational values can be accessed at runtime with the +.Xr sysctl 8 +command. +If multiple RME HDSPe 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.hdspe.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.hdspe.0.clock_preference +Select a preferred clock source from the clock list. +HDSPe 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 HDSPe card should act as master clock. +.It Va dev.hdspe.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.hdspe.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 diff --git a/sys/dev/sound/pci/hdspe-pcm.c b/sys/dev/sound/pci/hdspe-pcm.c --- a/sys/dev/sound/pci/hdspe-pcm.c +++ b/sys/dev/sound/pci/hdspe-pcm.c @@ -519,8 +519,8 @@ } switch (sc->type) { - case RAYDAT: - case AIO: + case HDSPE_RAYDAT: + case HDSPE_AIO: period = HDSPE_FREQ_AIO; break; default: diff --git a/sys/dev/sound/pci/hdspe.h b/sys/dev/sound/pci/hdspe.h --- a/sys/dev/sound/pci/hdspe.h +++ b/sys/dev/sound/pci/hdspe.h @@ -32,8 +32,8 @@ #define PCI_REVISION_AIO 212 #define PCI_REVISION_RAYDAT 211 -#define AIO 0 -#define RAYDAT 1 +#define HDSPE_AIO 0 +#define HDSPE_RAYDAT 1 /* Hardware mixer */ #define HDSPE_OUT_ENABLE_BASE 512 @@ -95,15 +95,13 @@ #define HDSP_PhoneGainMinus6dB (HDSP_PhoneGain0) #define HDSP_PhoneGainMinus12dB 0 -#define HDSPM_statusRegister 0 -#define HDSPM_statusRegister2 192 - /* Settings */ #define HDSPE_SETTINGS_REG 0 #define HDSPE_CONTROL_REG 64 #define HDSPE_STATUS_REG 0 +#define HDSPE_STATUS1_REG 64 +#define HDSPE_STATUS2_REG 192 #define HDSPE_ENABLE (1 << 0) -#define HDSPM_CLOCK_MODE_MASTER (1 << 4) /* Interrupts */ #define HDSPE_AUDIO_IRQ_PENDING (1 << 0) @@ -126,6 +124,23 @@ uint32_t rec; }; +/* Clock sources */ +#define HDSPE_SETTING_MASTER (1 << 0) +#define HDSPE_SETTING_CLOCK_MASK 0x1f + +#define HDSPE_STATUS1_CLOCK_SHIFT 28 +#define HDSPE_STATUS1_CLOCK_MASK (0x0f << HDSPE_STATUS1_CLOCK_SHIFT) +#define HDSPE_STATUS1_CLOCK(n) (((n) << HDSPE_STATUS1_CLOCK_SHIFT) & \ + HDSPE_STATUS1_CLOCK_MASK) + +struct hdspe_clock_source { + char *name; + uint32_t setting; + uint32_t status; + uint32_t lock_bit; + uint32_t sync_bit; +}; + static MALLOC_DEFINE(M_HDSPE, "hdspe", "hdspe audio"); /* Channel registers */ diff --git a/sys/dev/sound/pci/hdspe.c b/sys/dev/sound/pci/hdspe.c --- a/sys/dev/sound/pci/hdspe.c +++ b/sys/dev/sound/pci/hdspe.c @@ -31,6 +31,9 @@ * Supported cards: AIO, RayDAT. */ +#include +#include + #include #include #include @@ -40,6 +43,31 @@ #include +static struct hdspe_clock_source hdspe_clock_source_table_rd[] = { + { "internal", 0 << 1 | 1, HDSPE_STATUS1_CLOCK(15), 0, 0 }, + { "word", 0 << 1 | 0, HDSPE_STATUS1_CLOCK( 0), 1 << 24, 1 << 25 }, + { "aes", 1 << 1 | 0, HDSPE_STATUS1_CLOCK( 1), 1 << 0, 1 << 8 }, + { "spdif", 2 << 1 | 0, HDSPE_STATUS1_CLOCK( 2), 1 << 1, 1 << 9 }, + { "adat1", 3 << 1 | 0, HDSPE_STATUS1_CLOCK( 3), 1 << 2, 1 << 10 }, + { "adat2", 4 << 1 | 0, HDSPE_STATUS1_CLOCK( 4), 1 << 3, 1 << 11 }, + { "adat3", 5 << 1 | 0, HDSPE_STATUS1_CLOCK( 5), 1 << 4, 1 << 12 }, + { "adat4", 6 << 1 | 0, HDSPE_STATUS1_CLOCK( 6), 1 << 5, 1 << 13 }, + { "tco", 9 << 1 | 0, HDSPE_STATUS1_CLOCK( 9), 1 << 26, 1 << 27 }, + { "sync_in", 10 << 1 | 0, HDSPE_STATUS1_CLOCK(10), 0, 0 }, + { NULL, 0 << 1 | 0, HDSPE_STATUS1_CLOCK( 0), 0, 0 }, +}; + +static struct hdspe_clock_source hdspe_clock_source_table_aio[] = { + { "internal", 0 << 1 | 1, HDSPE_STATUS1_CLOCK(15), 0, 0 }, + { "word", 0 << 1 | 0, HDSPE_STATUS1_CLOCK( 0), 1 << 24, 1 << 25 }, + { "aes", 1 << 1 | 0, HDSPE_STATUS1_CLOCK( 1), 1 << 0, 1 << 8 }, + { "spdif", 2 << 1 | 0, HDSPE_STATUS1_CLOCK( 2), 1 << 1, 1 << 9 }, + { "adat", 3 << 1 | 0, HDSPE_STATUS1_CLOCK( 3), 1 << 2, 1 << 10 }, + { "tco", 9 << 1 | 0, HDSPE_STATUS1_CLOCK( 9), 1 << 26, 1 << 27 }, + { "sync_in", 10 << 1 | 0, HDSPE_STATUS1_CLOCK(10), 0, 0 }, + { NULL, 0 << 1 | 0, HDSPE_STATUS1_CLOCK( 0), 0, 0 }, +}; + static struct hdspe_channel chan_map_aio[] = { { 0, 1, "line", 1, 1 }, { 6, 7, "phone", 1, 0 }, @@ -224,6 +252,169 @@ } } +static int +hdspe_sysctl_clock_preference(SYSCTL_HANDLER_ARGS) +{ + struct sc_info *sc; + struct hdspe_clock_source *clock_table, *clock; + char buf[16] = "invalid"; + int error; + uint32_t setting; + + sc = oidp->oid_arg1; + + /* Select sync ports table for device type. */ + if (sc->type == HDSPE_AIO) + clock_table = hdspe_clock_source_table_aio; + else if (sc->type == HDSPE_RAYDAT) + clock_table = hdspe_clock_source_table_rd; + else + return (ENXIO); + + /* Extract preferred clock source from settings register. */ + setting = sc->settings_register & HDSPE_SETTING_CLOCK_MASK; + for (clock = clock_table; clock->name != NULL; ++clock) { + if (clock->setting == setting) + break; + } + if (clock->name != NULL) + strlcpy(buf, clock->name, sizeof(buf)); + + /* Process sysctl string request. */ + error = sysctl_handle_string(oidp, buf, sizeof(buf), req); + if (error != 0 || req->newptr == NULL) + return (error); + + /* Find clock source matching the sysctl string. */ + for (clock = clock_table; clock->name != NULL; ++clock) { + if (strncasecmp(buf, clock->name, sizeof(buf)) == 0) + break; + } + + /* Set preferred clock source in settings register. */ + if (clock->name != NULL) { + setting = clock->setting & HDSPE_SETTING_CLOCK_MASK; + snd_mtxlock(sc->lock); + sc->settings_register &= ~HDSPE_SETTING_CLOCK_MASK; + sc->settings_register |= setting; + hdspe_write_4(sc, HDSPE_SETTINGS_REG, sc->settings_register); + snd_mtxunlock(sc->lock); + } + return (0); +} + +static int +hdspe_sysctl_clock_source(SYSCTL_HANDLER_ARGS) +{ + struct sc_info *sc; + struct hdspe_clock_source *clock_table, *clock; + char buf[16] = "invalid"; + uint32_t status; + + sc = oidp->oid_arg1; + + /* Select sync ports table for device type. */ + if (sc->type == HDSPE_AIO) + clock_table = hdspe_clock_source_table_aio; + else if (sc->type == HDSPE_RAYDAT) + clock_table = hdspe_clock_source_table_rd; + else + return (ENXIO); + + /* Read current (autosync) clock source from status register. */ + snd_mtxlock(sc->lock); + status = hdspe_read_4(sc, HDSPE_STATUS1_REG); + status &= HDSPE_STATUS1_CLOCK_MASK; + snd_mtxunlock(sc->lock); + + /* Translate status register value to clock source. */ + for (clock = clock_table; clock->name != NULL; ++clock) { + /* In clock master mode, override with internal clock source. */ + if (sc->settings_register & HDSPE_SETTING_MASTER) { + if (clock->setting & HDSPE_SETTING_MASTER) + break; + } else if (clock->status == status) + break; + } + + /* Process sysctl string request. */ + if (clock->name != NULL) + strlcpy(buf, clock->name, sizeof(buf)); + return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); +} + +static int +hdspe_sysctl_clock_list(SYSCTL_HANDLER_ARGS) +{ + struct sc_info *sc; + struct hdspe_clock_source *clock_table, *clock; + char buf[256]; + int n; + + sc = oidp->oid_arg1; + n = 0; + + /* Select clock source table for device type. */ + if (sc->type == HDSPE_AIO) + clock_table = hdspe_clock_source_table_aio; + else if (sc->type == HDSPE_RAYDAT) + clock_table = hdspe_clock_source_table_rd; + else + return (ENXIO); + + /* List available clock sources. */ + buf[0] = 0; + for (clock = clock_table; clock->name != NULL; ++clock) { + if (n > 0) + n += strlcpy(buf + n, ",", sizeof(buf) - n); + n += strlcpy(buf + n, clock->name, sizeof(buf) - n); + } + return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); +} + +static int +hdspe_sysctl_sync_status(SYSCTL_HANDLER_ARGS) +{ + struct sc_info *sc; + struct hdspe_clock_source *clock_table, *clock; + char buf[256]; + char *state; + int n; + uint32_t status; + + sc = oidp->oid_arg1; + n = 0; + + /* Select sync ports table for device type. */ + if (sc->type == HDSPE_AIO) + clock_table = hdspe_clock_source_table_aio; + else if (sc->type == HDSPE_RAYDAT) + clock_table = hdspe_clock_source_table_rd; + else + return (ENXIO); + + /* Read current lock and sync bits from status register. */ + snd_mtxlock(sc->lock); + status = hdspe_read_4(sc, HDSPE_STATUS1_REG); + snd_mtxunlock(sc->lock); + + /* List clock sources with lock and sync state. */ + for (clock = clock_table; clock->name != NULL; ++clock) { + if (clock->sync_bit != 0) { + if (n > 0) + n += strlcpy(buf + n, ",", sizeof(buf) - n); + state = "none"; + if ((clock->sync_bit & status) != 0) + state = "sync"; + else if ((clock->lock_bit & status) != 0) + state = "lock"; + n += snprintf(buf + n, sizeof(buf) - n, "%s(%s)", + clock->name, state); + } + } + return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); +} + static int hdspe_probe(device_t dev) { @@ -250,9 +441,6 @@ { long long period; - /* Set defaults. */ - sc->ctrl_register |= HDSPM_CLOCK_MODE_MASTER; - /* Set latency. */ sc->period = 32; sc->ctrl_register = hdspe_encode_latency(7); @@ -264,8 +452,8 @@ hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register); switch (sc->type) { - case RAYDAT: - case AIO: + case HDSPE_RAYDAT: + case HDSPE_AIO: period = HDSPE_FREQ_AIO; break; default: @@ -305,11 +493,11 @@ rev = pci_get_revid(dev); switch (rev) { case PCI_REVISION_AIO: - sc->type = AIO; + sc->type = HDSPE_AIO; chan_map = chan_map_aio; break; case PCI_REVISION_RAYDAT: - sc->type = RAYDAT; + sc->type = HDSPE_RAYDAT; chan_map = chan_map_rd; break; default: @@ -336,6 +524,30 @@ hdspe_map_dmabuf(sc); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "sync_status", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, + sc, 0, hdspe_sysctl_sync_status, "A", + "List clock source signal lock and sync status"); + + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "clock_source", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, + sc, 0, hdspe_sysctl_clock_source, "A", + "Currently effective clock source"); + + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "clock_preference", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, + sc, 0, hdspe_sysctl_clock_preference, "A", + "Set 'internal' (master) or preferred autosync clock source"); + + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "clock_list", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, + sc, 0, hdspe_sysctl_clock_list, "A", + "List of supported clock sources"); + return (bus_generic_attach(dev)); }