diff options
| author | Pali Rohár <pali.rohar@gmail.com> | 2015-12-30 17:27:41 -0500 |
|---|---|---|
| committer | Darren Hart <dvhart@linux.intel.com> | 2016-01-19 20:35:49 -0500 |
| commit | bb28f3d51ff5e1be541d057708011cc1efe6fae9 (patch) | |
| tree | bcfb5bf417333f905fd76a92a6ee6d6f71d2ce27 /drivers | |
| parent | 481fe5be821c3d04f986e4061de42e1209a62374 (diff) | |
thinkpad_acpi: Add support for keyboard backlight
This patch adds support for controlling keyboard backlight via standard
linux led class interface (::kbd_backlight). It uses ACPI HKEY device with
MLCG and MLCS methods.
Signed-off-by: Pali Rohár <pali.rohar@gmail.com>
Tested-by: Fabio D'Urso <fabiodurso@hotmail.it>
Acked-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Darren Hart <dvhart@linux.intel.com>
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/platform/x86/thinkpad_acpi.c | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 0bed4733c4f0..a268a7abf8ab 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c | |||
| @@ -303,6 +303,7 @@ static struct { | |||
| 303 | u32 hotkey_mask:1; | 303 | u32 hotkey_mask:1; |
| 304 | u32 hotkey_wlsw:1; | 304 | u32 hotkey_wlsw:1; |
| 305 | u32 hotkey_tablet:1; | 305 | u32 hotkey_tablet:1; |
| 306 | u32 kbdlight:1; | ||
| 306 | u32 light:1; | 307 | u32 light:1; |
| 307 | u32 light_status:1; | 308 | u32 light_status:1; |
| 308 | u32 bright_acpimode:1; | 309 | u32 bright_acpimode:1; |
| @@ -4986,6 +4987,207 @@ static struct ibm_struct video_driver_data = { | |||
| 4986 | #endif /* CONFIG_THINKPAD_ACPI_VIDEO */ | 4987 | #endif /* CONFIG_THINKPAD_ACPI_VIDEO */ |
| 4987 | 4988 | ||
| 4988 | /************************************************************************* | 4989 | /************************************************************************* |
| 4990 | * Keyboard backlight subdriver | ||
| 4991 | */ | ||
| 4992 | |||
| 4993 | static int kbdlight_set_level(int level) | ||
| 4994 | { | ||
| 4995 | if (!hkey_handle) | ||
| 4996 | return -ENXIO; | ||
| 4997 | |||
| 4998 | if (!acpi_evalf(hkey_handle, NULL, "MLCS", "dd", level)) | ||
| 4999 | return -EIO; | ||
| 5000 | |||
| 5001 | return 0; | ||
| 5002 | } | ||
| 5003 | |||
| 5004 | static int kbdlight_get_level(void) | ||
| 5005 | { | ||
| 5006 | int status = 0; | ||
| 5007 | |||
| 5008 | if (!hkey_handle) | ||
| 5009 | return -ENXIO; | ||
| 5010 | |||
| 5011 | if (!acpi_evalf(hkey_handle, &status, "MLCG", "dd", 0)) | ||
| 5012 | return -EIO; | ||
| 5013 | |||
| 5014 | if (status < 0) | ||
| 5015 | return status; | ||
| 5016 | |||
| 5017 | return status & 0x3; | ||
| 5018 | } | ||
| 5019 | |||
| 5020 | static bool kbdlight_is_supported(void) | ||
| 5021 | { | ||
| 5022 | int status = 0; | ||
| 5023 | |||
| 5024 | if (!hkey_handle) | ||
| 5025 | return false; | ||
| 5026 | |||
| 5027 | if (!acpi_has_method(hkey_handle, "MLCG")) { | ||
| 5028 | vdbg_printk(TPACPI_DBG_INIT, "kbdlight MLCG is unavailable\n"); | ||
| 5029 | return false; | ||
| 5030 | } | ||
| 5031 | |||
| 5032 | if (!acpi_evalf(hkey_handle, &status, "MLCG", "qdd", 0)) { | ||
| 5033 | vdbg_printk(TPACPI_DBG_INIT, "kbdlight MLCG failed\n"); | ||
| 5034 | return false; | ||
| 5035 | } | ||
| 5036 | |||
| 5037 | if (status < 0) { | ||
| 5038 | vdbg_printk(TPACPI_DBG_INIT, "kbdlight MLCG err: %d\n", status); | ||
| 5039 | return false; | ||
| 5040 | } | ||
| 5041 | |||
| 5042 | vdbg_printk(TPACPI_DBG_INIT, "kbdlight MLCG returned 0x%x\n", status); | ||
| 5043 | /* | ||
| 5044 | * Guessed test for keyboard backlight: | ||
| 5045 | * | ||
| 5046 | * Machines with backlight keyboard return: | ||
| 5047 | * b010100000010000000XX - ThinkPad X1 Carbon 3rd | ||
| 5048 | * b110100010010000000XX - ThinkPad x230 | ||
| 5049 | * b010100000010000000XX - ThinkPad x240 | ||
| 5050 | * b010100000010000000XX - ThinkPad W541 | ||
| 5051 | * (XX is current backlight level) | ||
| 5052 | * | ||
| 5053 | * Machines without backlight keyboard return: | ||
| 5054 | * b10100001000000000000 - ThinkPad x230 | ||
| 5055 | * b10110001000000000000 - ThinkPad E430 | ||
| 5056 | * b00000000000000000000 - ThinkPad E450 | ||
| 5057 | * | ||
| 5058 | * Candidate BITs for detection test (XOR): | ||
| 5059 | * b01000000001000000000 | ||
| 5060 | * ^ | ||
| 5061 | */ | ||
| 5062 | return status & BIT(9); | ||
| 5063 | } | ||
| 5064 | |||
| 5065 | static void kbdlight_set_worker(struct work_struct *work) | ||
| 5066 | { | ||
| 5067 | struct tpacpi_led_classdev *data = | ||
| 5068 | container_of(work, struct tpacpi_led_classdev, work); | ||
| 5069 | |||
| 5070 | if (likely(tpacpi_lifecycle == TPACPI_LIFE_RUNNING)) | ||
| 5071 | kbdlight_set_level(data->new_state); | ||
| 5072 | } | ||
| 5073 | |||
| 5074 | static void kbdlight_sysfs_set(struct led_classdev *led_cdev, | ||
| 5075 | enum led_brightness brightness) | ||
| 5076 | { | ||
| 5077 | struct tpacpi_led_classdev *data = | ||
| 5078 | container_of(led_cdev, | ||
| 5079 | struct tpacpi_led_classdev, | ||
| 5080 | led_classdev); | ||
| 5081 | data->new_state = brightness; | ||
| 5082 | queue_work(tpacpi_wq, &data->work); | ||
| 5083 | } | ||
| 5084 | |||
| 5085 | static enum led_brightness kbdlight_sysfs_get(struct led_classdev *led_cdev) | ||
| 5086 | { | ||
| 5087 | int level; | ||
| 5088 | |||
| 5089 | level = kbdlight_get_level(); | ||
| 5090 | if (level < 0) | ||
| 5091 | return 0; | ||
| 5092 | |||
| 5093 | return level; | ||
| 5094 | } | ||
| 5095 | |||
| 5096 | static struct tpacpi_led_classdev tpacpi_led_kbdlight = { | ||
| 5097 | .led_classdev = { | ||
| 5098 | .name = "tpacpi::kbd_backlight", | ||
| 5099 | .max_brightness = 2, | ||
| 5100 | .brightness_set = &kbdlight_sysfs_set, | ||
| 5101 | .brightness_get = &kbdlight_sysfs_get, | ||
| 5102 | .flags = LED_CORE_SUSPENDRESUME, | ||
| 5103 | } | ||
| 5104 | }; | ||
| 5105 | |||
| 5106 | static int __init kbdlight_init(struct ibm_init_struct *iibm) | ||
| 5107 | { | ||
| 5108 | int rc; | ||
| 5109 | |||
| 5110 | vdbg_printk(TPACPI_DBG_INIT, "initializing kbdlight subdriver\n"); | ||
| 5111 | |||
| 5112 | TPACPI_ACPIHANDLE_INIT(hkey); | ||
| 5113 | INIT_WORK(&tpacpi_led_kbdlight.work, kbdlight_set_worker); | ||
| 5114 | |||
| 5115 | if (!kbdlight_is_supported()) { | ||
| 5116 | tp_features.kbdlight = 0; | ||
| 5117 | vdbg_printk(TPACPI_DBG_INIT, "kbdlight is unsupported\n"); | ||
| 5118 | return 1; | ||
| 5119 | } | ||
| 5120 | |||
| 5121 | tp_features.kbdlight = 1; | ||
| 5122 | |||
| 5123 | rc = led_classdev_register(&tpacpi_pdev->dev, | ||
| 5124 | &tpacpi_led_kbdlight.led_classdev); | ||
| 5125 | if (rc < 0) { | ||
| 5126 | tp_features.kbdlight = 0; | ||
| 5127 | return rc; | ||
| 5128 | } | ||
| 5129 | |||
| 5130 | return 0; | ||
| 5131 | } | ||
| 5132 | |||
| 5133 | static void kbdlight_exit(void) | ||
| 5134 | { | ||
| 5135 | if (tp_features.kbdlight) | ||
| 5136 | led_classdev_unregister(&tpacpi_led_kbdlight.led_classdev); | ||
| 5137 | flush_workqueue(tpacpi_wq); | ||
| 5138 | } | ||
| 5139 | |||
| 5140 | static int kbdlight_read(struct seq_file *m) | ||
| 5141 | { | ||
| 5142 | int level; | ||
| 5143 | |||
| 5144 | if (!tp_features.kbdlight) { | ||
| 5145 | seq_printf(m, "status:\t\tnot supported\n"); | ||
| 5146 | } else { | ||
| 5147 | level = kbdlight_get_level(); | ||
| 5148 | if (level < 0) | ||
| 5149 | seq_printf(m, "status:\t\terror %d\n", level); | ||
| 5150 | else | ||
| 5151 | seq_printf(m, "status:\t\t%d\n", level); | ||
| 5152 | seq_printf(m, "commands:\t0, 1, 2\n"); | ||
| 5153 | } | ||
| 5154 | |||
| 5155 | return 0; | ||
| 5156 | } | ||
| 5157 | |||
| 5158 | static int kbdlight_write(char *buf) | ||
| 5159 | { | ||
| 5160 | char *cmd; | ||
| 5161 | int level = -1; | ||
| 5162 | |||
| 5163 | if (!tp_features.kbdlight) | ||
| 5164 | return -ENODEV; | ||
| 5165 | |||
| 5166 | while ((cmd = next_cmd(&buf))) { | ||
| 5167 | if (strlencmp(cmd, "0") == 0) | ||
| 5168 | level = 0; | ||
| 5169 | else if (strlencmp(cmd, "1") == 0) | ||
| 5170 | level = 1; | ||
| 5171 | else if (strlencmp(cmd, "2") == 0) | ||
| 5172 | level = 2; | ||
| 5173 | else | ||
| 5174 | return -EINVAL; | ||
| 5175 | } | ||
| 5176 | |||
| 5177 | if (level == -1) | ||
| 5178 | return -EINVAL; | ||
| 5179 | |||
| 5180 | return kbdlight_set_level(level); | ||
| 5181 | } | ||
| 5182 | |||
| 5183 | static struct ibm_struct kbdlight_driver_data = { | ||
| 5184 | .name = "kbdlight", | ||
| 5185 | .read = kbdlight_read, | ||
| 5186 | .write = kbdlight_write, | ||
| 5187 | .exit = kbdlight_exit, | ||
| 5188 | }; | ||
| 5189 | |||
| 5190 | /************************************************************************* | ||
| 4989 | * Light (thinklight) subdriver | 5191 | * Light (thinklight) subdriver |
| 4990 | */ | 5192 | */ |
| 4991 | 5193 | ||
| @@ -9207,6 +9409,10 @@ static struct ibm_init_struct ibms_init[] __initdata = { | |||
| 9207 | }, | 9409 | }, |
| 9208 | #endif | 9410 | #endif |
| 9209 | { | 9411 | { |
| 9412 | .init = kbdlight_init, | ||
| 9413 | .data = &kbdlight_driver_data, | ||
| 9414 | }, | ||
| 9415 | { | ||
| 9210 | .init = light_init, | 9416 | .init = light_init, |
| 9211 | .data = &light_driver_data, | 9417 | .data = &light_driver_data, |
| 9212 | }, | 9418 | }, |
