| 
									
										
										
										
											2014-01-08 11:44:21 +01:00
										 |  |  | /* Helper functions for Thinkpad LED control;
 | 
					
						
							|  |  |  |  * to be included from codec driver | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #if IS_ENABLED(CONFIG_THINKPAD_ACPI)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/acpi.h>
 | 
					
						
							|  |  |  | #include <linux/thinkpad_acpi.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int (*led_set_func)(int, bool); | 
					
						
							| 
									
										
										
										
											2014-01-14 14:56:55 +01:00
										 |  |  | static void (*old_vmaster_hook)(void *, int); | 
					
						
							| 
									
										
										
										
											2014-01-08 11:44:21 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | static acpi_status acpi_check_cb(acpi_handle handle, u32 lvl, void *context, | 
					
						
							|  |  |  | 				 void **rv) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	bool *found = context; | 
					
						
							|  |  |  | 	*found = true; | 
					
						
							|  |  |  | 	return AE_OK; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static bool is_thinkpad(struct hda_codec *codec) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	bool found = false; | 
					
						
							|  |  |  | 	if (codec->subsystem_id >> 16 != 0x17aa) | 
					
						
							|  |  |  | 		return false; | 
					
						
							|  |  |  | 	if (ACPI_SUCCESS(acpi_get_devices("LEN0068", acpi_check_cb, &found, NULL)) && found) | 
					
						
							|  |  |  | 		return true; | 
					
						
							|  |  |  | 	found = false; | 
					
						
							|  |  |  | 	return ACPI_SUCCESS(acpi_get_devices("IBM0068", acpi_check_cb, &found, NULL)) && found; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void update_tpacpi_mute_led(void *private_data, int enabled) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2014-01-14 14:56:55 +01:00
										 |  |  | 	if (old_vmaster_hook) | 
					
						
							|  |  |  | 		old_vmaster_hook(private_data, enabled); | 
					
						
							| 
									
										
										
										
											2014-01-08 11:44:21 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if (led_set_func) | 
					
						
							|  |  |  | 		led_set_func(TPACPI_LED_MUTE, !enabled); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void update_tpacpi_micmute_led(struct hda_codec *codec, | 
					
						
							| 
									
										
										
										
											2014-01-30 17:59:02 +01:00
										 |  |  | 				      struct snd_kcontrol *kcontrol, | 
					
						
							| 
									
										
										
										
											2014-01-08 11:44:21 +01:00
										 |  |  | 				      struct snd_ctl_elem_value *ucontrol) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (!ucontrol || !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? */ | 
					
						
							|  |  |  | 		bool val = ucontrol->value.integer.value[0] || ucontrol->value.integer.value[1]; | 
					
						
							|  |  |  | 		led_set_func(TPACPI_LED_MICMUTE, !val); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void hda_fixup_thinkpad_acpi(struct hda_codec *codec, | 
					
						
							|  |  |  | 				    const struct hda_fixup *fix, int action) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct hda_gen_spec *spec = codec->spec; | 
					
						
							|  |  |  | 	bool removefunc = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (action == HDA_FIXUP_ACT_PROBE) { | 
					
						
							|  |  |  | 		if (!is_thinkpad(codec)) | 
					
						
							|  |  |  | 			return; | 
					
						
							|  |  |  | 		if (!led_set_func) | 
					
						
							|  |  |  | 			led_set_func = symbol_request(tpacpi_led_set); | 
					
						
							|  |  |  | 		if (!led_set_func) { | 
					
						
							| 
									
										
										
										
											2014-02-25 12:21:03 +01:00
										 |  |  | 			codec_warn(codec, | 
					
						
							|  |  |  | 				   "Failed to find thinkpad-acpi symbol tpacpi_led_set\n"); | 
					
						
							| 
									
										
										
										
											2014-01-08 11:44:21 +01:00
										 |  |  | 			return; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		removefunc = true; | 
					
						
							|  |  |  | 		if (led_set_func(TPACPI_LED_MUTE, false) >= 0) { | 
					
						
							| 
									
										
										
										
											2014-01-14 14:56:55 +01:00
										 |  |  | 			old_vmaster_hook = spec->vmaster_mute.hook; | 
					
						
							| 
									
										
										
										
											2014-01-08 11:44:21 +01:00
										 |  |  | 			spec->vmaster_mute.hook = update_tpacpi_mute_led; | 
					
						
							|  |  |  | 			removefunc = false; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (led_set_func(TPACPI_LED_MICMUTE, false) >= 0) { | 
					
						
							|  |  |  | 			if (spec->num_adc_nids > 1) | 
					
						
							| 
									
										
										
										
											2014-02-25 12:21:03 +01:00
										 |  |  | 				codec_dbg(codec, | 
					
						
							|  |  |  | 					  "Skipping micmute LED control due to several ADCs"); | 
					
						
							| 
									
										
										
										
											2014-01-08 11:44:21 +01:00
										 |  |  | 			else { | 
					
						
							|  |  |  | 				spec->cap_sync_hook = update_tpacpi_micmute_led; | 
					
						
							|  |  |  | 				removefunc = false; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (led_set_func && (action == HDA_FIXUP_ACT_FREE || removefunc)) { | 
					
						
							|  |  |  | 		symbol_put(tpacpi_led_set); | 
					
						
							|  |  |  | 		led_set_func = NULL; | 
					
						
							| 
									
										
										
										
											2014-01-14 14:56:55 +01:00
										 |  |  | 		old_vmaster_hook = NULL; | 
					
						
							| 
									
										
										
										
											2014-01-08 11:44:21 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #else /* CONFIG_THINKPAD_ACPI */
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void hda_fixup_thinkpad_acpi(struct hda_codec *codec, | 
					
						
							|  |  |  | 				    const struct hda_fixup *fix, int action) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #endif /* CONFIG_THINKPAD_ACPI */
 |