[PATCH] ALSA: hda/proc: show GPI and GPO state in codec proc output

From: Cássio Gabriel

Date: Sat Mar 28 2026 - 00:53:51 EST


print_gpio() prints the GPIO capability header and the bidirectional
GPIO state, but it never reports the separate GPI and GPO pins even
though AC_PAR_GPIO_CAP exposes their counts.

The HD-audio specification defines dedicated GPI and GPO verbs
alongside the GPIO ones, so codecs with input-only or output-only
general-purpose pins currently lose that state from
/proc/asound/card*/codec#* altogether.

Add the missing read verb definitions and extend print_gpio() to dump
the GPI and GPO pins, too, while leaving the existing IO[] output
unchanged.

Signed-off-by: Cássio Gabriel <cassiogabrielcontato@xxxxxxxxx>
---
include/sound/hda_verbs.h | 7 +++-
sound/hda/common/proc.c | 100 +++++++++++++++++++++++++++++++---------------
2 files changed, 74 insertions(+), 33 deletions(-)

diff --git a/include/sound/hda_verbs.h b/include/sound/hda_verbs.h
index 006d358acce2..127e7016e4fe 100644
--- a/include/sound/hda_verbs.h
+++ b/include/sound/hda_verbs.h
@@ -56,7 +56,12 @@ enum {
#define AC_VERB_GET_DIGI_CONVERT_1 0x0f0d
#define AC_VERB_GET_DIGI_CONVERT_2 0x0f0e /* unused */
#define AC_VERB_GET_VOLUME_KNOB_CONTROL 0x0f0f
-/* f10-f1a: GPIO */
+/* f10-f1a: GPI/GPO/GPIO */
+#define AC_VERB_GET_GPI_DATA 0x0f10
+#define AC_VERB_GET_GPI_WAKE_MASK 0x0f11
+#define AC_VERB_GET_GPI_UNSOLICITED_RSP_MASK 0x0f12
+#define AC_VERB_GET_GPI_STICKY_MASK 0x0f13
+#define AC_VERB_GET_GPO_DATA 0x0f14
#define AC_VERB_GET_GPIO_DATA 0x0f15
#define AC_VERB_GET_GPIO_MASK 0x0f16
#define AC_VERB_GET_GPIO_DIRECTION 0x0f17
diff --git a/sound/hda/common/proc.c b/sound/hda/common/proc.c
index 3bc33c5617b2..c83796b13d3d 100644
--- a/sound/hda/common/proc.c
+++ b/sound/hda/common/proc.c
@@ -640,41 +640,78 @@ static void print_gpio(struct snd_info_buffer *buffer,
{
unsigned int gpio =
param_read(codec, codec->core.afg, AC_PAR_GPIO_CAP);
- unsigned int enable, direction, wake, unsol, sticky, data;
- int i, max;
+ int i, gpio_max, gpo_max, gpi_max;
+
+ gpio_max = gpio & AC_GPIO_IO_COUNT;
+ gpo_max = (gpio & AC_GPIO_O_COUNT) >> AC_GPIO_O_COUNT_SHIFT;
+ gpi_max = (gpio & AC_GPIO_I_COUNT) >> AC_GPIO_I_COUNT_SHIFT;
+
snd_iprintf(buffer, "GPIO: io=%d, o=%d, i=%d, "
"unsolicited=%d, wake=%d\n",
- gpio & AC_GPIO_IO_COUNT,
- (gpio & AC_GPIO_O_COUNT) >> AC_GPIO_O_COUNT_SHIFT,
- (gpio & AC_GPIO_I_COUNT) >> AC_GPIO_I_COUNT_SHIFT,
+ gpio_max, gpo_max, gpi_max,
(gpio & AC_GPIO_UNSOLICITED) ? 1 : 0,
(gpio & AC_GPIO_WAKE) ? 1 : 0);
- max = gpio & AC_GPIO_IO_COUNT;
- if (!max || max > 8)
- return;
- enable = snd_hda_codec_read(codec, nid, 0,
- AC_VERB_GET_GPIO_MASK, 0);
- direction = snd_hda_codec_read(codec, nid, 0,
- AC_VERB_GET_GPIO_DIRECTION, 0);
- wake = snd_hda_codec_read(codec, nid, 0,
- AC_VERB_GET_GPIO_WAKE_MASK, 0);
- unsol = snd_hda_codec_read(codec, nid, 0,
- AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK, 0);
- sticky = snd_hda_codec_read(codec, nid, 0,
- AC_VERB_GET_GPIO_STICKY_MASK, 0);
- data = snd_hda_codec_read(codec, nid, 0,
- AC_VERB_GET_GPIO_DATA, 0);
- for (i = 0; i < max; ++i)
- snd_iprintf(buffer,
- " IO[%d]: enable=%d, dir=%d, wake=%d, "
- "sticky=%d, data=%d, unsol=%d\n", i,
- (enable & (1<<i)) ? 1 : 0,
- (direction & (1<<i)) ? 1 : 0,
- (wake & (1<<i)) ? 1 : 0,
- (sticky & (1<<i)) ? 1 : 0,
- (data & (1<<i)) ? 1 : 0,
- (unsol & (1<<i)) ? 1 : 0);
- /* FIXME: add GPO and GPI pin information */
+
+ if (gpio_max && gpio_max <= 8) {
+ unsigned int enable, direction, wake, unsol, sticky, data;
+
+ enable = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_MASK, 0);
+ direction = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_DIRECTION, 0);
+ wake = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_WAKE_MASK, 0);
+ unsol = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK,
+ 0);
+ sticky = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_STICKY_MASK, 0);
+ data = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_DATA, 0);
+ for (i = 0; i < gpio_max; ++i) {
+ snd_iprintf(buffer,
+ " IO[%d]: enable=%d, dir=%d, wake=%d, ",
+ i, (enable & (1 << i)) ? 1 : 0,
+ (direction & (1 << i)) ? 1 : 0,
+ (wake & (1 << i)) ? 1 : 0);
+ snd_iprintf(buffer,
+ "sticky=%d, data=%d, unsol=%d\n",
+ (sticky & (1 << i)) ? 1 : 0,
+ (data & (1 << i)) ? 1 : 0,
+ (unsol & (1 << i)) ? 1 : 0);
+ }
+ }
+
+ if (gpo_max && gpo_max <= 8) {
+ unsigned int gpo_data;
+
+ gpo_data = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPO_DATA, 0);
+ for (i = 0; i < gpo_max; ++i)
+ snd_iprintf(buffer, " GPO[%d]: data=%d\n", i,
+ (gpo_data & (1 << i)) ? 1 : 0);
+ }
+
+ if (gpi_max && gpi_max <= 8) {
+ unsigned int wake, unsol, sticky, data;
+
+ wake = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPI_WAKE_MASK, 0);
+ unsol = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPI_UNSOLICITED_RSP_MASK,
+ 0);
+ sticky = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPI_STICKY_MASK, 0);
+ data = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPI_DATA, 0);
+ for (i = 0; i < gpi_max; ++i)
+ snd_iprintf(buffer, " GPI[%d]: wake=%d, sticky=%d, data=%d, unsol=%d\n",
+ i, (wake & (1 << i)) ? 1 : 0,
+ (sticky & (1 << i)) ? 1 : 0,
+ (data & (1 << i)) ? 1 : 0,
+ (unsol & (1 << i)) ? 1 : 0);
+ }
+
print_nid_array(buffer, codec, nid, &codec->mixers);
print_nid_array(buffer, codec, nid, &codec->nids);
}
@@ -940,4 +977,3 @@ int snd_hda_codec_proc_new(struct hda_codec *codec)
snprintf(name, sizeof(name), "codec#%d", codec->core.addr);
return snd_card_ro_proc_new(codec->card, name, codec, print_codec_info);
}
-

---
base-commit: 0ce5115cb8f38e23044b04644fce7661336c1efc
change-id: 20260328-hda-proc-gpi-gpo-950a518e7ac6

Best regards,
--
Cássio Gabriel <cassiogabrielcontato@xxxxxxxxx>