thinkpad_helper.c 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. /* Helper functions for Thinkpad LED control;
  2. * to be included from codec driver
  3. */
  4. #if IS_ENABLED(CONFIG_THINKPAD_ACPI)
  5. #include <linux/acpi.h>
  6. #include <linux/thinkpad_acpi.h>
  7. static int (*led_set_func)(int, bool);
  8. static void (*old_vmaster_hook)(void *, int);
  9. static acpi_status acpi_check_cb(acpi_handle handle, u32 lvl, void *context,
  10. void **rv)
  11. {
  12. bool *found = context;
  13. *found = true;
  14. return AE_OK;
  15. }
  16. static bool is_thinkpad(struct hda_codec *codec)
  17. {
  18. bool found = false;
  19. if (codec->core.subsystem_id >> 16 != 0x17aa)
  20. return false;
  21. if (ACPI_SUCCESS(acpi_get_devices("LEN0068", acpi_check_cb, &found, NULL)) && found)
  22. return true;
  23. found = false;
  24. return ACPI_SUCCESS(acpi_get_devices("IBM0068", acpi_check_cb, &found, NULL)) && found;
  25. }
  26. static void update_tpacpi_mute_led(void *private_data, int enabled)
  27. {
  28. if (old_vmaster_hook)
  29. old_vmaster_hook(private_data, enabled);
  30. if (led_set_func)
  31. led_set_func(TPACPI_LED_MUTE, !enabled);
  32. }
  33. static void update_tpacpi_micmute_led(struct hda_codec *codec,
  34. struct snd_kcontrol *kcontrol,
  35. struct snd_ctl_elem_value *ucontrol)
  36. {
  37. if (!ucontrol || !led_set_func)
  38. return;
  39. if (strcmp("Capture Switch", ucontrol->id.name) == 0 && ucontrol->id.index == 0) {
  40. /* TODO: How do I verify if it's a mono or stereo here? */
  41. bool val = ucontrol->value.integer.value[0] || ucontrol->value.integer.value[1];
  42. led_set_func(TPACPI_LED_MICMUTE, !val);
  43. }
  44. }
  45. static void hda_fixup_thinkpad_acpi(struct hda_codec *codec,
  46. const struct hda_fixup *fix, int action)
  47. {
  48. struct hda_gen_spec *spec = codec->spec;
  49. bool removefunc = false;
  50. if (action == HDA_FIXUP_ACT_PROBE) {
  51. if (!is_thinkpad(codec))
  52. return;
  53. if (!led_set_func)
  54. led_set_func = symbol_request(tpacpi_led_set);
  55. if (!led_set_func) {
  56. codec_warn(codec,
  57. "Failed to find thinkpad-acpi symbol tpacpi_led_set\n");
  58. return;
  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)
  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. if (led_set_func && (action == HDA_FIXUP_ACT_FREE || removefunc)) {
  77. symbol_put(tpacpi_led_set);
  78. led_set_func = NULL;
  79. old_vmaster_hook = NULL;
  80. }
  81. }
  82. #else /* CONFIG_THINKPAD_ACPI */
  83. static void hda_fixup_thinkpad_acpi(struct hda_codec *codec,
  84. const struct hda_fixup *fix, int action)
  85. {
  86. }
  87. #endif /* CONFIG_THINKPAD_ACPI */