1
2
3
4
5#if IS_ENABLED(CONFIG_DELL_LAPTOP)
6#include <linux/dell-led.h>
7
8enum {
9 MICMUTE_LED_ON,
10 MICMUTE_LED_OFF,
11 MICMUTE_LED_FOLLOW_CAPTURE,
12 MICMUTE_LED_FOLLOW_MUTE,
13};
14
15static int dell_led_mode = MICMUTE_LED_FOLLOW_MUTE;
16static int dell_capture;
17static int dell_led_value;
18static int (*dell_micmute_led_set_func)(int);
19static void (*dell_old_cap_hook)(struct hda_codec *,
20 struct snd_kcontrol *,
21 struct snd_ctl_elem_value *);
22
23static void call_micmute_led_update(void)
24{
25 int val;
26
27 switch (dell_led_mode) {
28 case MICMUTE_LED_ON:
29 val = 1;
30 break;
31 case MICMUTE_LED_OFF:
32 val = 0;
33 break;
34 case MICMUTE_LED_FOLLOW_CAPTURE:
35 val = dell_capture;
36 break;
37 case MICMUTE_LED_FOLLOW_MUTE:
38 default:
39 val = !dell_capture;
40 break;
41 }
42
43 if (val == dell_led_value)
44 return;
45 dell_led_value = val;
46 dell_micmute_led_set_func(dell_led_value);
47}
48
49static void update_dell_wmi_micmute_led(struct hda_codec *codec,
50 struct snd_kcontrol *kcontrol,
51 struct snd_ctl_elem_value *ucontrol)
52{
53 if (dell_old_cap_hook)
54 dell_old_cap_hook(codec, kcontrol, ucontrol);
55
56 if (!ucontrol || !dell_micmute_led_set_func)
57 return;
58 if (strcmp("Capture Switch", ucontrol->id.name) == 0 && ucontrol->id.index == 0) {
59
60 dell_capture = (ucontrol->value.integer.value[0] ||
61 ucontrol->value.integer.value[1]);
62 call_micmute_led_update();
63 }
64}
65
66static int dell_mic_mute_led_mode_info(struct snd_kcontrol *kcontrol,
67 struct snd_ctl_elem_info *uinfo)
68{
69 static const char * const texts[] = {
70 "On", "Off", "Follow Capture", "Follow Mute",
71 };
72
73 return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts);
74}
75
76static int dell_mic_mute_led_mode_get(struct snd_kcontrol *kcontrol,
77 struct snd_ctl_elem_value *ucontrol)
78{
79 ucontrol->value.enumerated.item[0] = dell_led_mode;
80 return 0;
81}
82
83static int dell_mic_mute_led_mode_put(struct snd_kcontrol *kcontrol,
84 struct snd_ctl_elem_value *ucontrol)
85{
86 unsigned int mode;
87
88 mode = ucontrol->value.enumerated.item[0];
89 if (mode > MICMUTE_LED_FOLLOW_MUTE)
90 mode = MICMUTE_LED_FOLLOW_MUTE;
91 if (mode == dell_led_mode)
92 return 0;
93 dell_led_mode = mode;
94 call_micmute_led_update();
95 return 1;
96}
97
98static const struct snd_kcontrol_new dell_mic_mute_mode_ctls[] = {
99 {
100 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
101 .name = "Mic Mute-LED Mode",
102 .info = dell_mic_mute_led_mode_info,
103 .get = dell_mic_mute_led_mode_get,
104 .put = dell_mic_mute_led_mode_put,
105 },
106 {}
107};
108
109static void alc_fixup_dell_wmi(struct hda_codec *codec,
110 const struct hda_fixup *fix, int action)
111{
112 struct alc_spec *spec = codec->spec;
113 bool removefunc = false;
114
115 if (action == HDA_FIXUP_ACT_PROBE) {
116 if (!dell_micmute_led_set_func)
117 dell_micmute_led_set_func = symbol_request(dell_micmute_led_set);
118 if (!dell_micmute_led_set_func) {
119 codec_warn(codec, "Failed to find dell wmi symbol dell_micmute_led_set\n");
120 return;
121 }
122
123 removefunc = true;
124 if (dell_micmute_led_set_func(false) >= 0) {
125 dell_led_value = 0;
126 if (spec->gen.num_adc_nids > 1 && !spec->gen.dyn_adc_switch)
127 codec_dbg(codec, "Skipping micmute LED control due to several ADCs");
128 else {
129 dell_old_cap_hook = spec->gen.cap_sync_hook;
130 spec->gen.cap_sync_hook = update_dell_wmi_micmute_led;
131 removefunc = false;
132 add_mixer(spec, dell_mic_mute_mode_ctls);
133 }
134 }
135
136 }
137
138 if (dell_micmute_led_set_func && (action == HDA_FIXUP_ACT_FREE || removefunc)) {
139 symbol_put(dell_micmute_led_set);
140 dell_micmute_led_set_func = NULL;
141 dell_old_cap_hook = NULL;
142 }
143}
144
145#else
146static void alc_fixup_dell_wmi(struct hda_codec *codec,
147 const struct hda_fixup *fix, int action)
148{
149}
150
151#endif
152