xref: /openbmc/linux/sound/pci/hda/thinkpad_helper.c (revision 160b8e75)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Helper functions for Thinkpad LED control;
3  * to be included from codec driver
4  */
5 
6 #if IS_ENABLED(CONFIG_THINKPAD_ACPI)
7 
8 #include <linux/acpi.h>
9 #include <linux/thinkpad_acpi.h>
10 
11 static int (*led_set_func)(int, bool);
12 static void (*old_vmaster_hook)(void *, int);
13 
14 static bool is_thinkpad(struct hda_codec *codec)
15 {
16 	return (codec->core.subsystem_id >> 16 == 0x17aa) &&
17 	       (acpi_dev_found("LEN0068") || acpi_dev_found("LEN0268") ||
18 		acpi_dev_found("IBM0068"));
19 }
20 
21 static void update_tpacpi_mute_led(void *private_data, int enabled)
22 {
23 	if (old_vmaster_hook)
24 		old_vmaster_hook(private_data, enabled);
25 
26 	if (led_set_func)
27 		led_set_func(TPACPI_LED_MUTE, !enabled);
28 }
29 
30 static void update_tpacpi_micmute_led(struct hda_codec *codec,
31 				      struct snd_kcontrol *kcontrol,
32 				      struct snd_ctl_elem_value *ucontrol)
33 {
34 	if (!ucontrol || !led_set_func)
35 		return;
36 	if (strcmp("Capture Switch", ucontrol->id.name) == 0 && ucontrol->id.index == 0) {
37 		/* TODO: How do I verify if it's a mono or stereo here? */
38 		bool val = ucontrol->value.integer.value[0] || ucontrol->value.integer.value[1];
39 		led_set_func(TPACPI_LED_MICMUTE, !val);
40 	}
41 }
42 
43 static void hda_fixup_thinkpad_acpi(struct hda_codec *codec,
44 				    const struct hda_fixup *fix, int action)
45 {
46 	struct hda_gen_spec *spec = codec->spec;
47 	bool removefunc = false;
48 
49 	if (action == HDA_FIXUP_ACT_PROBE) {
50 		if (!is_thinkpad(codec))
51 			return;
52 		if (!led_set_func)
53 			led_set_func = symbol_request(tpacpi_led_set);
54 		if (!led_set_func) {
55 			codec_warn(codec,
56 				   "Failed to find thinkpad-acpi symbol tpacpi_led_set\n");
57 			return;
58 		}
59 
60 		removefunc = true;
61 		if (led_set_func(TPACPI_LED_MUTE, false) >= 0) {
62 			old_vmaster_hook = spec->vmaster_mute.hook;
63 			spec->vmaster_mute.hook = update_tpacpi_mute_led;
64 			removefunc = false;
65 		}
66 		if (led_set_func(TPACPI_LED_MICMUTE, false) >= 0) {
67 			if (spec->num_adc_nids > 1 && !spec->dyn_adc_switch)
68 				codec_dbg(codec,
69 					  "Skipping micmute LED control due to several ADCs");
70 			else {
71 				spec->cap_sync_hook = update_tpacpi_micmute_led;
72 				removefunc = false;
73 			}
74 		}
75 	}
76 
77 	if (led_set_func && (action == HDA_FIXUP_ACT_FREE || removefunc)) {
78 		symbol_put(tpacpi_led_set);
79 		led_set_func = NULL;
80 		old_vmaster_hook = NULL;
81 	}
82 }
83 
84 #else /* CONFIG_THINKPAD_ACPI */
85 
86 static void hda_fixup_thinkpad_acpi(struct hda_codec *codec,
87 				    const struct hda_fixup *fix, int action)
88 {
89 }
90 
91 #endif /* CONFIG_THINKPAD_ACPI */
92