]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ALSA: usb-audio: Add QUIRK_FLAG_MIXER_{PLAYBACK,CAPTURE}_LINEAR_VOL
authorRong Zhang <i@rong.moe>
Tue, 3 Mar 2026 19:48:01 +0000 (03:48 +0800)
committerTakashi Iwai <tiwai@suse.de>
Wed, 4 Mar 2026 11:05:57 +0000 (12:05 +0100)
Some quirky devices tune their volume by linearly tuning the voltage
level (linear volume). In other words, such devices has a linear TLV
mapping of DECLARE_TLV_DB_LINEAR(scale, TLV_DB_GAIN_MUTE, 0).

Add quirk flags MIXER_PLAYBACK_LINEAR_VOL and MIXER_CAPTURE_LINEAR_VOL
to represent this case respectively for playback and capture mixers.

No functional change intended.

Signed-off-by: Rong Zhang <i@rong.moe>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Link: https://patch.msgid.link/20260303194805.266158-7-i@rong.moe
Documentation/sound/alsa-configuration.rst
sound/usb/mixer_quirks.c
sound/usb/quirks.c
sound/usb/usbaudio.h

index 55b845d382368fdadf06b5e86b997234c4124668..f75f087639415dd171e18d06f2dc6db8def33aae 100644 (file)
@@ -2376,6 +2376,13 @@ quirk_flags
           Skip the probe-time interface setup (usb_set_interface,
           init_pitch, init_sample_rate); redundant with
           snd_usb_endpoint_prepare() at stream-open time
+        * bit 27: ``mixer_playback_linear_vol``
+          Set linear volume mapping for devices where the playback volume
+          control value is mapped to voltage (instead of dB) level linearly.
+          In short: ``x(raw) = (raw - raw_min) / (raw_max - raw_min)``;
+          ``V(x) = k * x``; ``dB(x) = 20 * log10(x)``. Overrides bit 24
+        * bit 28: ``mixer_capture_linear_vol``
+          Similar to bit 27 but for capture streams. Overrides bit 25
 
 This module supports multiple devices, autoprobe and hotplugging.
 
index 11e205da7964de636110b8f2eebcd224ab23f4c8..539044c0c6440afa80e64935026818b6885cc93a 100644 (file)
@@ -4634,6 +4634,25 @@ triggered:
        usb_audio_dbg(chip, "something wrong in kctl name %s\n", id->name);
 }
 
+static void snd_usb_mixer_fu_quirk_linear_scale(struct usb_mixer_interface *mixer,
+                                               struct usb_mixer_elem_info *cval,
+                                               struct snd_kcontrol *kctl)
+{
+       static const DECLARE_TLV_DB_LINEAR(scale, TLV_DB_GAIN_MUTE, 0);
+
+       if (cval->min_mute) {
+               /*
+                * We are clearing SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK,
+                * resulting in min_mute being a no-op.
+                */
+               usb_audio_warn(mixer->chip, "LINEAR_VOL overrides MIN_MUTE\n");
+       }
+
+       kctl->tlv.p = scale;
+       kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+       kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
+}
+
 void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer,
                                  struct usb_mixer_elem_info *cval, int unitid,
                                  struct snd_kcontrol *kctl)
@@ -4660,6 +4679,21 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer,
                                       "applying capture min mute quirk\n");
                        cval->min_mute = 1;
                }
+
+       if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL)
+               if (cval->control == UAC_FU_VOLUME && strstr(kctl->id.name, "Playback")) {
+                       usb_audio_info(mixer->chip,
+                                      "applying playback linear volume quirk\n");
+                       snd_usb_mixer_fu_quirk_linear_scale(mixer, cval, kctl);
+               }
+
+       if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL)
+               if (cval->control == UAC_FU_VOLUME && strstr(kctl->id.name, "Capture")) {
+                       usb_audio_info(mixer->chip,
+                                      "applying capture linear volume quirk\n");
+                       snd_usb_mixer_fu_quirk_linear_scale(mixer, cval, kctl);
+               }
+
        /* ALSA-ify some Plantronics headset control names */
        if (USB_ID_VENDOR(mixer->chip->usb_id) == 0x047f &&
            (cval->control == UAC_FU_MUTE || cval->control == UAC_FU_VOLUME))
index 00d1a7c2260e7e33ea7fbdccfd25fdc9a95505fc..7a5cec9cc4bd94ad20ac54f4477e0133cbe6e55f 100644 (file)
@@ -2543,6 +2543,8 @@ static const char *const snd_usb_audio_quirk_flag_names[] = {
        QUIRK_STRING_ENTRY(MIXER_PLAYBACK_MIN_MUTE),
        QUIRK_STRING_ENTRY(MIXER_CAPTURE_MIN_MUTE),
        QUIRK_STRING_ENTRY(SKIP_IFACE_SETUP),
+       QUIRK_STRING_ENTRY(MIXER_PLAYBACK_LINEAR_VOL),
+       QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL),
        NULL
 };
 
index 085530cf62d92421f16dbec8f03f9f9db4fcc03d..58fd07f8c3c97448f57d543bb22cd32233052fbe 100644 (file)
@@ -228,6 +228,14 @@ extern bool snd_usb_skip_validation;
  *  Skip the probe-time interface setup (usb_set_interface,
  *  init_pitch, init_sample_rate); redundant with
  *  snd_usb_endpoint_prepare() at stream-open time
+ * QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL
+ *  Set linear volume mapping for devices where the playback volume control
+ *  value is mapped to voltage (instead of dB) level linearly. In short:
+ *  x(raw) = (raw - raw_min) / (raw_max - raw_min); V(x) = k * x;
+ *  dB(x) = 20 * log10(x). Overrides QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE
+ * QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL
+ *  Similar to QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL, but for capture streams.
+ *  Overrides QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE
  */
 
 enum {
@@ -258,6 +266,8 @@ enum {
        QUIRK_TYPE_MIXER_PLAYBACK_MIN_MUTE      = 24,
        QUIRK_TYPE_MIXER_CAPTURE_MIN_MUTE       = 25,
        QUIRK_TYPE_SKIP_IFACE_SETUP             = 26,
+       QUIRK_TYPE_MIXER_PLAYBACK_LINEAR_VOL    = 27,
+       QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL     = 28,
 /* Please also edit snd_usb_audio_quirk_flag_names */
 };
 
@@ -290,5 +300,7 @@ enum {
 #define QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE     QUIRK_FLAG(MIXER_PLAYBACK_MIN_MUTE)
 #define QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE      QUIRK_FLAG(MIXER_CAPTURE_MIN_MUTE)
 #define QUIRK_FLAG_SKIP_IFACE_SETUP            QUIRK_FLAG(SKIP_IFACE_SETUP)
+#define QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL   QUIRK_FLAG(MIXER_PLAYBACK_LINEAR_VOL)
+#define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL    QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL)
 
 #endif /* __USBAUDIO_H */