HeadsetMicGain: query device-reported gain bounds from GetInfo

Per newly-documented HEADSET_MIC_WIRE_PROTOCOL.md, feature 0x0611
returns device-specific NACK 0x0B when SetMicGain is written with a
value outside the device's supported range. LGHUB calls GetInfo
(function 0) once at startup to cache (min_gain, max_gain) as two
signed int8 bytes, then rescales subsequent writes into that range.

Solaar was using int8's full range (-128..127) as validator bounds
and accepting any UI value — which goes out-of-range on devices with
narrow ranges like the G522 (current gain is 8, suggesting small
range). Every gain change attempt NACK'd with 0x0B.

Fix: add HeadsetMicGain.build() that queries GetInfo at probe time,
parses the two-byte [min, max] response as signed int8, and hands
those to the RangeValidator. Falls back to the int8 default range
if GetInfo is unavailable or returns nonsense. Logs the reported
range at INFO so testers can verify in the log.

Does NOT address mic-mute NACK 0x0A — per the same doc that's the
hardware mic-flip boom position locking software mute on the G522,
and there's nothing software can do about it.
This commit is contained in:
Ken Sanislo 2026-04-18 02:12:44 -07:00
parent fa205436f1
commit 299dc4daab
1 changed files with 29 additions and 0 deletions

View File

@ -1666,10 +1666,39 @@ class HeadsetMicGain(settings.Setting):
feature = _F.HEADSET_MIC_GAIN
rw_options = {"read_fnid": 0x10, "write_fnid": 0x20}
validator_class = settings_validator.RangeValidator
# Fallback range covers int8; build() overrides with device-reported bounds
# from GetInfo (fn 0) so SetMicGain doesn't get device-specific
# out-of-range NACK (error 0x0B) on devices that use a small signed range
# (e.g. G522 reports a narrow window like -12..+12).
min_value = -128
max_value = 127
validator_options = {"byte_count": 1, "signed": True}
@classmethod
def build(cls, device):
# GetInfo (function 0) returns [min_gain (int8), max_gain (int8)].
# LGHUB caches these once at startup to rescale SetMicGain writes.
try:
info = device.feature_request(cls.feature, 0x00)
except Exception:
info = None
if info and len(info) >= 2:
min_gain = struct.unpack("b", bytes([info[0]]))[0]
max_gain = struct.unpack("b", bytes([info[1]]))[0]
if max_gain <= min_gain: # sanity — fall back to class defaults
min_gain, max_gain = cls.min_value, cls.max_value
else:
logger.info(
"HeadsetMicGain: device reports gain range [%d, %d]",
min_gain,
max_gain,
)
else:
min_gain, max_gain = cls.min_value, cls.max_value
rw = settings.FeatureRW(cls.feature, **cls.rw_options)
validator = settings_validator.RangeValidator(min_value=min_gain, max_value=max_gain, byte_count=1, signed=True)
return cls(device, rw, validator)
class HeadsetMixBalance(settings.Setting):
name = "headset-mix-balance"