summaryrefslogtreecommitdiffstats
path: root/sound/pci/hda/dell_wmi_helper.c
blob: 44b1e15682b92ca6e794b19c50dce3780921ed7d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/* Helper functions for Dell Mic Mute LED control;
 * to be included from codec driver
 */

#if IS_ENABLED(CONFIG_DELL_LAPTOP)
#include <linux/dell-led.h>

enum {
	MICMUTE_LED_ON,
	MICMUTE_LED_OFF,
	MICMUTE_LED_FOLLOW_CAPTURE,
	MICMUTE_LED_FOLLOW_MUTE,
};

static int dell_led_mode = MICMUTE_LED_FOLLOW_MUTE;
static int dell_capture;
static int dell_led_value;
static int (*dell_micmute_led_set_func)(int);
static void (*dell_old_cap_hook)(struct hda_codec *,
			         struct snd_kcontrol *,
				 struct snd_ctl_elem_value *);

static void call_micmute_led_update(void)
{
	int val;

	switch (dell_led_mode) {
	case MICMUTE_LED_ON:
		val = 1;
		break;
	case MICMUTE_LED_OFF:
		val = 0;
		break;
	case MICMUTE_LED_FOLLOW_CAPTURE:
		val = dell_capture;
		break;
	case MICMUTE_LED_FOLLOW_MUTE:
	default:
		val = !dell_capture;
		break;
	}

	if (val == dell_led_value)
		return;
	dell_led_value = val;
	dell_micmute_led_set_func(dell_led_value);
}

static void update_dell_wmi_micmute_led(struct hda_codec *codec,
				        struct snd_kcontrol *kcontrol,
					struct snd_ctl_elem_value *ucontrol)
{
	if (dell_old_cap_hook)
		dell_old_cap_hook(codec, kcontrol, ucontrol);

	if (!ucontrol || !dell_micmute_led_set_func)
		return;
	if (strcmp("Capture Switch", ucontrol->id.name) == 0 && ucontrol->id.index == 0) {
		/* TODO: How do I verify if it's a mono or stereo here? */
		dell_capture = (ucontrol->value.integer.value[0] ||
				ucontrol->value.integer.value[1]);
		call_micmute_led_update();
	}
}

static int dell_mic_mute_led_mode_info(struct snd_kcontrol *kcontrol,
				       struct snd_ctl_elem_info *uinfo)
{
	static const char * const texts[] = {
		"On", "Off", "Follow Capture", "Follow Mute",
	};

	return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts);
}

static int dell_mic_mute_led_mode_get(struct snd_kcontrol *kcontrol,
				      struct snd_ctl_elem_value *ucontrol)
{
	ucontrol->value.enumerated.item[0] = dell_led_mode;
	return 0;
}

static int dell_mic_mute_led_mode_put(struct snd_kcontrol *kcontrol,
				      struct snd_ctl_elem_value *ucontrol)
{
	unsigned int mode;

	mode = ucontrol->value.enumerated.item[0];
	if (mode > MICMUTE_LED_FOLLOW_MUTE)
		mode = MICMUTE_LED_FOLLOW_MUTE;
	if (mode == dell_led_mode)
		return 0;
	dell_led_mode = mode;
	call_micmute_led_update();
	return 1;
}

static const struct snd_kcontrol_new dell_mic_mute_mode_ctls[] = {
	{
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.name = "Mic Mute-LED Mode",
		.info = dell_mic_mute_led_mode_info,
		.get = dell_mic_mute_led_mode_get,
		.put = dell_mic_mute_led_mode_put,
	},
	{}
};

static void alc_fixup_dell_wmi(struct hda_codec *codec,
			       const struct hda_fixup *fix, int action)
{
	struct alc_spec *spec = codec->spec;
	bool removefunc = false;

	if (action == HDA_FIXUP_ACT_PROBE) {
		if (!dell_micmute_led_set_func)
			dell_micmute_led_set_func = symbol_request(dell_micmute_led_set);
		if (!dell_micmute_led_set_func) {
			codec_warn(codec, "Failed to find dell wmi symbol dell_micmute_led_set\n");
			return;
		}

		removefunc = true;
		if (dell_micmute_led_set_func(false) >= 0) {
			dell_led_value = 0;
			if (spec->gen.num_adc_nids > 1 && !spec->gen.dyn_adc_switch)
				codec_dbg(codec, "Skipping micmute LED control due to several ADCs");
			else {
				dell_old_cap_hook = spec->gen.cap_sync_hook;
				spec->gen.cap_sync_hook = update_dell_wmi_micmute_led;
				removefunc = false;
				add_mixer(spec, dell_mic_mute_mode_ctls);
			}
		}

	}

	if (dell_micmute_led_set_func && (action == HDA_FIXUP_ACT_FREE || removefunc)) {
		symbol_put(dell_micmute_led_set);
		dell_micmute_led_set_func = NULL;
		dell_old_cap_hook = NULL;
	}
}

#else /* CONFIG_DELL_LAPTOP */
static void alc_fixup_dell_wmi(struct hda_codec *codec,
			       const struct hda_fixup *fix, int action)
{
}

#endif /* CONFIG_DELL_LAPTOP */