Implementando correctamente el audio en Asus ROG Zephyrus M15 en GNU/Linux

Holas, como sabrán soy usuario de GNU/Linux desde hace algunos años, específicamente de openSUSE, debido a que necesito bastante potencia para el trabajo que realizo decidí adquirir una nueva laptop en concreto una Asus ROG Zephyrus M15 modelo GU502LW, como muchos usuarios de GNU/Linux encendí el equipo con el sistema operativo Windows pre-instalado, actualicé la BIOS y me puse manos a la obra para borrar Windows del disco e instalar el preciado sistema del pingüino.

El proceso de instalación con openSUSE Tumbleweed avanzó de manera correcta, el equipo se reinicia con normalidad, al igual que acceder a una sesión de usuario en KDE Plasma 5, pero al empezar a configurarlo e instalar las aplicaciones que necesito doy con el inconveniente de que no tenía audio en mis auriculares. El audio através de los parlantes del equipo funcionaba correctamente, pero los auricualres ni si quiera eran reconocidos, ya que la conexión vía el Jack 3.5mm se gestiona a través de un firmware en la tarjeta de audio, del cual no tenemos el driver para GNU/Linux, que es la situación actual los equipos portátiles actuales.

Este tipo de situaciones se suelen presentar cuando los equipos son nuevos en el mercado y/o el fabricante no libera el driver y/o las especificaciones para el correcto funcionamiento bajo un sistema diferente a Windows, ya sea GNU/Linux, BSD o algún otro, por lo tanto los usuarios tenemos que hacer algunos malabares y así conseguir que el hardware que tenemos pueda trabajar en nuesto sistema operativo de preferencia.

En base a esto lo primero es obtener los dumps del audio bajo un sistema Windows (donde funciona correctamente) para ello podemos utilizar una aplicación llamada RtHDDump la cual nos va a guardar un archivo con un texto similar al siguiente:

PCI ID ==> PCI\VEN_8086&DEV_06C8&SUBSYS_1E511043&REV_00\3&11583659&0&FB
HDA ID ==> HDAUDIO\FUNC_01&VEN_10EC&DEV_0294&SUBSYS_10431E51&REV_1000 <Realtek High Definition Audio>

< RtHDDump.exe file version V2.3.0.6 >
File version => 6.0.8931.1 built by: WinDDK
File internal name=> RTKVHD64.sys 8931
<8931> <RTKVHD64.sys 8931>
Model index = 83

Wid=12  Codec=90A60120  Drv=90A60120  Loc=00000000
Wid=13  Codec=40000000  Drv=40000000  Loc=00000000
Wid=14  Codec=411111F0  Drv=411111F0  Loc=00000000
Wid=15  Codec=411111F0  Drv=01211420  Loc=00000000
Wid=16  Codec=411111F0  Drv=411111F0  Loc=00000000
Wid=17  Codec=90170110  Drv=90170110  Loc=00000000
Wid=18  Codec=411111F0  Drv=411111F0  Loc=00000000
Wid=19  Codec=411111F0  Drv=01A11050  Loc=00080000
Wid=1A  Codec=411111F0  Drv=01A11830  Loc=00020200
Wid=1B  Codec=411111F0  Drv=411111F0  Loc=00000000
Wid=1D  Codec=40600001  Drv=40600001  Loc=00000000
Wid=1E  Codec=411111F0  Drv=411111F0  Loc=00000000
Wid=1F  Codec=411111F0  Drv=411111F0  Loc=00000000
Wid=21  Codec=411111F0  Drv=411111F0  Loc=00000000

Codec Address = 0
Subsystem ID = 10431E51
Revision ID = 00100004

=====================================================================================================================
[General Information]

Major Version = 10
Minor Version = 0
Windows 2000 = 0
Windows XP = 0
Windows 2003 = 0
Windows Vista = 0
Windows 7 = 0
Windows 8 = 0
Windows 8.1 = 0
Windows 10 = 1
MCE System = 0
Server System = 0
Windows x64 System = 1
Build Number = 19042
Service Pack = 0
System Language ID = 0x080A
User Language ID = 0x300A

Rtport interface status = 0
HDA Driver interface status = 1
HDACD status = 0

I/O Interface : CORB/RIRB

High Definition Audio Device : Cad:0 VEN_10EC&DEV_0294&SUBSYS_10431E51&REV_1000\4&23F68B4&0&0001
C:\Windows\system32\drivers\RTKVHD64.sys                                        : 6.0.8931.1
C:\Windows\system32\drivers\RTAIODAT.DAT                                        : 
C:\Windows\inf\oem31.inf
.....

De este dump nos interesa por el momento la sección que dice:

Wid=12 Codec=90A60120 Drv=90A60120 Loc=00000000
Wid=13 Codec=40000000 Drv=40000000 Loc=00000000
Wid=14 Codec=411111F0 Drv=411111F0 Loc=00000000
Wid=15 Codec=411111F0 Drv=01211420 Loc=00000000
Wid=16 Codec=411111F0 Drv=411111F0 Loc=00000000
Wid=17 Codec=90170110 Drv=90170110 Loc=00000000
Wid=18 Codec=411111F0 Drv=411111F0 Loc=00000000
Wid=19 Codec=411111F0 Drv=01A11050 Loc=00080000
Wid=1A Codec=411111F0 Drv=01A11830 Loc=00020200
Wid=1B Codec=411111F0 Drv=411111F0 Loc=00000000
Wid=1D Codec=40600001 Drv=40600001 Loc=00000000
Wid=1E Codec=411111F0 Drv=411111F0 Loc=00000000
Wid=1F Codec=411111F0 Drv=411111F0 Loc=00000000
Wid=21 Codec=411111F0 Drv=411111F0 Loc=00000000

Luego vamos a nuesto sistema GNU/Linux de preferencia y obtenemos los dumps con el siguiente comando:

cat /proc/asound/card0/codec#0

Esto nos va a mostrar un texto con un formato similar al siguiente:

Codec: Realtek ALC294
Address: 0
AFG Function Id: 0x1 (unsol 1)
Vendor Id: 0x10ec0294
Subsystem Id: 0x10431e51
Revision Id: 0x100004
No Modem Function Group found
Default PCM:
    rates [0x560]: 44100 48000 96000 192000
    bits [0xe]: 16 20 24
    formats [0x1]: PCM
Default Amp-In caps: N/A
Default Amp-Out caps: N/A
State of AFG node 0x01:
  Power states:  D0 D1 D2 D3 D3cold CLKSTOP EPSS
  Power: setting=D0, actual=D0
GPIO: io=4, o=0, i=0, unsolicited=1, wake=0
  IO[0]: enable=0, dir=0, wake=0, sticky=0, data=0, unsol=0
  IO[1]: enable=0, dir=0, wake=0, sticky=0, data=0, unsol=0
  IO[2]: enable=0, dir=0, wake=0, sticky=0, data=0, unsol=0
  IO[3]: enable=0, dir=0, wake=0, sticky=0, data=0, unsol=0
Node 0x02 [Audio Output] wcaps 0x41d: Stereo Amp-Out
  Control: name="Master Playback Volume", index=0, device=0
    ControlAmp: chs=3, dir=Out, idx=0, ofs=0
  Device: name="ALC294 Analog", type="Audio", device=0
  Amp-Out caps: ofs=0x57, nsteps=0x57, stepsize=0x02, mute=0
  Amp-Out vals:  [0x3b 0x3b]
  Converter: stream=0, channel=0
  PCM:
    rates [0x540]: 48000 96000 192000
    bits [0xe]: 16 20 24
    formats [0x1]: PCM
  Power states:  D0 D1 D2 D3 EPSS
  Power: setting=D0, actual=D0
Node 0x03 [Audio Output] wcaps 0x41d: Stereo Amp-Out
  Amp-Out caps: ofs=0x57, nsteps=0x57, stepsize=0x02, mute=0
  Amp-Out vals:  [0x57 0x57]
  Converter: stream=0, channel=0
  PCM:
    rates [0x40]: 48000
    bits [0xe]: 16 20 24
    formats [0x1]: PCM
  Power states:  D0 D1 D2 D3 EPSS
  Power: setting=D0, actual=D0
Node 0x04 [Vendor Defined Widget] wcaps 0xf00000: Mono
Node 0x05 [Vendor Defined Widget] wcaps 0xf00000: Mono
Node 0x06 [Audio Output] wcaps 0x411: Stereo
  Converter: stream=0, channel=0
......

Aquí podemos ver que lo que en Windows se llama Wid en Linux es Node

Wid12 -> Node 0x12

También necesitamos el Vendor y el Subsistema los cuales se encuentran al inicio del dump y se muestran de la siguiente manera:

Vendor Id: 0x10ec0294
Subsystem Id: 0x10431e51

Debemos encontrar donde se diferencia el código Drv de Windows con el Pin Default XXXXX: de Linux

Por último necesitamos buscar los llamados verbs, dichos verbs nos permiten añadir la acción correcta para cosas como conectar unos auriculares y que el sistema operativo pueda cambiar el sonido a los mismos y poner en silencio los parlantes del equipo. Para ello utilizaremos una aplicación llamada hda-verb que en GNU/Linux que nos permitirá probar las acciones mencionadas.

Cambiar de los parlantes a los auriculares

hda-verb /dev/snd/hwC0D0 0x20 0x500 0x10
hda-verb /dev/snd/hwC0D0 0x20 0x400 0x8420

Cambiar de los auriculares a los parlantes

hda-verb /dev/snd/hwC0D0 0x20 0x500 0x10
hda-verb /dev/snd/hwC0D0 0x20 0x400 0x0a20

Ahora que tenemos funcionando correctamente el audio en nuestro sistema GNU/Linux debemos proceder a ponerlo en el arranque del sistema, para ello vamos a necesitar 2 archivos.

alc-sound-patch.fw

[codec]
0x10ec0294 0x10431e51 0

[pincfg]
0x19 0x01A11050
0x1a 0x01A11830
0x21 0x012110f0

[verb]
0x15 0x707 0xc0
0x15 0x300 0xb000
0x1b 0x707 0x24

Este archivo debe ubicarse en el path /lib/firmware/alc-sound-patch.fw

alsa-patch.conf

options snd-hda-intel patch=alc-sound-patch.fw

Este archivo debe ubicarse en el path /etc/modprobe.d/alsa-patch.conf

Con esto podemos reiniciar nuestro equipo y probar que todo trabaje de manera propicia, es decir conectar nuestros auriculares y que el audio se envíe a través de los mismos mientras que los parlantes del equipo estan en silencio y al desconectar se realice automaticamente la acción opuesta, si es así los más habilidosos y/o curiosos pueden dedicarse a programar en C el respectivo parche para el kernel (en este caso de GNU/Linux) que pertenezca a su modelo de equipo portátil.

El parche específico para el modelo Asus ROG Zephyrus M15 es el siguiente:

From 8ca8d00c84ddc1263bb3b1449ecfd5130f82bd28 Mon Sep 17 00:00:00 2001
From: Daniel Cordova A <[email protected]>
Date: Fri, 7 May 2021 12:29:03 -0500
Subject: [PATCH] ALSA: hda: fixup headset for ASUS GU502 laptop

The GU502 requires a few steps to make headset i/o works properly:
pincfg, verbs to unmute headphone out and callback to toggle output
between speakers and headphone using jack.

Signed-off-by: Daniel Cordova A. <[email protected]>
---
 sound/pci/hda/patch_realtek.c | 62 +++++++++++++++++++++++++++++++++++
 1 file changed, 62 insertions(+)

diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
index bd7bfd7c9ee7..2b18f078edc7 100644
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -6210,6 +6210,35 @@ static void alc294_fixup_gx502_hp(struct hda_codec *codec,
    }
 }

+static void alc294_gu502_toggle_output(struct hda_codec *codec,
+                      struct hda_jack_callback *cb)
+{
+   /* Windows sets 0x10 to 0x8420 for Node 0x20 which is
+    * responsible from changes between speakers and headphones
+    */
+   if (snd_hda_jack_detect_state(codec, 0x21) == HDA_JACK_PRESENT)
+       alc_write_coef_idx(codec, 0x10, 0x8420);
+   else
+       alc_write_coef_idx(codec, 0x10, 0x0a20);
+}
+
+static void alc294_fixup_gu502_hp(struct hda_codec *codec,
+                 const struct hda_fixup *fix, int action)
+{
+   if (!is_jack_detectable(codec, 0x21))
+       return;
+
+   switch (action) {
+   case HDA_FIXUP_ACT_PRE_PROBE:
+       snd_hda_jack_detect_enable_callback(codec, 0x21,
+               alc294_gu502_toggle_output);
+       break;
+   case HDA_FIXUP_ACT_INIT:
+       alc294_gu502_toggle_output(codec, NULL);
+       break;
+   }
+}
+
 static void  alc285_fixup_hp_gpio_amp_init(struct hda_codec *codec,
                  const struct hda_fixup *fix, int action)
 {
@@ -6427,6 +6456,9 @@ enum {
    ALC294_FIXUP_ASUS_GX502_HP,
    ALC294_FIXUP_ASUS_GX502_PINS,
    ALC294_FIXUP_ASUS_GX502_VERBS,
+   ALC294_FIXUP_ASUS_GU502_HP,
+   ALC294_FIXUP_ASUS_GU502_PINS,
+   ALC294_FIXUP_ASUS_GU502_VERBS,
    ALC285_FIXUP_HP_GPIO_LED,
    ALC285_FIXUP_HP_MUTE_LED,
    ALC236_FIXUP_HP_GPIO_LED,
@@ -7665,6 +7697,35 @@ static const struct hda_fixup alc269_fixups[] = {
        .type = HDA_FIXUP_FUNC,
        .v.func = alc294_fixup_gx502_hp,
    },
+   [ALC294_FIXUP_ASUS_GU502_PINS] = {
+       .type = HDA_FIXUP_PINS,
+       .v.pins = (const struct hda_pintbl[]) {
+           { 0x19, 0x01a11050 }, /* rear HP mic */
+           { 0x1a, 0x01a11830 }, /* rear external mic */
+           { 0x21, 0x012110f0 }, /* rear HP out */
+           { }
+       },
+       .chained = true,
+       .chain_id = ALC294_FIXUP_ASUS_GU502_VERBS
+   },
+   [ALC294_FIXUP_ASUS_GU502_VERBS] = {
+       .type = HDA_FIXUP_VERBS,
+       .v.verbs = (const struct hda_verb[]) {
+           /* set 0x15 to HP-OUT ctrl */
+           { 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+           /* unmute the 0x15 amp */
+           { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000 },
+           /* set 0x1b to HP-OUT */
+           { 0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+           { }
+       },
+       .chained = true,
+       .chain_id = ALC294_FIXUP_ASUS_GU502_HP
+   },
+   [ALC294_FIXUP_ASUS_GU502_HP] = {
+       .type = HDA_FIXUP_FUNC,
+       .v.func = alc294_fixup_gu502_hp,
+   },
    [ALC294_FIXUP_ASUS_COEF_1B] = {
        .type = HDA_FIXUP_VERBS,
        .v.verbs = (const struct hda_verb[]) {
@@ -8178,6 +8239,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
    SND_PCI_QUIRK(0x1043, 0x1ccd, "ASUS X555UB", ALC256_FIXUP_ASUS_MIC),
    SND_PCI_QUIRK(0x1043, 0x1d4e, "ASUS TM420", ALC256_FIXUP_ASUS_HPE),
    SND_PCI_QUIRK(0x1043, 0x1e11, "ASUS Zephyrus G15", ALC289_FIXUP_ASUS_GA502),
+   SND_PCI_QUIRK(0x1043, 0x1e51, "ASUS Zephyrus M15", ALC294_FIXUP_ASUS_GU502_PINS),
    SND_PCI_QUIRK(0x1043, 0x1e8e, "ASUS Zephyrus G15", ALC289_FIXUP_ASUS_GA401),
    SND_PCI_QUIRK(0x1043, 0x1f11, "ASUS Zephyrus G14", ALC289_FIXUP_ASUS_GA401),
    SND_PCI_QUIRK(0x1043, 0x3030, "ASUS ZN270IE", ALC256_FIXUP_ASUS_AIO_GPIO2),
-- 
2.31.1

Los que deseen obtener más información respecto a este tema, pueden consultar el siguiente enlace:
https://asus-linux.org/blog/sound-2021-01-11/

Eso es todo por hoy
Happy Hacking!

You may also like...