diff --git a/share/man/man4/snd_hdsp.4 b/share/man/man4/snd_hdsp.4 --- a/share/man/man4/snd_hdsp.4 +++ b/share/man/man4/snd_hdsp.4 @@ -23,7 +23,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd September 28, 2024 +.Dd October 28, 2024 .Dt SND_HDSP 4 .Os .Sh NAME @@ -134,6 +134,28 @@ audio). .El .Pp +The following tunables are applicable to HDSP 9632 devices only: +.Bl -tag -width indent +.It Va dev.hdsp.0.input_level +Select the sensitivity of the analog line input. +Available reference levels for the input signal are +.Ql LowGain , +.Ql +4dBu +and +.Ql -10dBV . +.It Va dev.hdsp.0.output_level +Select the gain level of the analog line output. +Available reference levels for the output signal are +.Ql HighGain , +.Ql +4dBu +and +.Ql -10dBV . +.It Va dev.hdsp.0.phones_level +Adjust the gain level of the phones output, separately from the analog line +output. +The signal can be lowered by 6 or 12 dB. +.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. diff --git a/sys/dev/sound/pci/hdsp.h b/sys/dev/sound/pci/hdsp.h --- a/sys/dev/sound/pci/hdsp.h +++ b/sys/dev/sound/pci/hdsp.h @@ -80,39 +80,47 @@ #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 */ +/* Register addresses */ #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 +/* Control register flags */ #define HDSP_ENABLE (1 << 0) #define HDSP_CONTROL_SPDIF_COAX (1 << 14) #define HDSP_CONTROL_LINE_OUT (1 << 24) +#define HDSP_CONTROL_INPUT_GAIN0 (1 << 25) +#define HDSP_CONTROL_INPUT_GAIN1 (1 << 26) +#define HDSP_CONTROL_OUTPUT_GAIN0 (1 << 27) +#define HDSP_CONTROL_OUTPUT_GAIN1 (1 << 28) +#define HDSP_CONTROL_PHONES_GAIN0 (1 << 29) +#define HDSP_CONTROL_PHONES_GAIN1 (1 << 30) + +/* Analog input gain level */ +#define HDSP_INPUT_LEVEL_MASK (HDSP_CONTROL_INPUT_GAIN0 | \ + HDSP_CONTROL_INPUT_GAIN1) +#define HDSP_INPUT_LEVEL_LOWGAIN 0 +#define HDSP_INPUT_LEVEL_PLUS4DBU (HDSP_CONTROL_INPUT_GAIN0) +#define HDSP_INPUT_LEVEL_MINUS10DBV (HDSP_CONTROL_INPUT_GAIN0 | \ + HDSP_CONTROL_INPUT_GAIN1) + +/* Analog output gain level */ +#define HDSP_OUTPUT_LEVEL_MASK (HDSP_CONTROL_OUTPUT_GAIN0 | \ + HDSP_CONTROL_OUTPUT_GAIN1) +#define HDSP_OUTPUT_LEVEL_MINUS10DBV 0 +#define HDSP_OUTPUT_LEVEL_PLUS4DBU (HDSP_CONTROL_OUTPUT_GAIN0) +#define HDSP_OUTPUT_LEVEL_HIGHGAIN (HDSP_CONTROL_OUTPUT_GAIN0 | \ + HDSP_CONTROL_OUTPUT_GAIN1) + +/* Phones output gain level */ +#define HDSP_PHONES_LEVEL_MASK (HDSP_CONTROL_PHONES_GAIN0 | \ + HDSP_CONTROL_PHONES_GAIN1) +#define HDSP_PHONES_LEVEL_MINUS12DB 0 +#define HDSP_PHONES_LEVEL_MINUS6DB (HDSP_CONTROL_PHONES_GAIN0) +#define HDSP_PHONES_LEVEL_0DB (HDSP_CONTROL_PHONES_GAIN0 | \ + HDSP_CONTROL_PHONES_GAIN1) /* Interrupts */ #define HDSP_AUDIO_IRQ_PENDING (1 << 0) diff --git a/sys/dev/sound/pci/hdsp.c b/sys/dev/sound/pci/hdsp.c --- a/sys/dev/sound/pci/hdsp.c +++ b/sys/dev/sound/pci/hdsp.c @@ -231,6 +231,198 @@ hdsp_write_4(sc, HDSP_PAGE_ADDR_BUF_IN, raddr); } +static const char * +hdsp_control_input_level(uint32_t control) +{ + switch (control & HDSP_INPUT_LEVEL_MASK) { + case HDSP_INPUT_LEVEL_LOWGAIN: + return ("LowGain"); + case HDSP_INPUT_LEVEL_PLUS4DBU: + return ("+4dBu"); + case HDSP_INPUT_LEVEL_MINUS10DBV: + return ("-10dBV"); + default: + return (NULL); + } +} + +static int +hdsp_sysctl_input_level(SYSCTL_HANDLER_ARGS) +{ + struct sc_info *sc; + const char *label; + char buf[16] = "invalid"; + int error; + uint32_t control; + + sc = oidp->oid_arg1; + + /* Only available on HDSP 9632. */ + if (sc->type != HDSP_9632) + return (ENXIO); + + /* Extract current input level from control register. */ + control = sc->ctrl_register & HDSP_INPUT_LEVEL_MASK; + label = hdsp_control_input_level(control); + if (label != NULL) + strlcpy(buf, label, sizeof(buf)); + + /* Process sysctl string request. */ + error = sysctl_handle_string(oidp, buf, sizeof(buf), req); + if (error != 0 || req->newptr == NULL) + return (error); + + /* Find input level matching the sysctl string. */ + label = hdsp_control_input_level(HDSP_INPUT_LEVEL_LOWGAIN); + if (strncasecmp(buf, label, sizeof(buf)) == 0) + control = HDSP_INPUT_LEVEL_LOWGAIN; + label = hdsp_control_input_level(HDSP_INPUT_LEVEL_PLUS4DBU); + if (strncasecmp(buf, label, sizeof(buf)) == 0) + control = HDSP_INPUT_LEVEL_PLUS4DBU; + label = hdsp_control_input_level(HDSP_INPUT_LEVEL_MINUS10DBV); + if (strncasecmp(buf, label, sizeof(buf)) == 0) + control = HDSP_INPUT_LEVEL_MINUS10DBV; + + /* Set input level in control register. */ + control &= HDSP_INPUT_LEVEL_MASK; + if (control != (sc->ctrl_register & HDSP_INPUT_LEVEL_MASK)) { + snd_mtxlock(sc->lock); + sc->ctrl_register &= ~HDSP_INPUT_LEVEL_MASK; + sc->ctrl_register |= control; + hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); + snd_mtxunlock(sc->lock); + } + return (0); +} + +static const char * +hdsp_control_output_level(uint32_t control) +{ + switch (control & HDSP_OUTPUT_LEVEL_MASK) { + case HDSP_OUTPUT_LEVEL_MINUS10DBV: + return ("-10dBV"); + case HDSP_OUTPUT_LEVEL_PLUS4DBU: + return ("+4dBu"); + case HDSP_OUTPUT_LEVEL_HIGHGAIN: + return ("HighGain"); + default: + return (NULL); + } +} + +static int +hdsp_sysctl_output_level(SYSCTL_HANDLER_ARGS) +{ + struct sc_info *sc; + const char *label; + char buf[16] = "invalid"; + int error; + uint32_t control; + + sc = oidp->oid_arg1; + + /* Only available on HDSP 9632. */ + if (sc->type != HDSP_9632) + return (ENXIO); + + /* Extract current output level from control register. */ + control = sc->ctrl_register & HDSP_OUTPUT_LEVEL_MASK; + label = hdsp_control_output_level(control); + if (label != NULL) + strlcpy(buf, label, sizeof(buf)); + + /* Process sysctl string request. */ + error = sysctl_handle_string(oidp, buf, sizeof(buf), req); + if (error != 0 || req->newptr == NULL) + return (error); + + /* Find output level matching the sysctl string. */ + label = hdsp_control_output_level(HDSP_OUTPUT_LEVEL_MINUS10DBV); + if (strncasecmp(buf, label, sizeof(buf)) == 0) + control = HDSP_OUTPUT_LEVEL_MINUS10DBV; + label = hdsp_control_output_level(HDSP_OUTPUT_LEVEL_PLUS4DBU); + if (strncasecmp(buf, label, sizeof(buf)) == 0) + control = HDSP_OUTPUT_LEVEL_PLUS4DBU; + label = hdsp_control_output_level(HDSP_OUTPUT_LEVEL_HIGHGAIN); + if (strncasecmp(buf, label, sizeof(buf)) == 0) + control = HDSP_OUTPUT_LEVEL_HIGHGAIN; + + /* Set output level in control register. */ + control &= HDSP_OUTPUT_LEVEL_MASK; + if (control != (sc->ctrl_register & HDSP_OUTPUT_LEVEL_MASK)) { + snd_mtxlock(sc->lock); + sc->ctrl_register &= ~HDSP_OUTPUT_LEVEL_MASK; + sc->ctrl_register |= control; + hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); + snd_mtxunlock(sc->lock); + } + return (0); +} + +static const char * +hdsp_control_phones_level(uint32_t control) +{ + switch (control & HDSP_PHONES_LEVEL_MASK) { + case HDSP_PHONES_LEVEL_MINUS12DB: + return ("-12dB"); + case HDSP_PHONES_LEVEL_MINUS6DB: + return ("-6dB"); + case HDSP_PHONES_LEVEL_0DB: + return ("0dB"); + default: + return (NULL); + } +} + +static int +hdsp_sysctl_phones_level(SYSCTL_HANDLER_ARGS) +{ + struct sc_info *sc; + const char *label; + char buf[16] = "invalid"; + int error; + uint32_t control; + + sc = oidp->oid_arg1; + + /* Only available on HDSP 9632. */ + if (sc->type != HDSP_9632) + return (ENXIO); + + /* Extract current phones level from control register. */ + control = sc->ctrl_register & HDSP_PHONES_LEVEL_MASK; + label = hdsp_control_phones_level(control); + if (label != NULL) + strlcpy(buf, label, sizeof(buf)); + + /* Process sysctl string request. */ + error = sysctl_handle_string(oidp, buf, sizeof(buf), req); + if (error != 0 || req->newptr == NULL) + return (error); + + /* Find phones level matching the sysctl string. */ + label = hdsp_control_phones_level(HDSP_PHONES_LEVEL_MINUS12DB); + if (strncasecmp(buf, label, sizeof(buf)) == 0) + control = HDSP_PHONES_LEVEL_MINUS12DB; + label = hdsp_control_phones_level(HDSP_PHONES_LEVEL_MINUS6DB); + if (strncasecmp(buf, label, sizeof(buf)) == 0) + control = HDSP_PHONES_LEVEL_MINUS6DB; + label = hdsp_control_phones_level(HDSP_PHONES_LEVEL_0DB); + if (strncasecmp(buf, label, sizeof(buf)) == 0) + control = HDSP_PHONES_LEVEL_0DB; + + /* Set phones level in control register. */ + control &= HDSP_PHONES_LEVEL_MASK; + if (control != (sc->ctrl_register & HDSP_PHONES_LEVEL_MASK)) { + snd_mtxlock(sc->lock); + sc->ctrl_register &= ~HDSP_PHONES_LEVEL_MASK; + sc->ctrl_register |= control; + hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); + snd_mtxunlock(sc->lock); + } + return (0); +} + static int hdsp_sysctl_sample_rate(SYSCTL_HANDLER_ARGS) { @@ -606,6 +798,14 @@ sc->ctrl_register &= ~HDSP_CONTROL_LINE_OUT; sc->ctrl_register |= HDSP_CONTROL_LINE_OUT; + /* Default gain levels. */ + sc->ctrl_register &= ~HDSP_INPUT_LEVEL_MASK; + sc->ctrl_register |= HDSP_INPUT_LEVEL_LOWGAIN; + sc->ctrl_register &= ~HDSP_OUTPUT_LEVEL_MASK; + sc->ctrl_register |= HDSP_OUTPUT_LEVEL_MINUS10DBV; + sc->ctrl_register &= ~HDSP_PHONES_LEVEL_MASK; + sc->ctrl_register |= HDSP_PHONES_LEVEL_MINUS12DB; + hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); if (sc->type == HDSP_9652) @@ -732,6 +932,26 @@ sc, 0, hdsp_sysctl_sample_rate, "A", "Force sample rate (32000, 44100, 48000, ... 192000)"); + if (sc->type == HDSP_9632) { + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "phones_level", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, + sc, 0, hdsp_sysctl_phones_level, "A", + "Phones output level ('0dB', '-6dB', '-12dB')"); + + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "output_level", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, + sc, 0, hdsp_sysctl_output_level, "A", + "Analog output level ('HighGain', '+4dBU', '-10dBV')"); + + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "input_level", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, + sc, 0, hdsp_sysctl_input_level, "A", + "Analog input level ('LowGain', '+4dBU', '-10dBV')"); + } + return (bus_generic_attach(dev)); }