* [PATCH 1/2] ALSA: usb-audio: Use ALC4080 mixer map for ASRock X870E Taichi
2026-11-08 18:58 [PATCH 0/2] Subject: [PATCH 0/2] ALSA: usb-audio: Mixer quirks for ASRock X870E Taichi Brian Harring via B4 Relay
@ 2026-11-08 18:58 ` Brian Harring via B4 Relay
2026-11-08 18:58 ` [PATCH 2/2] ALSA: usb-audio: Add ES9219 speaker volume " Brian Harring via B4 Relay
1 sibling, 0 replies; 4+ messages in thread
From: Brian Harring via B4 Relay @ 2026-11-08 18:58 UTC (permalink / raw)
To: Jaroslav Kysela, Takashi Iwai; +Cc: linux-sound, Brian Harring
From: Brian Harring <ferringb@gmail.com>
The ASRock X870E Taichi / Taichi Lite onboard audio (26ce:0a0b, Realtek
ALC4082) has the same problem as the MSI ALC4080 boards already using
this map: all its playback feature units are named "PCM", so the UCM
profile pairs the headphone volume with the rear output's mute. Its
unit IDs match, so use the same map, renamed to alc408x_map now that
it's no longer MSI-only.
Signed-off-by: Brian Harring <ferringb@gmail.com>
---
sound/usb/mixer_maps.c | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c
index 9218ca6314ad..15065ee3f363 100644
--- a/sound/usb/mixer_maps.c
+++ b/sound/usb/mixer_maps.c
@@ -486,8 +486,8 @@ static const struct usbmix_name_map aorus_master_alc1220vb_map[] = {
{}
};
-/* MSI MPG X570S Carbon Max Wifi with ALC4080 */
-static const struct usbmix_name_map msi_mpg_x570s_carbon_max_wifi_alc4080_map[] = {
+/* ALC4080/ALC4082 boards with this playback feature unit layout */
+static const struct usbmix_name_map alc408x_map[] = {
{ 29, "Speaker Playback" },
{ 30, "Front Headphone Playback" },
{ 32, "IEC958 Playback" },
@@ -770,15 +770,19 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = {
},
{ /* MSI MPG X570S Carbon Max Wifi */
.id = USB_ID(0x0db0, 0x419c),
- .map = msi_mpg_x570s_carbon_max_wifi_alc4080_map,
+ .map = alc408x_map,
},
{ /* MSI MAG X570S Torpedo Max */
.id = USB_ID(0x0db0, 0xa073),
- .map = msi_mpg_x570s_carbon_max_wifi_alc4080_map,
+ .map = alc408x_map,
},
{ /* MSI MAG B850M MORTAR WIFI */
.id = USB_ID(0x0db0, 0xcc78),
- .map = msi_mpg_x570s_carbon_max_wifi_alc4080_map,
+ .map = alc408x_map,
+ },
+ { /* ASRock X870E Taichi / Taichi Lite (ALC4082) */
+ .id = USB_ID(0x26ce, 0x0a0b),
+ .map = alc408x_map,
},
{ /* MSI TRX40 */
.id = USB_ID(0x0db0, 0x543d),
--
2.56.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH 2/2] ALSA: usb-audio: Add ES9219 speaker volume for ASRock X870E Taichi
2026-11-08 18:58 [PATCH 0/2] Subject: [PATCH 0/2] ALSA: usb-audio: Mixer quirks for ASRock X870E Taichi Brian Harring via B4 Relay
2026-11-08 18:58 ` [PATCH 1/2] ALSA: usb-audio: Use ALC4080 mixer map " Brian Harring via B4 Relay
@ 2026-11-08 18:58 ` Brian Harring via B4 Relay
2026-11-08 19:12 ` sashiko-bot
1 sibling, 1 reply; 4+ messages in thread
From: Brian Harring via B4 Relay @ 2026-11-08 18:58 UTC (permalink / raw)
To: Jaroslav Kysela, Takashi Iwai; +Cc: linux-sound, Brian Harring
From: Brian Harring <ferringb@gmail.com>
The rear output of the ASRock X870E Taichi / Taichi Lite onboard audio
(26ce:0a0b, Realtek ALC4082) has a feature unit with only a mute, but an
ESS ES9219 DAC sits behind it which can be used for volume control.
The DAC is reachable through the codec's I2C bridge, using the vendor
requests found in Realtek's Windows 10 driver (version 6.4.0.2449). That
driver doesn't enable this for any ASRock device; at a guess, ASRock
simply overlooked it.
Assisted-by: Claude Opus 5.5
Signed-off-by: Brian Harring <ferringb@gmail.com>
---
sound/usb/mixer_quirks.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 157 insertions(+)
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
index 4c7d68311f20..075fb5847030 100644
--- a/sound/usb/mixer_quirks.c
+++ b/sound/usb/mixer_quirks.c
@@ -4656,6 +4656,159 @@ static int snd_xiisound_init_fu_pins(struct usb_mixer_interface *mixer)
return 0;
}
+/*
+ * ASRock X870E Taichi (ALC4082): the rear line exposes only mute (feature
+ * unit 19), but there is an ESS ES9219 DAC behind it that is usable for
+ * volume control.
+ */
+#define TAICHI_I2C_READ 0x3d
+#define TAICHI_I2C_WRITE 0x3e
+#define TAICHI_I2C_INDEX 0x0100
+#define TAICHI_ES9219_I2C_ADDR 0x48
+#define TAICHI_ES9219_REG_VOL1 0x0f
+#define TAICHI_ES9219_REG_VOL2 0x10
+#define TAICHI_ES9219_VOL_MAX 0xff
+
+static int taichi_es9219_read(struct snd_usb_audio *chip, u8 reg, u8 *val)
+{
+ struct usb_device *dev = chip->dev;
+ u8 buf[2] = { reg, 0 };
+ int err;
+
+ err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), TAICHI_I2C_READ,
+ USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_OUT,
+ TAICHI_ES9219_I2C_ADDR, TAICHI_I2C_INDEX,
+ buf, sizeof(buf));
+ if (err < 0)
+ return err;
+ err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), TAICHI_I2C_READ,
+ USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_IN,
+ TAICHI_ES9219_I2C_ADDR, TAICHI_I2C_INDEX,
+ buf, sizeof(buf));
+ if (err < 0)
+ return err;
+ if (err < 1)
+ return -EIO;
+ *val = buf[0];
+ return 0;
+}
+
+static int taichi_es9219_write(struct snd_usb_audio *chip, u8 reg, u8 val)
+{
+ struct usb_device *dev = chip->dev;
+ u8 buf[2] = { reg, val };
+ int err;
+
+ err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), TAICHI_I2C_WRITE,
+ USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_OUT,
+ TAICHI_ES9219_I2C_ADDR, TAICHI_I2C_INDEX,
+ buf, sizeof(buf));
+ return err < 0 ? err : 0;
+}
+
+static int taichi_es9219_vol_update(struct usb_mixer_interface *mixer,
+ unsigned long pv)
+{
+ struct snd_usb_audio *chip = mixer->chip;
+ int err;
+
+ CLASS(snd_usb_lock, pm)(chip);
+ if (pm.err < 0)
+ return pm.err;
+
+ err = taichi_es9219_write(chip, TAICHI_ES9219_REG_VOL1,
+ TAICHI_ES9219_VOL_MAX - (pv & 0xff));
+ if (err < 0)
+ return err;
+ return taichi_es9219_write(chip, TAICHI_ES9219_REG_VOL2,
+ TAICHI_ES9219_VOL_MAX - ((pv >> 8) & 0xff));
+}
+
+static int taichi_es9219_vol_info(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = TAICHI_ES9219_VOL_MAX;
+ return 0;
+}
+
+static int taichi_es9219_vol_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = kctl->private_value & 0xff;
+ ucontrol->value.integer.value[1] = (kctl->private_value >> 8) & 0xff;
+ return 0;
+}
+
+static int taichi_es9219_vol_put(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl);
+ long l = ucontrol->value.integer.value[0];
+ long r = ucontrol->value.integer.value[1];
+ unsigned long pv;
+ int err;
+
+ if (l < 0 || l > TAICHI_ES9219_VOL_MAX ||
+ r < 0 || r > TAICHI_ES9219_VOL_MAX)
+ return -EINVAL;
+
+ pv = l | (r << 8);
+ if (pv == kctl->private_value)
+ return 0;
+
+ kctl->private_value = pv;
+ err = taichi_es9219_vol_update(list->mixer, pv);
+ return err < 0 ? err : 1;
+}
+
+static int taichi_es9219_vol_resume(struct usb_mixer_elem_list *list)
+{
+ return taichi_es9219_vol_update(list->mixer, list->kctl->private_value);
+}
+
+static const DECLARE_TLV_DB_SCALE(taichi_es9219_db_scale, -12750, 50, 0);
+
+static const struct snd_kcontrol_new taichi_es9219_vol = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Speaker Playback Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .info = taichi_es9219_vol_info,
+ .get = taichi_es9219_vol_get,
+ .put = taichi_es9219_vol_put,
+ .tlv = { .p = taichi_es9219_db_scale },
+};
+
+static int taichi_es9219_controls_create(struct usb_mixer_interface *mixer)
+{
+ struct snd_usb_audio *chip = mixer->chip;
+ struct usb_mixer_elem_list *list;
+ u8 v1, v2;
+ int err;
+
+ err = taichi_es9219_read(chip, TAICHI_ES9219_REG_VOL1, &v1);
+ if (err < 0) {
+ usb_audio_dbg(chip, "ES9219 VOL1 read failed: %d\n", err);
+ return 0;
+ }
+ err = taichi_es9219_read(chip, TAICHI_ES9219_REG_VOL2, &v2);
+ if (err < 0) {
+ usb_audio_dbg(chip, "ES9219 VOL2 read failed: %d\n", err);
+ return 0;
+ }
+
+ err = add_single_ctl_with_resume(mixer, 0, taichi_es9219_vol_resume,
+ &taichi_es9219_vol, &list);
+ if (err < 0)
+ return err;
+ list->kctl->private_value = (TAICHI_ES9219_VOL_MAX - v1) |
+ ((TAICHI_ES9219_VOL_MAX - v2) << 8);
+ return 0;
+}
+
int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
{
int err = 0;
@@ -4797,6 +4950,10 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
err = dell_dock_mixer_create(mixer);
break;
+ case USB_ID(0x26ce, 0x0a0b): /* ASRock X870E Taichi (Lite) */
+ err = taichi_es9219_controls_create(mixer);
+ break;
+
case USB_ID(0x2a39, 0x3fd2): /* RME ADI-2 Pro */
case USB_ID(0x2a39, 0x3fd3): /* RME ADI-2 DAC */
case USB_ID(0x2a39, 0x3fd4): /* RME */
--
2.56.0
^ permalink raw reply related [flat|nested] 4+ messages in thread