snd_hdspe(4): Add sysctls to select analog signal levels.

RME HDSPE AIO sound cards support different sensitivity levels at the
analog input, and different gain levels for the analog line and phones
outputs. Expose these settings as sysctl tunables.

Test Plan:
The following tests on hardware should be performed if possible:
  - Phones output volume changes according to sysctl setting
    (caution, still extremely loud).
  - Create loop to feedback the line output to line input, recording the
    sound played.
  - Iterate through input signal levels and observe volume changes in
    recorded audio.
  - Iterate through output signal levels and observe volume changes in
    recorded audio.

Reviewed by: br, christos
Differential Revision: https://reviews.freebsd.org/D47412
This commit is contained in:
Florian Walpen 2024-12-03 22:27:47 +00:00 committed by Ruslan Bukin
parent e1eff81ea9
commit 49a7f2b313
3 changed files with 281 additions and 25 deletions

View File

@ -22,7 +22,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd September 28, 2024
.Dd November 2, 2024
.Dt SND_HDSPE 4
.Os
.Sh NAME
@ -132,6 +132,32 @@ for accurately synchronized signals (required for recording digital
audio).
.El
.Pp
The following tunables are applicable to HDSPe AIO devices only:
.Bl -tag -width indent
.It Va dev.hdspe.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.hdspe.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.hdspe.0.phones_level
Adjust the gain level of the phones output, separately from the analog line
output.
Available reference levels for the output signal are
.Ql HighGain ,
.Ql +4dBu
and
.Ql -10dBV .
.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.

View File

@ -246,6 +246,198 @@ hdspe_map_dmabuf(struct sc_info *sc)
}
}
static const char *
hdspe_settings_input_level(uint32_t settings)
{
switch (settings & HDSPE_INPUT_LEVEL_MASK) {
case HDSPE_INPUT_LEVEL_LOWGAIN:
return ("LowGain");
case HDSPE_INPUT_LEVEL_PLUS4DBU:
return ("+4dBu");
case HDSPE_INPUT_LEVEL_MINUS10DBV:
return ("-10dBV");
default:
return (NULL);
}
}
static int
hdspe_sysctl_input_level(SYSCTL_HANDLER_ARGS)
{
struct sc_info *sc;
const char *label;
char buf[16] = "invalid";
int error;
uint32_t settings;
sc = oidp->oid_arg1;
/* Only available on HDSPE AIO. */
if (sc->type != HDSPE_AIO)
return (ENXIO);
/* Extract current input level from settings register. */
settings = sc->settings_register & HDSPE_INPUT_LEVEL_MASK;
label = hdspe_settings_input_level(settings);
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 = hdspe_settings_input_level(HDSPE_INPUT_LEVEL_LOWGAIN);
if (strncasecmp(buf, label, sizeof(buf)) == 0)
settings = HDSPE_INPUT_LEVEL_LOWGAIN;
label = hdspe_settings_input_level(HDSPE_INPUT_LEVEL_PLUS4DBU);
if (strncasecmp(buf, label, sizeof(buf)) == 0)
settings = HDSPE_INPUT_LEVEL_PLUS4DBU;
label = hdspe_settings_input_level(HDSPE_INPUT_LEVEL_MINUS10DBV);
if (strncasecmp(buf, label, sizeof(buf)) == 0)
settings = HDSPE_INPUT_LEVEL_MINUS10DBV;
/* Set input level in settings register. */
settings &= HDSPE_INPUT_LEVEL_MASK;
if (settings != (sc->settings_register & HDSPE_INPUT_LEVEL_MASK)) {
snd_mtxlock(sc->lock);
sc->settings_register &= ~HDSPE_INPUT_LEVEL_MASK;
sc->settings_register |= settings;
hdspe_write_4(sc, HDSPE_SETTINGS_REG, sc->settings_register);
snd_mtxunlock(sc->lock);
}
return (0);
}
static const char *
hdspe_settings_output_level(uint32_t settings)
{
switch (settings & HDSPE_OUTPUT_LEVEL_MASK) {
case HDSPE_OUTPUT_LEVEL_HIGHGAIN:
return ("HighGain");
case HDSPE_OUTPUT_LEVEL_PLUS4DBU:
return ("+4dBu");
case HDSPE_OUTPUT_LEVEL_MINUS10DBV:
return ("-10dBV");
default:
return (NULL);
}
}
static int
hdspe_sysctl_output_level(SYSCTL_HANDLER_ARGS)
{
struct sc_info *sc;
const char *label;
char buf[16] = "invalid";
int error;
uint32_t settings;
sc = oidp->oid_arg1;
/* Only available on HDSPE AIO. */
if (sc->type != HDSPE_AIO)
return (ENXIO);
/* Extract current output level from settings register. */
settings = sc->settings_register & HDSPE_OUTPUT_LEVEL_MASK;
label = hdspe_settings_output_level(settings);
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 = hdspe_settings_output_level(HDSPE_OUTPUT_LEVEL_HIGHGAIN);
if (strncasecmp(buf, label, sizeof(buf)) == 0)
settings = HDSPE_OUTPUT_LEVEL_HIGHGAIN;
label = hdspe_settings_output_level(HDSPE_OUTPUT_LEVEL_PLUS4DBU);
if (strncasecmp(buf, label, sizeof(buf)) == 0)
settings = HDSPE_OUTPUT_LEVEL_PLUS4DBU;
label = hdspe_settings_output_level(HDSPE_OUTPUT_LEVEL_MINUS10DBV);
if (strncasecmp(buf, label, sizeof(buf)) == 0)
settings = HDSPE_OUTPUT_LEVEL_MINUS10DBV;
/* Set output level in settings register. */
settings &= HDSPE_OUTPUT_LEVEL_MASK;
if (settings != (sc->settings_register & HDSPE_OUTPUT_LEVEL_MASK)) {
snd_mtxlock(sc->lock);
sc->settings_register &= ~HDSPE_OUTPUT_LEVEL_MASK;
sc->settings_register |= settings;
hdspe_write_4(sc, HDSPE_SETTINGS_REG, sc->settings_register);
snd_mtxunlock(sc->lock);
}
return (0);
}
static const char *
hdspe_settings_phones_level(uint32_t settings)
{
switch (settings & HDSPE_PHONES_LEVEL_MASK) {
case HDSPE_PHONES_LEVEL_HIGHGAIN:
return ("HighGain");
case HDSPE_PHONES_LEVEL_PLUS4DBU:
return ("+4dBu");
case HDSPE_PHONES_LEVEL_MINUS10DBV:
return ("-10dBV");
default:
return (NULL);
}
}
static int
hdspe_sysctl_phones_level(SYSCTL_HANDLER_ARGS)
{
struct sc_info *sc;
const char *label;
char buf[16] = "invalid";
int error;
uint32_t settings;
sc = oidp->oid_arg1;
/* Only available on HDSPE AIO. */
if (sc->type != HDSPE_AIO)
return (ENXIO);
/* Extract current phones level from settings register. */
settings = sc->settings_register & HDSPE_PHONES_LEVEL_MASK;
label = hdspe_settings_phones_level(settings);
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 = hdspe_settings_phones_level(HDSPE_PHONES_LEVEL_HIGHGAIN);
if (strncasecmp(buf, label, sizeof(buf)) == 0)
settings = HDSPE_PHONES_LEVEL_HIGHGAIN;
label = hdspe_settings_phones_level(HDSPE_PHONES_LEVEL_PLUS4DBU);
if (strncasecmp(buf, label, sizeof(buf)) == 0)
settings = HDSPE_PHONES_LEVEL_PLUS4DBU;
label = hdspe_settings_phones_level(HDSPE_PHONES_LEVEL_MINUS10DBV);
if (strncasecmp(buf, label, sizeof(buf)) == 0)
settings = HDSPE_PHONES_LEVEL_MINUS10DBV;
/* Set phones level in settings register. */
settings &= HDSPE_PHONES_LEVEL_MASK;
if (settings != (sc->settings_register & HDSPE_PHONES_LEVEL_MASK)) {
snd_mtxlock(sc->lock);
sc->settings_register &= ~HDSPE_PHONES_LEVEL_MASK;
sc->settings_register |= settings;
hdspe_write_4(sc, HDSPE_SETTINGS_REG, sc->settings_register);
snd_mtxunlock(sc->lock);
}
return (0);
}
static int
hdspe_sysctl_sample_rate(SYSCTL_HANDLER_ARGS)
{
@ -529,6 +721,15 @@ hdspe_init(struct sc_info *sc)
/* Other settings. */
sc->settings_register = 0;
/* Default gain levels. */
sc->settings_register &= ~HDSPE_INPUT_LEVEL_MASK;
sc->settings_register |= HDSPE_INPUT_LEVEL_LOWGAIN;
sc->settings_register &= ~HDSPE_OUTPUT_LEVEL_MASK;
sc->settings_register |= HDSPE_OUTPUT_LEVEL_MINUS10DBV;
sc->settings_register &= ~HDSPE_PHONES_LEVEL_MASK;
sc->settings_register |= HDSPE_PHONES_LEVEL_MINUS10DBV;
hdspe_write_4(sc, HDSPE_SETTINGS_REG, sc->settings_register);
return (0);
@ -623,6 +824,27 @@ hdspe_attach(device_t dev)
sc, 0, hdspe_sysctl_sample_rate, "A",
"Force sample rate (32000, 44100, 48000, ... 192000)");
if (sc->type == HDSPE_AIO) {
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, hdspe_sysctl_phones_level, "A",
"Phones output level ('HighGain', '+4dBU', '-10dBV')");
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, hdspe_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, hdspe_sysctl_input_level, "A",
"Analog input level ('LowGain', '+4dBU', '-10dBV')");
}
return (bus_generic_attach(dev));
}

View File

@ -74,35 +74,43 @@
#define HDSPE_LAT_BYTES_MIN (32 * 4)
#define hdspe_encode_latency(x) (((x)<<1) & HDSPE_LAT_MASK)
/* Gain */
#define HDSP_ADGain0 (1 << 25)
#define HDSP_ADGain1 (1 << 26)
#define HDSP_DAGain0 (1 << 27)
#define HDSP_DAGain1 (1 << 28)
#define HDSP_PhoneGain0 (1 << 29)
#define HDSP_PhoneGain1 (1 << 30)
#define HDSP_ADGainMask (HDSP_ADGain0 | HDSP_ADGain1)
#define HDSP_ADGainMinus10dBV (HDSP_ADGainMask)
#define HDSP_ADGainPlus4dBu (HDSP_ADGain0)
#define HDSP_ADGainLowGain 0
#define HDSP_DAGainMask (HDSP_DAGain0 | HDSP_DAGain1)
#define HDSP_DAGainHighGain (HDSP_DAGainMask)
#define HDSP_DAGainPlus4dBu (HDSP_DAGain0)
#define HDSP_DAGainMinus10dBV 0
#define HDSP_PhoneGainMask (HDSP_PhoneGain0|HDSP_PhoneGain1)
#define HDSP_PhoneGain0dB HDSP_PhoneGainMask
#define HDSP_PhoneGainMinus6dB (HDSP_PhoneGain0)
#define HDSP_PhoneGainMinus12dB 0
/* Settings */
/* Register addresses */
#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
/* Settings register flags */
#define HDSPE_SETTINGS_INPUT_GAIN0 (1 << 20)
#define HDSPE_SETTINGS_INPUT_GAIN1 (1 << 21)
#define HDSPE_SETTINGS_OUTPUT_GAIN0 (1 << 22)
#define HDSPE_SETTINGS_OUTPUT_GAIN1 (1 << 23)
#define HDSPE_SETTINGS_PHONES_GAIN0 (1 << 24)
#define HDSPE_SETTINGS_PHONES_GAIN1 (1 << 25)
/* Analog input gain level */
#define HDSPE_INPUT_LEVEL_MASK (HDSPE_SETTINGS_INPUT_GAIN0 | \
HDSPE_SETTINGS_INPUT_GAIN1)
#define HDSPE_INPUT_LEVEL_LOWGAIN 0
#define HDSPE_INPUT_LEVEL_PLUS4DBU (HDSPE_SETTINGS_INPUT_GAIN0)
#define HDSPE_INPUT_LEVEL_MINUS10DBV (HDSPE_SETTINGS_INPUT_GAIN1)
/* Analog output gain level */
#define HDSPE_OUTPUT_LEVEL_MASK (HDSPE_SETTINGS_OUTPUT_GAIN0 | \
HDSPE_SETTINGS_OUTPUT_GAIN1)
#define HDSPE_OUTPUT_LEVEL_HIGHGAIN 0
#define HDSPE_OUTPUT_LEVEL_PLUS4DBU (HDSPE_SETTINGS_OUTPUT_GAIN0)
#define HDSPE_OUTPUT_LEVEL_MINUS10DBV (HDSPE_SETTINGS_OUTPUT_GAIN1)
/* Phones output gain level */
#define HDSPE_PHONES_LEVEL_MASK (HDSPE_SETTINGS_PHONES_GAIN0 | \
HDSPE_SETTINGS_PHONES_GAIN1)
#define HDSPE_PHONES_LEVEL_HIGHGAIN 0
#define HDSPE_PHONES_LEVEL_PLUS4DBU (HDSPE_SETTINGS_PHONES_GAIN0)
#define HDSPE_PHONES_LEVEL_MINUS10DBV (HDSPE_SETTINGS_PHONES_GAIN1)
/* Control register flags */
#define HDSPE_ENABLE (1 << 0)
/* Interrupts */