1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * ideapad-laptop.c - Lenovo IdeaPad ACPI Extras |
4 | * |
5 | * Copyright © 2010 Intel Corporation |
6 | * Copyright © 2010 David Woodhouse <dwmw2@infradead.org> |
7 | */ |
8 | |
9 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
10 | |
11 | #include <linux/acpi.h> |
12 | #include <linux/backlight.h> |
13 | #include <linux/bitfield.h> |
14 | #include <linux/bitops.h> |
15 | #include <linux/bug.h> |
16 | #include <linux/debugfs.h> |
17 | #include <linux/device.h> |
18 | #include <linux/dmi.h> |
19 | #include <linux/fb.h> |
20 | #include <linux/i8042.h> |
21 | #include <linux/init.h> |
22 | #include <linux/input.h> |
23 | #include <linux/input/sparse-keymap.h> |
24 | #include <linux/kernel.h> |
25 | #include <linux/leds.h> |
26 | #include <linux/module.h> |
27 | #include <linux/platform_device.h> |
28 | #include <linux/platform_profile.h> |
29 | #include <linux/rfkill.h> |
30 | #include <linux/seq_file.h> |
31 | #include <linux/sysfs.h> |
32 | #include <linux/types.h> |
33 | #include <linux/wmi.h> |
34 | #include "ideapad-laptop.h" |
35 | |
36 | #include <acpi/video.h> |
37 | |
38 | #include <dt-bindings/leds/common.h> |
39 | |
40 | #define IDEAPAD_RFKILL_DEV_NUM 3 |
41 | |
42 | enum { |
43 | CFG_CAP_BT_BIT = 16, |
44 | CFG_CAP_3G_BIT = 17, |
45 | CFG_CAP_WIFI_BIT = 18, |
46 | CFG_CAP_CAM_BIT = 19, |
47 | |
48 | /* |
49 | * These are OnScreenDisplay support bits that can be useful to determine |
50 | * whether a hotkey exists/should show OSD. But they aren't particularly |
51 | * meaningful since they were introduced later, i.e. 2010 IdeaPads |
52 | * don't have these, but they still have had OSD for hotkeys. |
53 | */ |
54 | CFG_OSD_NUMLK_BIT = 27, |
55 | CFG_OSD_CAPSLK_BIT = 28, |
56 | CFG_OSD_MICMUTE_BIT = 29, |
57 | CFG_OSD_TOUCHPAD_BIT = 30, |
58 | CFG_OSD_CAM_BIT = 31, |
59 | }; |
60 | |
61 | enum { |
62 | GBMD_CONSERVATION_STATE_BIT = 5, |
63 | }; |
64 | |
65 | enum { |
66 | SBMC_CONSERVATION_ON = 3, |
67 | SBMC_CONSERVATION_OFF = 5, |
68 | }; |
69 | |
70 | enum { |
71 | HALS_KBD_BL_SUPPORT_BIT = 4, |
72 | HALS_KBD_BL_STATE_BIT = 5, |
73 | HALS_USB_CHARGING_SUPPORT_BIT = 6, |
74 | HALS_USB_CHARGING_STATE_BIT = 7, |
75 | HALS_FNLOCK_SUPPORT_BIT = 9, |
76 | HALS_FNLOCK_STATE_BIT = 10, |
77 | HALS_HOTKEYS_PRIMARY_BIT = 11, |
78 | }; |
79 | |
80 | enum { |
81 | SALS_KBD_BL_ON = 0x8, |
82 | SALS_KBD_BL_OFF = 0x9, |
83 | SALS_USB_CHARGING_ON = 0xa, |
84 | SALS_USB_CHARGING_OFF = 0xb, |
85 | SALS_FNLOCK_ON = 0xe, |
86 | SALS_FNLOCK_OFF = 0xf, |
87 | }; |
88 | |
89 | /* |
90 | * These correspond to the number of supported states - 1 |
91 | * Future keyboard types may need a new system, if there's a collision |
92 | * KBD_BL_TRISTATE_AUTO has no way to report or set the auto state |
93 | * so it effectively has 3 states, but needs to handle 4 |
94 | */ |
95 | enum { |
96 | KBD_BL_STANDARD = 1, |
97 | KBD_BL_TRISTATE = 2, |
98 | KBD_BL_TRISTATE_AUTO = 3, |
99 | }; |
100 | |
101 | #define KBD_BL_QUERY_TYPE 0x1 |
102 | #define KBD_BL_TRISTATE_TYPE 0x5 |
103 | #define KBD_BL_TRISTATE_AUTO_TYPE 0x7 |
104 | |
105 | #define KBD_BL_COMMAND_GET 0x2 |
106 | #define KBD_BL_COMMAND_SET 0x3 |
107 | #define KBD_BL_COMMAND_TYPE GENMASK(7, 4) |
108 | |
109 | #define KBD_BL_GET_BRIGHTNESS GENMASK(15, 1) |
110 | #define KBD_BL_SET_BRIGHTNESS GENMASK(19, 16) |
111 | |
112 | #define KBD_BL_KBLC_CHANGED_EVENT 12 |
113 | |
114 | struct ideapad_dytc_priv { |
115 | enum platform_profile_option current_profile; |
116 | struct platform_profile_handler pprof; |
117 | struct mutex mutex; /* protects the DYTC interface */ |
118 | struct ideapad_private *priv; |
119 | }; |
120 | |
121 | struct ideapad_rfk_priv { |
122 | int dev; |
123 | struct ideapad_private *priv; |
124 | }; |
125 | |
126 | struct ideapad_private { |
127 | struct acpi_device *adev; |
128 | struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM]; |
129 | struct ideapad_rfk_priv rfk_priv[IDEAPAD_RFKILL_DEV_NUM]; |
130 | struct platform_device *platform_device; |
131 | struct input_dev *inputdev; |
132 | struct backlight_device *blightdev; |
133 | struct ideapad_dytc_priv *dytc; |
134 | struct dentry *debug; |
135 | unsigned long cfg; |
136 | unsigned long r_touchpad_val; |
137 | struct { |
138 | bool conservation_mode : 1; |
139 | bool dytc : 1; |
140 | bool fan_mode : 1; |
141 | bool fn_lock : 1; |
142 | bool set_fn_lock_led : 1; |
143 | bool hw_rfkill_switch : 1; |
144 | bool kbd_bl : 1; |
145 | bool touchpad_ctrl_via_ec : 1; |
146 | bool ctrl_ps2_aux_port : 1; |
147 | bool usb_charging : 1; |
148 | } features; |
149 | struct { |
150 | bool initialized; |
151 | int type; |
152 | struct led_classdev led; |
153 | unsigned int last_brightness; |
154 | } kbd_bl; |
155 | }; |
156 | |
157 | static bool no_bt_rfkill; |
158 | module_param(no_bt_rfkill, bool, 0444); |
159 | MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth." ); |
160 | |
161 | static bool allow_v4_dytc; |
162 | module_param(allow_v4_dytc, bool, 0444); |
163 | MODULE_PARM_DESC(allow_v4_dytc, |
164 | "Enable DYTC version 4 platform-profile support. " |
165 | "If you need this please report this to: platform-driver-x86@vger.kernel.org" ); |
166 | |
167 | static bool hw_rfkill_switch; |
168 | module_param(hw_rfkill_switch, bool, 0444); |
169 | MODULE_PARM_DESC(hw_rfkill_switch, |
170 | "Enable rfkill support for laptops with a hw on/off wifi switch/slider. " |
171 | "If you need this please report this to: platform-driver-x86@vger.kernel.org" ); |
172 | |
173 | static bool set_fn_lock_led; |
174 | module_param(set_fn_lock_led, bool, 0444); |
175 | MODULE_PARM_DESC(set_fn_lock_led, |
176 | "Enable driver based updates of the fn-lock LED on fn-lock changes. " |
177 | "If you need this please report this to: platform-driver-x86@vger.kernel.org" ); |
178 | |
179 | static bool ctrl_ps2_aux_port; |
180 | module_param(ctrl_ps2_aux_port, bool, 0444); |
181 | MODULE_PARM_DESC(ctrl_ps2_aux_port, |
182 | "Enable driver based PS/2 aux port en-/dis-abling on touchpad on/off toggle. " |
183 | "If you need this please report this to: platform-driver-x86@vger.kernel.org" ); |
184 | |
185 | static bool touchpad_ctrl_via_ec; |
186 | module_param(touchpad_ctrl_via_ec, bool, 0444); |
187 | MODULE_PARM_DESC(touchpad_ctrl_via_ec, |
188 | "Enable registering a 'touchpad' sysfs-attribute which can be used to manually " |
189 | "tell the EC to enable/disable the touchpad. This may not work on all models." ); |
190 | |
191 | /* |
192 | * shared data |
193 | */ |
194 | |
195 | static struct ideapad_private *ideapad_shared; |
196 | static DEFINE_MUTEX(ideapad_shared_mutex); |
197 | |
198 | static int ideapad_shared_init(struct ideapad_private *priv) |
199 | { |
200 | int ret; |
201 | |
202 | mutex_lock(&ideapad_shared_mutex); |
203 | |
204 | if (!ideapad_shared) { |
205 | ideapad_shared = priv; |
206 | ret = 0; |
207 | } else { |
208 | dev_warn(&priv->adev->dev, "found multiple platform devices\n" ); |
209 | ret = -EINVAL; |
210 | } |
211 | |
212 | mutex_unlock(lock: &ideapad_shared_mutex); |
213 | |
214 | return ret; |
215 | } |
216 | |
217 | static void ideapad_shared_exit(struct ideapad_private *priv) |
218 | { |
219 | mutex_lock(&ideapad_shared_mutex); |
220 | |
221 | if (ideapad_shared == priv) |
222 | ideapad_shared = NULL; |
223 | |
224 | mutex_unlock(lock: &ideapad_shared_mutex); |
225 | } |
226 | |
227 | /* |
228 | * ACPI Helpers |
229 | */ |
230 | |
231 | static int eval_int(acpi_handle handle, const char *name, unsigned long *res) |
232 | { |
233 | unsigned long long result; |
234 | acpi_status status; |
235 | |
236 | status = acpi_evaluate_integer(handle, pathname: (char *)name, NULL, data: &result); |
237 | if (ACPI_FAILURE(status)) |
238 | return -EIO; |
239 | |
240 | *res = result; |
241 | |
242 | return 0; |
243 | } |
244 | |
245 | static int exec_simple_method(acpi_handle handle, const char *name, unsigned long arg) |
246 | { |
247 | acpi_status status = acpi_execute_simple_method(handle, method: (char *)name, arg); |
248 | |
249 | return ACPI_FAILURE(status) ? -EIO : 0; |
250 | } |
251 | |
252 | static int eval_gbmd(acpi_handle handle, unsigned long *res) |
253 | { |
254 | return eval_int(handle, name: "GBMD" , res); |
255 | } |
256 | |
257 | static int exec_sbmc(acpi_handle handle, unsigned long arg) |
258 | { |
259 | return exec_simple_method(handle, name: "SBMC" , arg); |
260 | } |
261 | |
262 | static int eval_hals(acpi_handle handle, unsigned long *res) |
263 | { |
264 | return eval_int(handle, name: "HALS" , res); |
265 | } |
266 | |
267 | static int exec_sals(acpi_handle handle, unsigned long arg) |
268 | { |
269 | return exec_simple_method(handle, name: "SALS" , arg); |
270 | } |
271 | |
272 | static int exec_kblc(acpi_handle handle, unsigned long arg) |
273 | { |
274 | return exec_simple_method(handle, name: "KBLC" , arg); |
275 | } |
276 | |
277 | static int eval_kblc(acpi_handle handle, unsigned long cmd, unsigned long *res) |
278 | { |
279 | return eval_int_with_arg(handle, name: "KBLC" , arg: cmd, res); |
280 | } |
281 | |
282 | static int eval_dytc(acpi_handle handle, unsigned long cmd, unsigned long *res) |
283 | { |
284 | return eval_int_with_arg(handle, name: "DYTC" , arg: cmd, res); |
285 | } |
286 | |
287 | /* |
288 | * debugfs |
289 | */ |
290 | static int debugfs_status_show(struct seq_file *s, void *data) |
291 | { |
292 | struct ideapad_private *priv = s->private; |
293 | unsigned long value; |
294 | |
295 | if (!read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_BL_MAX, data: &value)) |
296 | seq_printf(m: s, fmt: "Backlight max: %lu\n" , value); |
297 | if (!read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_BL, data: &value)) |
298 | seq_printf(m: s, fmt: "Backlight now: %lu\n" , value); |
299 | if (!read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_BL_POWER, data: &value)) |
300 | seq_printf(m: s, fmt: "BL power value: %s (%lu)\n" , value ? "on" : "off" , value); |
301 | |
302 | seq_puts(m: s, s: "=====================\n" ); |
303 | |
304 | if (!read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_RF, data: &value)) |
305 | seq_printf(m: s, fmt: "Radio status: %s (%lu)\n" , value ? "on" : "off" , value); |
306 | if (!read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_WIFI, data: &value)) |
307 | seq_printf(m: s, fmt: "Wifi status: %s (%lu)\n" , value ? "on" : "off" , value); |
308 | if (!read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_BT, data: &value)) |
309 | seq_printf(m: s, fmt: "BT status: %s (%lu)\n" , value ? "on" : "off" , value); |
310 | if (!read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_3G, data: &value)) |
311 | seq_printf(m: s, fmt: "3G status: %s (%lu)\n" , value ? "on" : "off" , value); |
312 | |
313 | seq_puts(m: s, s: "=====================\n" ); |
314 | |
315 | if (!read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_TOUCHPAD, data: &value)) |
316 | seq_printf(m: s, fmt: "Touchpad status: %s (%lu)\n" , value ? "on" : "off" , value); |
317 | if (!read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_CAMERA, data: &value)) |
318 | seq_printf(m: s, fmt: "Camera status: %s (%lu)\n" , value ? "on" : "off" , value); |
319 | |
320 | seq_puts(m: s, s: "=====================\n" ); |
321 | |
322 | if (!eval_gbmd(handle: priv->adev->handle, res: &value)) |
323 | seq_printf(m: s, fmt: "GBMD: %#010lx\n" , value); |
324 | if (!eval_hals(handle: priv->adev->handle, res: &value)) |
325 | seq_printf(m: s, fmt: "HALS: %#010lx\n" , value); |
326 | |
327 | return 0; |
328 | } |
329 | DEFINE_SHOW_ATTRIBUTE(debugfs_status); |
330 | |
331 | static int debugfs_cfg_show(struct seq_file *s, void *data) |
332 | { |
333 | struct ideapad_private *priv = s->private; |
334 | |
335 | seq_printf(m: s, fmt: "_CFG: %#010lx\n\n" , priv->cfg); |
336 | |
337 | seq_puts(m: s, s: "Capabilities:" ); |
338 | if (test_bit(CFG_CAP_BT_BIT, &priv->cfg)) |
339 | seq_puts(m: s, s: " bluetooth" ); |
340 | if (test_bit(CFG_CAP_3G_BIT, &priv->cfg)) |
341 | seq_puts(m: s, s: " 3G" ); |
342 | if (test_bit(CFG_CAP_WIFI_BIT, &priv->cfg)) |
343 | seq_puts(m: s, s: " wifi" ); |
344 | if (test_bit(CFG_CAP_CAM_BIT, &priv->cfg)) |
345 | seq_puts(m: s, s: " camera" ); |
346 | seq_puts(m: s, s: "\n" ); |
347 | |
348 | seq_puts(m: s, s: "OSD support:" ); |
349 | if (test_bit(CFG_OSD_NUMLK_BIT, &priv->cfg)) |
350 | seq_puts(m: s, s: " num-lock" ); |
351 | if (test_bit(CFG_OSD_CAPSLK_BIT, &priv->cfg)) |
352 | seq_puts(m: s, s: " caps-lock" ); |
353 | if (test_bit(CFG_OSD_MICMUTE_BIT, &priv->cfg)) |
354 | seq_puts(m: s, s: " mic-mute" ); |
355 | if (test_bit(CFG_OSD_TOUCHPAD_BIT, &priv->cfg)) |
356 | seq_puts(m: s, s: " touchpad" ); |
357 | if (test_bit(CFG_OSD_CAM_BIT, &priv->cfg)) |
358 | seq_puts(m: s, s: " camera" ); |
359 | seq_puts(m: s, s: "\n" ); |
360 | |
361 | seq_puts(m: s, s: "Graphics: " ); |
362 | switch (priv->cfg & 0x700) { |
363 | case 0x100: |
364 | seq_puts(m: s, s: "Intel" ); |
365 | break; |
366 | case 0x200: |
367 | seq_puts(m: s, s: "ATI" ); |
368 | break; |
369 | case 0x300: |
370 | seq_puts(m: s, s: "Nvidia" ); |
371 | break; |
372 | case 0x400: |
373 | seq_puts(m: s, s: "Intel and ATI" ); |
374 | break; |
375 | case 0x500: |
376 | seq_puts(m: s, s: "Intel and Nvidia" ); |
377 | break; |
378 | } |
379 | seq_puts(m: s, s: "\n" ); |
380 | |
381 | return 0; |
382 | } |
383 | DEFINE_SHOW_ATTRIBUTE(debugfs_cfg); |
384 | |
385 | static void ideapad_debugfs_init(struct ideapad_private *priv) |
386 | { |
387 | struct dentry *dir; |
388 | |
389 | dir = debugfs_create_dir(name: "ideapad" , NULL); |
390 | priv->debug = dir; |
391 | |
392 | debugfs_create_file(name: "cfg" , mode: 0444, parent: dir, data: priv, fops: &debugfs_cfg_fops); |
393 | debugfs_create_file(name: "status" , mode: 0444, parent: dir, data: priv, fops: &debugfs_status_fops); |
394 | } |
395 | |
396 | static void ideapad_debugfs_exit(struct ideapad_private *priv) |
397 | { |
398 | debugfs_remove_recursive(dentry: priv->debug); |
399 | priv->debug = NULL; |
400 | } |
401 | |
402 | /* |
403 | * sysfs |
404 | */ |
405 | static ssize_t camera_power_show(struct device *dev, |
406 | struct device_attribute *attr, |
407 | char *buf) |
408 | { |
409 | struct ideapad_private *priv = dev_get_drvdata(dev); |
410 | unsigned long result; |
411 | int err; |
412 | |
413 | err = read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_CAMERA, data: &result); |
414 | if (err) |
415 | return err; |
416 | |
417 | return sysfs_emit(buf, fmt: "%d\n" , !!result); |
418 | } |
419 | |
420 | static ssize_t camera_power_store(struct device *dev, |
421 | struct device_attribute *attr, |
422 | const char *buf, size_t count) |
423 | { |
424 | struct ideapad_private *priv = dev_get_drvdata(dev); |
425 | bool state; |
426 | int err; |
427 | |
428 | err = kstrtobool(s: buf, res: &state); |
429 | if (err) |
430 | return err; |
431 | |
432 | err = write_ec_cmd(handle: priv->adev->handle, cmd: VPCCMD_W_CAMERA, data: state); |
433 | if (err) |
434 | return err; |
435 | |
436 | return count; |
437 | } |
438 | |
439 | static DEVICE_ATTR_RW(camera_power); |
440 | |
441 | static ssize_t conservation_mode_show(struct device *dev, |
442 | struct device_attribute *attr, |
443 | char *buf) |
444 | { |
445 | struct ideapad_private *priv = dev_get_drvdata(dev); |
446 | unsigned long result; |
447 | int err; |
448 | |
449 | err = eval_gbmd(handle: priv->adev->handle, res: &result); |
450 | if (err) |
451 | return err; |
452 | |
453 | return sysfs_emit(buf, fmt: "%d\n" , !!test_bit(GBMD_CONSERVATION_STATE_BIT, &result)); |
454 | } |
455 | |
456 | static ssize_t conservation_mode_store(struct device *dev, |
457 | struct device_attribute *attr, |
458 | const char *buf, size_t count) |
459 | { |
460 | struct ideapad_private *priv = dev_get_drvdata(dev); |
461 | bool state; |
462 | int err; |
463 | |
464 | err = kstrtobool(s: buf, res: &state); |
465 | if (err) |
466 | return err; |
467 | |
468 | err = exec_sbmc(handle: priv->adev->handle, arg: state ? SBMC_CONSERVATION_ON : SBMC_CONSERVATION_OFF); |
469 | if (err) |
470 | return err; |
471 | |
472 | return count; |
473 | } |
474 | |
475 | static DEVICE_ATTR_RW(conservation_mode); |
476 | |
477 | static ssize_t fan_mode_show(struct device *dev, |
478 | struct device_attribute *attr, |
479 | char *buf) |
480 | { |
481 | struct ideapad_private *priv = dev_get_drvdata(dev); |
482 | unsigned long result; |
483 | int err; |
484 | |
485 | err = read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_FAN, data: &result); |
486 | if (err) |
487 | return err; |
488 | |
489 | return sysfs_emit(buf, fmt: "%lu\n" , result); |
490 | } |
491 | |
492 | static ssize_t fan_mode_store(struct device *dev, |
493 | struct device_attribute *attr, |
494 | const char *buf, size_t count) |
495 | { |
496 | struct ideapad_private *priv = dev_get_drvdata(dev); |
497 | unsigned int state; |
498 | int err; |
499 | |
500 | err = kstrtouint(s: buf, base: 0, res: &state); |
501 | if (err) |
502 | return err; |
503 | |
504 | if (state > 4 || state == 3) |
505 | return -EINVAL; |
506 | |
507 | err = write_ec_cmd(handle: priv->adev->handle, cmd: VPCCMD_W_FAN, data: state); |
508 | if (err) |
509 | return err; |
510 | |
511 | return count; |
512 | } |
513 | |
514 | static DEVICE_ATTR_RW(fan_mode); |
515 | |
516 | static ssize_t fn_lock_show(struct device *dev, |
517 | struct device_attribute *attr, |
518 | char *buf) |
519 | { |
520 | struct ideapad_private *priv = dev_get_drvdata(dev); |
521 | unsigned long hals; |
522 | int err; |
523 | |
524 | err = eval_hals(handle: priv->adev->handle, res: &hals); |
525 | if (err) |
526 | return err; |
527 | |
528 | return sysfs_emit(buf, fmt: "%d\n" , !!test_bit(HALS_FNLOCK_STATE_BIT, &hals)); |
529 | } |
530 | |
531 | static ssize_t fn_lock_store(struct device *dev, |
532 | struct device_attribute *attr, |
533 | const char *buf, size_t count) |
534 | { |
535 | struct ideapad_private *priv = dev_get_drvdata(dev); |
536 | bool state; |
537 | int err; |
538 | |
539 | err = kstrtobool(s: buf, res: &state); |
540 | if (err) |
541 | return err; |
542 | |
543 | err = exec_sals(handle: priv->adev->handle, arg: state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); |
544 | if (err) |
545 | return err; |
546 | |
547 | return count; |
548 | } |
549 | |
550 | static DEVICE_ATTR_RW(fn_lock); |
551 | |
552 | static ssize_t touchpad_show(struct device *dev, |
553 | struct device_attribute *attr, |
554 | char *buf) |
555 | { |
556 | struct ideapad_private *priv = dev_get_drvdata(dev); |
557 | unsigned long result; |
558 | int err; |
559 | |
560 | err = read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_TOUCHPAD, data: &result); |
561 | if (err) |
562 | return err; |
563 | |
564 | priv->r_touchpad_val = result; |
565 | |
566 | return sysfs_emit(buf, fmt: "%d\n" , !!result); |
567 | } |
568 | |
569 | static ssize_t touchpad_store(struct device *dev, |
570 | struct device_attribute *attr, |
571 | const char *buf, size_t count) |
572 | { |
573 | struct ideapad_private *priv = dev_get_drvdata(dev); |
574 | bool state; |
575 | int err; |
576 | |
577 | err = kstrtobool(s: buf, res: &state); |
578 | if (err) |
579 | return err; |
580 | |
581 | err = write_ec_cmd(handle: priv->adev->handle, cmd: VPCCMD_W_TOUCHPAD, data: state); |
582 | if (err) |
583 | return err; |
584 | |
585 | priv->r_touchpad_val = state; |
586 | |
587 | return count; |
588 | } |
589 | |
590 | static DEVICE_ATTR_RW(touchpad); |
591 | |
592 | static ssize_t usb_charging_show(struct device *dev, |
593 | struct device_attribute *attr, |
594 | char *buf) |
595 | { |
596 | struct ideapad_private *priv = dev_get_drvdata(dev); |
597 | unsigned long hals; |
598 | int err; |
599 | |
600 | err = eval_hals(handle: priv->adev->handle, res: &hals); |
601 | if (err) |
602 | return err; |
603 | |
604 | return sysfs_emit(buf, fmt: "%d\n" , !!test_bit(HALS_USB_CHARGING_STATE_BIT, &hals)); |
605 | } |
606 | |
607 | static ssize_t usb_charging_store(struct device *dev, |
608 | struct device_attribute *attr, |
609 | const char *buf, size_t count) |
610 | { |
611 | struct ideapad_private *priv = dev_get_drvdata(dev); |
612 | bool state; |
613 | int err; |
614 | |
615 | err = kstrtobool(s: buf, res: &state); |
616 | if (err) |
617 | return err; |
618 | |
619 | err = exec_sals(handle: priv->adev->handle, arg: state ? SALS_USB_CHARGING_ON : SALS_USB_CHARGING_OFF); |
620 | if (err) |
621 | return err; |
622 | |
623 | return count; |
624 | } |
625 | |
626 | static DEVICE_ATTR_RW(usb_charging); |
627 | |
628 | static struct attribute *ideapad_attributes[] = { |
629 | &dev_attr_camera_power.attr, |
630 | &dev_attr_conservation_mode.attr, |
631 | &dev_attr_fan_mode.attr, |
632 | &dev_attr_fn_lock.attr, |
633 | &dev_attr_touchpad.attr, |
634 | &dev_attr_usb_charging.attr, |
635 | NULL |
636 | }; |
637 | |
638 | static umode_t ideapad_is_visible(struct kobject *kobj, |
639 | struct attribute *attr, |
640 | int idx) |
641 | { |
642 | struct device *dev = kobj_to_dev(kobj); |
643 | struct ideapad_private *priv = dev_get_drvdata(dev); |
644 | bool supported = true; |
645 | |
646 | if (attr == &dev_attr_camera_power.attr) |
647 | supported = test_bit(CFG_CAP_CAM_BIT, &priv->cfg); |
648 | else if (attr == &dev_attr_conservation_mode.attr) |
649 | supported = priv->features.conservation_mode; |
650 | else if (attr == &dev_attr_fan_mode.attr) |
651 | supported = priv->features.fan_mode; |
652 | else if (attr == &dev_attr_fn_lock.attr) |
653 | supported = priv->features.fn_lock; |
654 | else if (attr == &dev_attr_touchpad.attr) |
655 | supported = priv->features.touchpad_ctrl_via_ec; |
656 | else if (attr == &dev_attr_usb_charging.attr) |
657 | supported = priv->features.usb_charging; |
658 | |
659 | return supported ? attr->mode : 0; |
660 | } |
661 | |
662 | static const struct attribute_group ideapad_attribute_group = { |
663 | .is_visible = ideapad_is_visible, |
664 | .attrs = ideapad_attributes |
665 | }; |
666 | |
667 | /* |
668 | * DYTC Platform profile |
669 | */ |
670 | #define DYTC_CMD_QUERY 0 /* To get DYTC status - enable/revision */ |
671 | #define DYTC_CMD_SET 1 /* To enable/disable IC function mode */ |
672 | #define DYTC_CMD_GET 2 /* To get current IC function and mode */ |
673 | #define DYTC_CMD_RESET 0x1ff /* To reset back to default */ |
674 | |
675 | #define DYTC_QUERY_ENABLE_BIT 8 /* Bit 8 - 0 = disabled, 1 = enabled */ |
676 | #define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revision */ |
677 | #define DYTC_QUERY_REV_BIT 28 /* Bits 28 - 31 - revision */ |
678 | |
679 | #define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */ |
680 | #define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */ |
681 | |
682 | #define DYTC_SET_FUNCTION_BIT 12 /* Bits 12-15 - function setting */ |
683 | #define DYTC_SET_MODE_BIT 16 /* Bits 16-19 - mode setting */ |
684 | #define DYTC_SET_VALID_BIT 20 /* Bit 20 - 1 = on, 0 = off */ |
685 | |
686 | #define DYTC_FUNCTION_STD 0 /* Function = 0, standard mode */ |
687 | #define DYTC_FUNCTION_CQL 1 /* Function = 1, lap mode */ |
688 | #define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode */ |
689 | |
690 | #define DYTC_MODE_PERFORM 2 /* High power mode aka performance */ |
691 | #define DYTC_MODE_LOW_POWER 3 /* Low power mode aka quiet */ |
692 | #define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */ |
693 | |
694 | #define DYTC_SET_COMMAND(function, mode, on) \ |
695 | (DYTC_CMD_SET | (function) << DYTC_SET_FUNCTION_BIT | \ |
696 | (mode) << DYTC_SET_MODE_BIT | \ |
697 | (on) << DYTC_SET_VALID_BIT) |
698 | |
699 | #define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0) |
700 | |
701 | #define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1) |
702 | |
703 | static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile) |
704 | { |
705 | switch (dytcmode) { |
706 | case DYTC_MODE_LOW_POWER: |
707 | *profile = PLATFORM_PROFILE_LOW_POWER; |
708 | break; |
709 | case DYTC_MODE_BALANCE: |
710 | *profile = PLATFORM_PROFILE_BALANCED; |
711 | break; |
712 | case DYTC_MODE_PERFORM: |
713 | *profile = PLATFORM_PROFILE_PERFORMANCE; |
714 | break; |
715 | default: /* Unknown mode */ |
716 | return -EINVAL; |
717 | } |
718 | |
719 | return 0; |
720 | } |
721 | |
722 | static int convert_profile_to_dytc(enum platform_profile_option profile, int *perfmode) |
723 | { |
724 | switch (profile) { |
725 | case PLATFORM_PROFILE_LOW_POWER: |
726 | *perfmode = DYTC_MODE_LOW_POWER; |
727 | break; |
728 | case PLATFORM_PROFILE_BALANCED: |
729 | *perfmode = DYTC_MODE_BALANCE; |
730 | break; |
731 | case PLATFORM_PROFILE_PERFORMANCE: |
732 | *perfmode = DYTC_MODE_PERFORM; |
733 | break; |
734 | default: /* Unknown profile */ |
735 | return -EOPNOTSUPP; |
736 | } |
737 | |
738 | return 0; |
739 | } |
740 | |
741 | /* |
742 | * dytc_profile_get: Function to register with platform_profile |
743 | * handler. Returns current platform profile. |
744 | */ |
745 | static int dytc_profile_get(struct platform_profile_handler *pprof, |
746 | enum platform_profile_option *profile) |
747 | { |
748 | struct ideapad_dytc_priv *dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); |
749 | |
750 | *profile = dytc->current_profile; |
751 | return 0; |
752 | } |
753 | |
754 | /* |
755 | * Helper function - check if we are in CQL mode and if we are |
756 | * - disable CQL, |
757 | * - run the command |
758 | * - enable CQL |
759 | * If not in CQL mode, just run the command |
760 | */ |
761 | static int dytc_cql_command(struct ideapad_private *priv, unsigned long cmd, |
762 | unsigned long *output) |
763 | { |
764 | int err, cmd_err, cur_funcmode; |
765 | |
766 | /* Determine if we are in CQL mode. This alters the commands we do */ |
767 | err = eval_dytc(handle: priv->adev->handle, DYTC_CMD_GET, res: output); |
768 | if (err) |
769 | return err; |
770 | |
771 | cur_funcmode = (*output >> DYTC_GET_FUNCTION_BIT) & 0xF; |
772 | /* Check if we're OK to return immediately */ |
773 | if (cmd == DYTC_CMD_GET && cur_funcmode != DYTC_FUNCTION_CQL) |
774 | return 0; |
775 | |
776 | if (cur_funcmode == DYTC_FUNCTION_CQL) { |
777 | err = eval_dytc(handle: priv->adev->handle, DYTC_DISABLE_CQL, NULL); |
778 | if (err) |
779 | return err; |
780 | } |
781 | |
782 | cmd_err = eval_dytc(handle: priv->adev->handle, cmd, res: output); |
783 | /* Check return condition after we've restored CQL state */ |
784 | |
785 | if (cur_funcmode == DYTC_FUNCTION_CQL) { |
786 | err = eval_dytc(handle: priv->adev->handle, DYTC_ENABLE_CQL, NULL); |
787 | if (err) |
788 | return err; |
789 | } |
790 | |
791 | return cmd_err; |
792 | } |
793 | |
794 | /* |
795 | * dytc_profile_set: Function to register with platform_profile |
796 | * handler. Sets current platform profile. |
797 | */ |
798 | static int dytc_profile_set(struct platform_profile_handler *pprof, |
799 | enum platform_profile_option profile) |
800 | { |
801 | struct ideapad_dytc_priv *dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); |
802 | struct ideapad_private *priv = dytc->priv; |
803 | unsigned long output; |
804 | int err; |
805 | |
806 | err = mutex_lock_interruptible(&dytc->mutex); |
807 | if (err) |
808 | return err; |
809 | |
810 | if (profile == PLATFORM_PROFILE_BALANCED) { |
811 | /* To get back to balanced mode we just issue a reset command */ |
812 | err = eval_dytc(handle: priv->adev->handle, DYTC_CMD_RESET, NULL); |
813 | if (err) |
814 | goto unlock; |
815 | } else { |
816 | int perfmode; |
817 | |
818 | err = convert_profile_to_dytc(profile, perfmode: &perfmode); |
819 | if (err) |
820 | goto unlock; |
821 | |
822 | /* Determine if we are in CQL mode. This alters the commands we do */ |
823 | err = dytc_cql_command(priv, DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), |
824 | output: &output); |
825 | if (err) |
826 | goto unlock; |
827 | } |
828 | |
829 | /* Success - update current profile */ |
830 | dytc->current_profile = profile; |
831 | |
832 | unlock: |
833 | mutex_unlock(lock: &dytc->mutex); |
834 | |
835 | return err; |
836 | } |
837 | |
838 | static void dytc_profile_refresh(struct ideapad_private *priv) |
839 | { |
840 | enum platform_profile_option profile; |
841 | unsigned long output; |
842 | int err, perfmode; |
843 | |
844 | mutex_lock(&priv->dytc->mutex); |
845 | err = dytc_cql_command(priv, DYTC_CMD_GET, output: &output); |
846 | mutex_unlock(lock: &priv->dytc->mutex); |
847 | if (err) |
848 | return; |
849 | |
850 | perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF; |
851 | |
852 | if (convert_dytc_to_profile(dytcmode: perfmode, profile: &profile)) |
853 | return; |
854 | |
855 | if (profile != priv->dytc->current_profile) { |
856 | priv->dytc->current_profile = profile; |
857 | platform_profile_notify(); |
858 | } |
859 | } |
860 | |
861 | static const struct dmi_system_id ideapad_dytc_v4_allow_table[] = { |
862 | { |
863 | /* Ideapad 5 Pro 16ACH6 */ |
864 | .matches = { |
865 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO" ), |
866 | DMI_MATCH(DMI_PRODUCT_NAME, "82L5" ) |
867 | } |
868 | }, |
869 | { |
870 | /* Ideapad 5 15ITL05 */ |
871 | .matches = { |
872 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO" ), |
873 | DMI_MATCH(DMI_PRODUCT_VERSION, "IdeaPad 5 15ITL05" ) |
874 | } |
875 | }, |
876 | {} |
877 | }; |
878 | |
879 | static int ideapad_dytc_profile_init(struct ideapad_private *priv) |
880 | { |
881 | int err, dytc_version; |
882 | unsigned long output; |
883 | |
884 | if (!priv->features.dytc) |
885 | return -ENODEV; |
886 | |
887 | err = eval_dytc(handle: priv->adev->handle, DYTC_CMD_QUERY, res: &output); |
888 | /* For all other errors we can flag the failure */ |
889 | if (err) |
890 | return err; |
891 | |
892 | /* Check DYTC is enabled and supports mode setting */ |
893 | if (!test_bit(DYTC_QUERY_ENABLE_BIT, &output)) { |
894 | dev_info(&priv->platform_device->dev, "DYTC_QUERY_ENABLE_BIT returned false\n" ); |
895 | return -ENODEV; |
896 | } |
897 | |
898 | dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF; |
899 | |
900 | if (dytc_version < 4) { |
901 | dev_info(&priv->platform_device->dev, "DYTC_VERSION < 4 is not supported\n" ); |
902 | return -ENODEV; |
903 | } |
904 | |
905 | if (dytc_version < 5 && |
906 | !(allow_v4_dytc || dmi_check_system(list: ideapad_dytc_v4_allow_table))) { |
907 | dev_info(&priv->platform_device->dev, |
908 | "DYTC_VERSION 4 support may not work. Pass ideapad_laptop.allow_v4_dytc=Y on the kernel commandline to enable\n" ); |
909 | return -ENODEV; |
910 | } |
911 | |
912 | priv->dytc = kzalloc(size: sizeof(*priv->dytc), GFP_KERNEL); |
913 | if (!priv->dytc) |
914 | return -ENOMEM; |
915 | |
916 | mutex_init(&priv->dytc->mutex); |
917 | |
918 | priv->dytc->priv = priv; |
919 | priv->dytc->pprof.profile_get = dytc_profile_get; |
920 | priv->dytc->pprof.profile_set = dytc_profile_set; |
921 | |
922 | /* Setup supported modes */ |
923 | set_bit(nr: PLATFORM_PROFILE_LOW_POWER, addr: priv->dytc->pprof.choices); |
924 | set_bit(nr: PLATFORM_PROFILE_BALANCED, addr: priv->dytc->pprof.choices); |
925 | set_bit(nr: PLATFORM_PROFILE_PERFORMANCE, addr: priv->dytc->pprof.choices); |
926 | |
927 | /* Create platform_profile structure and register */ |
928 | err = platform_profile_register(pprof: &priv->dytc->pprof); |
929 | if (err) |
930 | goto pp_reg_failed; |
931 | |
932 | /* Ensure initial values are correct */ |
933 | dytc_profile_refresh(priv); |
934 | |
935 | return 0; |
936 | |
937 | pp_reg_failed: |
938 | mutex_destroy(lock: &priv->dytc->mutex); |
939 | kfree(objp: priv->dytc); |
940 | priv->dytc = NULL; |
941 | |
942 | return err; |
943 | } |
944 | |
945 | static void ideapad_dytc_profile_exit(struct ideapad_private *priv) |
946 | { |
947 | if (!priv->dytc) |
948 | return; |
949 | |
950 | platform_profile_remove(); |
951 | mutex_destroy(lock: &priv->dytc->mutex); |
952 | kfree(objp: priv->dytc); |
953 | |
954 | priv->dytc = NULL; |
955 | } |
956 | |
957 | /* |
958 | * Rfkill |
959 | */ |
960 | struct ideapad_rfk_data { |
961 | char *name; |
962 | int cfgbit; |
963 | int opcode; |
964 | int type; |
965 | }; |
966 | |
967 | static const struct ideapad_rfk_data ideapad_rfk_data[] = { |
968 | { "ideapad_wlan" , CFG_CAP_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN }, |
969 | { "ideapad_bluetooth" , CFG_CAP_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH }, |
970 | { "ideapad_3g" , CFG_CAP_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN }, |
971 | }; |
972 | |
973 | static int ideapad_rfk_set(void *data, bool blocked) |
974 | { |
975 | struct ideapad_rfk_priv *priv = data; |
976 | int opcode = ideapad_rfk_data[priv->dev].opcode; |
977 | |
978 | return write_ec_cmd(handle: priv->priv->adev->handle, cmd: opcode, data: !blocked); |
979 | } |
980 | |
981 | static const struct rfkill_ops ideapad_rfk_ops = { |
982 | .set_block = ideapad_rfk_set, |
983 | }; |
984 | |
985 | static void ideapad_sync_rfk_state(struct ideapad_private *priv) |
986 | { |
987 | unsigned long hw_blocked = 0; |
988 | int i; |
989 | |
990 | if (priv->features.hw_rfkill_switch) { |
991 | if (read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_RF, data: &hw_blocked)) |
992 | return; |
993 | hw_blocked = !hw_blocked; |
994 | } |
995 | |
996 | for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) |
997 | if (priv->rfk[i]) |
998 | rfkill_set_hw_state(rfkill: priv->rfk[i], blocked: hw_blocked); |
999 | } |
1000 | |
1001 | static int ideapad_register_rfkill(struct ideapad_private *priv, int dev) |
1002 | { |
1003 | unsigned long rf_enabled; |
1004 | int err; |
1005 | |
1006 | if (no_bt_rfkill && ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH) { |
1007 | /* Force to enable bluetooth when no_bt_rfkill=1 */ |
1008 | write_ec_cmd(handle: priv->adev->handle, cmd: ideapad_rfk_data[dev].opcode, data: 1); |
1009 | return 0; |
1010 | } |
1011 | |
1012 | priv->rfk_priv[dev].dev = dev; |
1013 | priv->rfk_priv[dev].priv = priv; |
1014 | |
1015 | priv->rfk[dev] = rfkill_alloc(name: ideapad_rfk_data[dev].name, |
1016 | parent: &priv->platform_device->dev, |
1017 | type: ideapad_rfk_data[dev].type, |
1018 | ops: &ideapad_rfk_ops, |
1019 | ops_data: &priv->rfk_priv[dev]); |
1020 | if (!priv->rfk[dev]) |
1021 | return -ENOMEM; |
1022 | |
1023 | err = read_ec_data(handle: priv->adev->handle, cmd: ideapad_rfk_data[dev].opcode - 1, data: &rf_enabled); |
1024 | if (err) |
1025 | rf_enabled = 1; |
1026 | |
1027 | rfkill_init_sw_state(rfkill: priv->rfk[dev], blocked: !rf_enabled); |
1028 | |
1029 | err = rfkill_register(rfkill: priv->rfk[dev]); |
1030 | if (err) |
1031 | rfkill_destroy(rfkill: priv->rfk[dev]); |
1032 | |
1033 | return err; |
1034 | } |
1035 | |
1036 | static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev) |
1037 | { |
1038 | if (!priv->rfk[dev]) |
1039 | return; |
1040 | |
1041 | rfkill_unregister(rfkill: priv->rfk[dev]); |
1042 | rfkill_destroy(rfkill: priv->rfk[dev]); |
1043 | } |
1044 | |
1045 | /* |
1046 | * Platform device |
1047 | */ |
1048 | static int ideapad_sysfs_init(struct ideapad_private *priv) |
1049 | { |
1050 | return device_add_group(dev: &priv->platform_device->dev, |
1051 | grp: &ideapad_attribute_group); |
1052 | } |
1053 | |
1054 | static void ideapad_sysfs_exit(struct ideapad_private *priv) |
1055 | { |
1056 | device_remove_group(dev: &priv->platform_device->dev, |
1057 | grp: &ideapad_attribute_group); |
1058 | } |
1059 | |
1060 | /* |
1061 | * input device |
1062 | */ |
1063 | #define IDEAPAD_WMI_KEY 0x100 |
1064 | |
1065 | static const struct key_entry ideapad_keymap[] = { |
1066 | { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } }, |
1067 | { KE_KEY, 7, { KEY_CAMERA } }, |
1068 | { KE_KEY, 8, { KEY_MICMUTE } }, |
1069 | { KE_KEY, 11, { KEY_F16 } }, |
1070 | { KE_KEY, 13, { KEY_WLAN } }, |
1071 | { KE_KEY, 16, { KEY_PROG1 } }, |
1072 | { KE_KEY, 17, { KEY_PROG2 } }, |
1073 | { KE_KEY, 64, { KEY_PROG3 } }, |
1074 | { KE_KEY, 65, { KEY_PROG4 } }, |
1075 | { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, |
1076 | { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, |
1077 | { KE_KEY, 128, { KEY_ESC } }, |
1078 | |
1079 | /* |
1080 | * WMI keys |
1081 | */ |
1082 | |
1083 | /* FnLock (handled by the firmware) */ |
1084 | { KE_IGNORE, 0x02 | IDEAPAD_WMI_KEY }, |
1085 | /* Esc (handled by the firmware) */ |
1086 | { KE_IGNORE, 0x03 | IDEAPAD_WMI_KEY }, |
1087 | /* Customizable Lenovo Hotkey ("star" with 'S' inside) */ |
1088 | { KE_KEY, 0x01 | IDEAPAD_WMI_KEY, { KEY_FAVORITES } }, |
1089 | { KE_KEY, 0x04 | IDEAPAD_WMI_KEY, { KEY_SELECTIVE_SCREENSHOT } }, |
1090 | /* Lenovo Support */ |
1091 | { KE_KEY, 0x07 | IDEAPAD_WMI_KEY, { KEY_HELP } }, |
1092 | { KE_KEY, 0x0e | IDEAPAD_WMI_KEY, { KEY_PICKUP_PHONE } }, |
1093 | { KE_KEY, 0x0f | IDEAPAD_WMI_KEY, { KEY_HANGUP_PHONE } }, |
1094 | /* Dark mode toggle */ |
1095 | { KE_KEY, 0x13 | IDEAPAD_WMI_KEY, { KEY_PROG1 } }, |
1096 | /* Sound profile switch */ |
1097 | { KE_KEY, 0x12 | IDEAPAD_WMI_KEY, { KEY_PROG2 } }, |
1098 | /* Lenovo Virtual Background application */ |
1099 | { KE_KEY, 0x28 | IDEAPAD_WMI_KEY, { KEY_PROG3 } }, |
1100 | /* Lenovo Support */ |
1101 | { KE_KEY, 0x27 | IDEAPAD_WMI_KEY, { KEY_HELP } }, |
1102 | /* Refresh Rate Toggle */ |
1103 | { KE_KEY, 0x0a | IDEAPAD_WMI_KEY, { KEY_DISPLAYTOGGLE } }, |
1104 | |
1105 | { KE_END }, |
1106 | }; |
1107 | |
1108 | static int ideapad_input_init(struct ideapad_private *priv) |
1109 | { |
1110 | struct input_dev *inputdev; |
1111 | int err; |
1112 | |
1113 | inputdev = input_allocate_device(); |
1114 | if (!inputdev) |
1115 | return -ENOMEM; |
1116 | |
1117 | inputdev->name = "Ideapad extra buttons" ; |
1118 | inputdev->phys = "ideapad/input0" ; |
1119 | inputdev->id.bustype = BUS_HOST; |
1120 | inputdev->dev.parent = &priv->platform_device->dev; |
1121 | |
1122 | err = sparse_keymap_setup(dev: inputdev, keymap: ideapad_keymap, NULL); |
1123 | if (err) { |
1124 | dev_err(&priv->platform_device->dev, |
1125 | "Could not set up input device keymap: %d\n" , err); |
1126 | goto err_free_dev; |
1127 | } |
1128 | |
1129 | err = input_register_device(inputdev); |
1130 | if (err) { |
1131 | dev_err(&priv->platform_device->dev, |
1132 | "Could not register input device: %d\n" , err); |
1133 | goto err_free_dev; |
1134 | } |
1135 | |
1136 | priv->inputdev = inputdev; |
1137 | |
1138 | return 0; |
1139 | |
1140 | err_free_dev: |
1141 | input_free_device(dev: inputdev); |
1142 | |
1143 | return err; |
1144 | } |
1145 | |
1146 | static void ideapad_input_exit(struct ideapad_private *priv) |
1147 | { |
1148 | input_unregister_device(priv->inputdev); |
1149 | priv->inputdev = NULL; |
1150 | } |
1151 | |
1152 | static void ideapad_input_report(struct ideapad_private *priv, |
1153 | unsigned long scancode) |
1154 | { |
1155 | sparse_keymap_report_event(dev: priv->inputdev, code: scancode, value: 1, autorelease: true); |
1156 | } |
1157 | |
1158 | static void ideapad_input_novokey(struct ideapad_private *priv) |
1159 | { |
1160 | unsigned long long_pressed; |
1161 | |
1162 | if (read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_NOVO, data: &long_pressed)) |
1163 | return; |
1164 | |
1165 | if (long_pressed) |
1166 | ideapad_input_report(priv, scancode: 17); |
1167 | else |
1168 | ideapad_input_report(priv, scancode: 16); |
1169 | } |
1170 | |
1171 | static void ideapad_check_special_buttons(struct ideapad_private *priv) |
1172 | { |
1173 | unsigned long bit, value; |
1174 | |
1175 | if (read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_SPECIAL_BUTTONS, data: &value)) |
1176 | return; |
1177 | |
1178 | for_each_set_bit (bit, &value, 16) { |
1179 | switch (bit) { |
1180 | case 6: /* Z570 */ |
1181 | case 0: /* Z580 */ |
1182 | /* Thermal Management button */ |
1183 | ideapad_input_report(priv, scancode: 65); |
1184 | break; |
1185 | case 1: |
1186 | /* OneKey Theater button */ |
1187 | ideapad_input_report(priv, scancode: 64); |
1188 | break; |
1189 | default: |
1190 | dev_info(&priv->platform_device->dev, |
1191 | "Unknown special button: %lu\n" , bit); |
1192 | break; |
1193 | } |
1194 | } |
1195 | } |
1196 | |
1197 | /* |
1198 | * backlight |
1199 | */ |
1200 | static int ideapad_backlight_get_brightness(struct backlight_device *blightdev) |
1201 | { |
1202 | struct ideapad_private *priv = bl_get_data(bl_dev: blightdev); |
1203 | unsigned long now; |
1204 | int err; |
1205 | |
1206 | err = read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_BL, data: &now); |
1207 | if (err) |
1208 | return err; |
1209 | |
1210 | return now; |
1211 | } |
1212 | |
1213 | static int ideapad_backlight_update_status(struct backlight_device *blightdev) |
1214 | { |
1215 | struct ideapad_private *priv = bl_get_data(bl_dev: blightdev); |
1216 | int err; |
1217 | |
1218 | err = write_ec_cmd(handle: priv->adev->handle, cmd: VPCCMD_W_BL, |
1219 | data: blightdev->props.brightness); |
1220 | if (err) |
1221 | return err; |
1222 | |
1223 | err = write_ec_cmd(handle: priv->adev->handle, cmd: VPCCMD_W_BL_POWER, |
1224 | data: blightdev->props.power != FB_BLANK_POWERDOWN); |
1225 | if (err) |
1226 | return err; |
1227 | |
1228 | return 0; |
1229 | } |
1230 | |
1231 | static const struct backlight_ops ideapad_backlight_ops = { |
1232 | .get_brightness = ideapad_backlight_get_brightness, |
1233 | .update_status = ideapad_backlight_update_status, |
1234 | }; |
1235 | |
1236 | static int ideapad_backlight_init(struct ideapad_private *priv) |
1237 | { |
1238 | struct backlight_device *blightdev; |
1239 | struct backlight_properties props; |
1240 | unsigned long max, now, power; |
1241 | int err; |
1242 | |
1243 | err = read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_BL_MAX, data: &max); |
1244 | if (err) |
1245 | return err; |
1246 | |
1247 | err = read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_BL, data: &now); |
1248 | if (err) |
1249 | return err; |
1250 | |
1251 | err = read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_BL_POWER, data: &power); |
1252 | if (err) |
1253 | return err; |
1254 | |
1255 | memset(&props, 0, sizeof(props)); |
1256 | |
1257 | props.max_brightness = max; |
1258 | props.type = BACKLIGHT_PLATFORM; |
1259 | |
1260 | blightdev = backlight_device_register(name: "ideapad" , |
1261 | dev: &priv->platform_device->dev, |
1262 | devdata: priv, |
1263 | ops: &ideapad_backlight_ops, |
1264 | props: &props); |
1265 | if (IS_ERR(ptr: blightdev)) { |
1266 | err = PTR_ERR(ptr: blightdev); |
1267 | dev_err(&priv->platform_device->dev, |
1268 | "Could not register backlight device: %d\n" , err); |
1269 | return err; |
1270 | } |
1271 | |
1272 | priv->blightdev = blightdev; |
1273 | blightdev->props.brightness = now; |
1274 | blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; |
1275 | |
1276 | backlight_update_status(bd: blightdev); |
1277 | |
1278 | return 0; |
1279 | } |
1280 | |
1281 | static void ideapad_backlight_exit(struct ideapad_private *priv) |
1282 | { |
1283 | backlight_device_unregister(bd: priv->blightdev); |
1284 | priv->blightdev = NULL; |
1285 | } |
1286 | |
1287 | static void ideapad_backlight_notify_power(struct ideapad_private *priv) |
1288 | { |
1289 | struct backlight_device *blightdev = priv->blightdev; |
1290 | unsigned long power; |
1291 | |
1292 | if (!blightdev) |
1293 | return; |
1294 | |
1295 | if (read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_BL_POWER, data: &power)) |
1296 | return; |
1297 | |
1298 | blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; |
1299 | } |
1300 | |
1301 | static void ideapad_backlight_notify_brightness(struct ideapad_private *priv) |
1302 | { |
1303 | unsigned long now; |
1304 | |
1305 | /* if we control brightness via acpi video driver */ |
1306 | if (!priv->blightdev) |
1307 | read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_BL, data: &now); |
1308 | else |
1309 | backlight_force_update(bd: priv->blightdev, reason: BACKLIGHT_UPDATE_HOTKEY); |
1310 | } |
1311 | |
1312 | /* |
1313 | * keyboard backlight |
1314 | */ |
1315 | static int ideapad_kbd_bl_check_tristate(int type) |
1316 | { |
1317 | return (type == KBD_BL_TRISTATE) || (type == KBD_BL_TRISTATE_AUTO); |
1318 | } |
1319 | |
1320 | static int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv) |
1321 | { |
1322 | unsigned long value; |
1323 | int err; |
1324 | |
1325 | if (ideapad_kbd_bl_check_tristate(type: priv->kbd_bl.type)) { |
1326 | err = eval_kblc(handle: priv->adev->handle, |
1327 | FIELD_PREP(KBD_BL_COMMAND_TYPE, priv->kbd_bl.type) | |
1328 | KBD_BL_COMMAND_GET, |
1329 | res: &value); |
1330 | |
1331 | if (err) |
1332 | return err; |
1333 | |
1334 | /* Convert returned value to brightness level */ |
1335 | value = FIELD_GET(KBD_BL_GET_BRIGHTNESS, value); |
1336 | |
1337 | /* Off, low or high */ |
1338 | if (value <= priv->kbd_bl.led.max_brightness) |
1339 | return value; |
1340 | |
1341 | /* Auto, report as off */ |
1342 | if (value == priv->kbd_bl.led.max_brightness + 1) |
1343 | return 0; |
1344 | |
1345 | /* Unknown value */ |
1346 | dev_warn(&priv->platform_device->dev, |
1347 | "Unknown keyboard backlight value: %lu" , value); |
1348 | return -EINVAL; |
1349 | } |
1350 | |
1351 | err = eval_hals(handle: priv->adev->handle, res: &value); |
1352 | if (err) |
1353 | return err; |
1354 | |
1355 | return !!test_bit(HALS_KBD_BL_STATE_BIT, &value); |
1356 | } |
1357 | |
1358 | static enum led_brightness ideapad_kbd_bl_led_cdev_brightness_get(struct led_classdev *led_cdev) |
1359 | { |
1360 | struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led); |
1361 | |
1362 | return ideapad_kbd_bl_brightness_get(priv); |
1363 | } |
1364 | |
1365 | static int ideapad_kbd_bl_brightness_set(struct ideapad_private *priv, unsigned int brightness) |
1366 | { |
1367 | int err; |
1368 | unsigned long value; |
1369 | int type = priv->kbd_bl.type; |
1370 | |
1371 | if (ideapad_kbd_bl_check_tristate(type)) { |
1372 | if (brightness > priv->kbd_bl.led.max_brightness) |
1373 | return -EINVAL; |
1374 | |
1375 | value = FIELD_PREP(KBD_BL_SET_BRIGHTNESS, brightness) | |
1376 | FIELD_PREP(KBD_BL_COMMAND_TYPE, type) | |
1377 | KBD_BL_COMMAND_SET; |
1378 | err = exec_kblc(handle: priv->adev->handle, arg: value); |
1379 | } else { |
1380 | err = exec_sals(handle: priv->adev->handle, arg: brightness ? SALS_KBD_BL_ON : SALS_KBD_BL_OFF); |
1381 | } |
1382 | |
1383 | if (err) |
1384 | return err; |
1385 | |
1386 | priv->kbd_bl.last_brightness = brightness; |
1387 | |
1388 | return 0; |
1389 | } |
1390 | |
1391 | static int ideapad_kbd_bl_led_cdev_brightness_set(struct led_classdev *led_cdev, |
1392 | enum led_brightness brightness) |
1393 | { |
1394 | struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led); |
1395 | |
1396 | return ideapad_kbd_bl_brightness_set(priv, brightness); |
1397 | } |
1398 | |
1399 | static void ideapad_kbd_bl_notify(struct ideapad_private *priv) |
1400 | { |
1401 | int brightness; |
1402 | |
1403 | if (!priv->kbd_bl.initialized) |
1404 | return; |
1405 | |
1406 | brightness = ideapad_kbd_bl_brightness_get(priv); |
1407 | if (brightness < 0) |
1408 | return; |
1409 | |
1410 | if (brightness == priv->kbd_bl.last_brightness) |
1411 | return; |
1412 | |
1413 | priv->kbd_bl.last_brightness = brightness; |
1414 | |
1415 | led_classdev_notify_brightness_hw_changed(led_cdev: &priv->kbd_bl.led, brightness); |
1416 | } |
1417 | |
1418 | static int ideapad_kbd_bl_init(struct ideapad_private *priv) |
1419 | { |
1420 | int brightness, err; |
1421 | |
1422 | if (!priv->features.kbd_bl) |
1423 | return -ENODEV; |
1424 | |
1425 | if (WARN_ON(priv->kbd_bl.initialized)) |
1426 | return -EEXIST; |
1427 | |
1428 | brightness = ideapad_kbd_bl_brightness_get(priv); |
1429 | if (brightness < 0) |
1430 | return brightness; |
1431 | |
1432 | priv->kbd_bl.last_brightness = brightness; |
1433 | |
1434 | if (ideapad_kbd_bl_check_tristate(type: priv->kbd_bl.type)) { |
1435 | priv->kbd_bl.led.max_brightness = 2; |
1436 | } else { |
1437 | priv->kbd_bl.led.max_brightness = 1; |
1438 | } |
1439 | |
1440 | priv->kbd_bl.led.name = "platform::" LED_FUNCTION_KBD_BACKLIGHT; |
1441 | priv->kbd_bl.led.brightness_get = ideapad_kbd_bl_led_cdev_brightness_get; |
1442 | priv->kbd_bl.led.brightness_set_blocking = ideapad_kbd_bl_led_cdev_brightness_set; |
1443 | priv->kbd_bl.led.flags = LED_BRIGHT_HW_CHANGED; |
1444 | |
1445 | err = led_classdev_register(parent: &priv->platform_device->dev, led_cdev: &priv->kbd_bl.led); |
1446 | if (err) |
1447 | return err; |
1448 | |
1449 | priv->kbd_bl.initialized = true; |
1450 | |
1451 | return 0; |
1452 | } |
1453 | |
1454 | static void ideapad_kbd_bl_exit(struct ideapad_private *priv) |
1455 | { |
1456 | if (!priv->kbd_bl.initialized) |
1457 | return; |
1458 | |
1459 | priv->kbd_bl.initialized = false; |
1460 | |
1461 | led_classdev_unregister(led_cdev: &priv->kbd_bl.led); |
1462 | } |
1463 | |
1464 | /* |
1465 | * module init/exit |
1466 | */ |
1467 | static void ideapad_sync_touchpad_state(struct ideapad_private *priv, bool send_events) |
1468 | { |
1469 | unsigned long value; |
1470 | unsigned char param; |
1471 | int ret; |
1472 | |
1473 | /* Without reading from EC touchpad LED doesn't switch state */ |
1474 | ret = read_ec_data(handle: priv->adev->handle, cmd: VPCCMD_R_TOUCHPAD, data: &value); |
1475 | if (ret) |
1476 | return; |
1477 | |
1478 | /* |
1479 | * Some IdeaPads don't really turn off touchpad - they only |
1480 | * switch the LED state. We (de)activate KBC AUX port to turn |
1481 | * touchpad off and on. We send KEY_TOUCHPAD_OFF and |
1482 | * KEY_TOUCHPAD_ON to not to get out of sync with LED |
1483 | */ |
1484 | if (priv->features.ctrl_ps2_aux_port) |
1485 | i8042_command(param: ¶m, command: value ? I8042_CMD_AUX_ENABLE : I8042_CMD_AUX_DISABLE); |
1486 | |
1487 | /* |
1488 | * On older models the EC controls the touchpad and toggles it on/off |
1489 | * itself, in this case we report KEY_TOUCHPAD_ON/_OFF. Some models do |
1490 | * an acpi-notify with VPC bit 5 set on resume, so this function get |
1491 | * called with send_events=true on every resume. Therefor if the EC did |
1492 | * not toggle, do nothing to avoid sending spurious KEY_TOUCHPAD_TOGGLE. |
1493 | */ |
1494 | if (send_events && value != priv->r_touchpad_val) { |
1495 | ideapad_input_report(priv, scancode: value ? 67 : 66); |
1496 | sysfs_notify(kobj: &priv->platform_device->dev.kobj, NULL, attr: "touchpad" ); |
1497 | } |
1498 | |
1499 | priv->r_touchpad_val = value; |
1500 | } |
1501 | |
1502 | static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) |
1503 | { |
1504 | struct ideapad_private *priv = data; |
1505 | unsigned long vpc1, vpc2, bit; |
1506 | |
1507 | if (read_ec_data(handle, cmd: VPCCMD_R_VPC1, data: &vpc1)) |
1508 | return; |
1509 | |
1510 | if (read_ec_data(handle, cmd: VPCCMD_R_VPC2, data: &vpc2)) |
1511 | return; |
1512 | |
1513 | vpc1 = (vpc2 << 8) | vpc1; |
1514 | |
1515 | for_each_set_bit (bit, &vpc1, 16) { |
1516 | switch (bit) { |
1517 | case 13: |
1518 | case 11: |
1519 | case 8: |
1520 | case 7: |
1521 | case 6: |
1522 | ideapad_input_report(priv, scancode: bit); |
1523 | break; |
1524 | case 10: |
1525 | /* |
1526 | * This event gets send on a Yoga 300-11IBR when the EC |
1527 | * believes that the device has changed between laptop/ |
1528 | * tent/stand/tablet mode. The EC relies on getting |
1529 | * angle info from 2 accelerometers through a special |
1530 | * windows service calling a DSM on the DUAL250E ACPI- |
1531 | * device. Linux does not do this, making the laptop/ |
1532 | * tent/stand/tablet mode info unreliable, so we simply |
1533 | * ignore these events. |
1534 | */ |
1535 | break; |
1536 | case 9: |
1537 | ideapad_sync_rfk_state(priv); |
1538 | break; |
1539 | case 5: |
1540 | ideapad_sync_touchpad_state(priv, send_events: true); |
1541 | break; |
1542 | case 4: |
1543 | ideapad_backlight_notify_brightness(priv); |
1544 | break; |
1545 | case 3: |
1546 | ideapad_input_novokey(priv); |
1547 | break; |
1548 | case 2: |
1549 | ideapad_backlight_notify_power(priv); |
1550 | break; |
1551 | case KBD_BL_KBLC_CHANGED_EVENT: |
1552 | case 1: |
1553 | /* |
1554 | * Some IdeaPads report event 1 every ~20 |
1555 | * seconds while on battery power; some |
1556 | * report this when changing to/from tablet |
1557 | * mode; some report this when the keyboard |
1558 | * backlight has changed. |
1559 | */ |
1560 | ideapad_kbd_bl_notify(priv); |
1561 | break; |
1562 | case 0: |
1563 | ideapad_check_special_buttons(priv); |
1564 | break; |
1565 | default: |
1566 | dev_info(&priv->platform_device->dev, |
1567 | "Unknown event: %lu\n" , bit); |
1568 | } |
1569 | } |
1570 | } |
1571 | |
1572 | /* On some models we need to call exec_sals(SALS_FNLOCK_ON/OFF) to set the LED */ |
1573 | static const struct dmi_system_id set_fn_lock_led_list[] = { |
1574 | { |
1575 | /* https://bugzilla.kernel.org/show_bug.cgi?id=212671 */ |
1576 | .matches = { |
1577 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO" ), |
1578 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Legion R7000P2020H" ), |
1579 | } |
1580 | }, |
1581 | { |
1582 | .matches = { |
1583 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO" ), |
1584 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Legion 5 15ARH05" ), |
1585 | } |
1586 | }, |
1587 | {} |
1588 | }; |
1589 | |
1590 | /* |
1591 | * Some ideapads have a hardware rfkill switch, but most do not have one. |
1592 | * Reading VPCCMD_R_RF always results in 0 on models without a hardware rfkill, |
1593 | * switch causing ideapad_laptop to wrongly report all radios as hw-blocked. |
1594 | * There used to be a long list of DMI ids for models without a hw rfkill |
1595 | * switch here, but that resulted in playing whack a mole. |
1596 | * More importantly wrongly reporting the wifi radio as hw-blocked, results in |
1597 | * non working wifi. Whereas not reporting it hw-blocked, when it actually is |
1598 | * hw-blocked results in an empty SSID list, which is a much more benign |
1599 | * failure mode. |
1600 | * So the default now is the much safer option of assuming there is no |
1601 | * hardware rfkill switch. This default also actually matches most hardware, |
1602 | * since having a hw rfkill switch is quite rare on modern hardware, so this |
1603 | * also leads to a much shorter list. |
1604 | */ |
1605 | static const struct dmi_system_id hw_rfkill_list[] = { |
1606 | {} |
1607 | }; |
1608 | |
1609 | /* |
1610 | * On some models the EC toggles the touchpad muted LED on touchpad toggle |
1611 | * hotkey presses, but the EC does not actually disable the touchpad itself. |
1612 | * On these models the driver needs to explicitly enable/disable the i8042 |
1613 | * (PS/2) aux port. |
1614 | */ |
1615 | static const struct dmi_system_id ctrl_ps2_aux_port_list[] = { |
1616 | { |
1617 | /* Lenovo Ideapad Z570 */ |
1618 | .matches = { |
1619 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO" ), |
1620 | DMI_MATCH(DMI_PRODUCT_VERSION, "Ideapad Z570" ), |
1621 | }, |
1622 | }, |
1623 | {} |
1624 | }; |
1625 | |
1626 | static void ideapad_check_features(struct ideapad_private *priv) |
1627 | { |
1628 | acpi_handle handle = priv->adev->handle; |
1629 | unsigned long val; |
1630 | |
1631 | priv->features.set_fn_lock_led = |
1632 | set_fn_lock_led || dmi_check_system(list: set_fn_lock_led_list); |
1633 | priv->features.hw_rfkill_switch = |
1634 | hw_rfkill_switch || dmi_check_system(list: hw_rfkill_list); |
1635 | priv->features.ctrl_ps2_aux_port = |
1636 | ctrl_ps2_aux_port || dmi_check_system(list: ctrl_ps2_aux_port_list); |
1637 | priv->features.touchpad_ctrl_via_ec = touchpad_ctrl_via_ec; |
1638 | |
1639 | if (!read_ec_data(handle, cmd: VPCCMD_R_FAN, data: &val)) |
1640 | priv->features.fan_mode = true; |
1641 | |
1642 | if (acpi_has_method(handle, name: "GBMD" ) && acpi_has_method(handle, name: "SBMC" )) |
1643 | priv->features.conservation_mode = true; |
1644 | |
1645 | if (acpi_has_method(handle, name: "DYTC" )) |
1646 | priv->features.dytc = true; |
1647 | |
1648 | if (acpi_has_method(handle, name: "HALS" ) && acpi_has_method(handle, name: "SALS" )) { |
1649 | if (!eval_hals(handle, res: &val)) { |
1650 | if (test_bit(HALS_FNLOCK_SUPPORT_BIT, &val)) |
1651 | priv->features.fn_lock = true; |
1652 | |
1653 | if (test_bit(HALS_KBD_BL_SUPPORT_BIT, &val)) { |
1654 | priv->features.kbd_bl = true; |
1655 | priv->kbd_bl.type = KBD_BL_STANDARD; |
1656 | } |
1657 | |
1658 | if (test_bit(HALS_USB_CHARGING_SUPPORT_BIT, &val)) |
1659 | priv->features.usb_charging = true; |
1660 | } |
1661 | } |
1662 | |
1663 | if (acpi_has_method(handle, name: "KBLC" )) { |
1664 | if (!eval_kblc(handle: priv->adev->handle, KBD_BL_QUERY_TYPE, res: &val)) { |
1665 | if (val == KBD_BL_TRISTATE_TYPE) { |
1666 | priv->features.kbd_bl = true; |
1667 | priv->kbd_bl.type = KBD_BL_TRISTATE; |
1668 | } else if (val == KBD_BL_TRISTATE_AUTO_TYPE) { |
1669 | priv->features.kbd_bl = true; |
1670 | priv->kbd_bl.type = KBD_BL_TRISTATE_AUTO; |
1671 | } else { |
1672 | dev_warn(&priv->platform_device->dev, |
1673 | "Unknown keyboard type: %lu" , |
1674 | val); |
1675 | } |
1676 | } |
1677 | } |
1678 | } |
1679 | |
1680 | #if IS_ENABLED(CONFIG_ACPI_WMI) |
1681 | /* |
1682 | * WMI driver |
1683 | */ |
1684 | enum ideapad_wmi_event_type { |
1685 | IDEAPAD_WMI_EVENT_ESC, |
1686 | IDEAPAD_WMI_EVENT_FN_KEYS, |
1687 | }; |
1688 | |
1689 | struct ideapad_wmi_private { |
1690 | enum ideapad_wmi_event_type event; |
1691 | }; |
1692 | |
1693 | static int ideapad_wmi_probe(struct wmi_device *wdev, const void *context) |
1694 | { |
1695 | struct ideapad_wmi_private *wpriv; |
1696 | |
1697 | wpriv = devm_kzalloc(dev: &wdev->dev, size: sizeof(*wpriv), GFP_KERNEL); |
1698 | if (!wpriv) |
1699 | return -ENOMEM; |
1700 | |
1701 | *wpriv = *(const struct ideapad_wmi_private *)context; |
1702 | |
1703 | dev_set_drvdata(dev: &wdev->dev, data: wpriv); |
1704 | return 0; |
1705 | } |
1706 | |
1707 | static void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data) |
1708 | { |
1709 | struct ideapad_wmi_private *wpriv = dev_get_drvdata(dev: &wdev->dev); |
1710 | struct ideapad_private *priv; |
1711 | unsigned long result; |
1712 | |
1713 | mutex_lock(&ideapad_shared_mutex); |
1714 | |
1715 | priv = ideapad_shared; |
1716 | if (!priv) |
1717 | goto unlock; |
1718 | |
1719 | switch (wpriv->event) { |
1720 | case IDEAPAD_WMI_EVENT_ESC: |
1721 | ideapad_input_report(priv, scancode: 128); |
1722 | break; |
1723 | case IDEAPAD_WMI_EVENT_FN_KEYS: |
1724 | if (priv->features.set_fn_lock_led && |
1725 | !eval_hals(handle: priv->adev->handle, res: &result)) { |
1726 | bool state = test_bit(HALS_FNLOCK_STATE_BIT, &result); |
1727 | |
1728 | exec_sals(handle: priv->adev->handle, arg: state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); |
1729 | } |
1730 | |
1731 | if (data->type != ACPI_TYPE_INTEGER) { |
1732 | dev_warn(&wdev->dev, |
1733 | "WMI event data is not an integer\n" ); |
1734 | break; |
1735 | } |
1736 | |
1737 | dev_dbg(&wdev->dev, "WMI fn-key event: 0x%llx\n" , |
1738 | data->integer.value); |
1739 | |
1740 | ideapad_input_report(priv, |
1741 | scancode: data->integer.value | IDEAPAD_WMI_KEY); |
1742 | |
1743 | break; |
1744 | } |
1745 | unlock: |
1746 | mutex_unlock(lock: &ideapad_shared_mutex); |
1747 | } |
1748 | |
1749 | static const struct ideapad_wmi_private ideapad_wmi_context_esc = { |
1750 | .event = IDEAPAD_WMI_EVENT_ESC |
1751 | }; |
1752 | |
1753 | static const struct ideapad_wmi_private ideapad_wmi_context_fn_keys = { |
1754 | .event = IDEAPAD_WMI_EVENT_FN_KEYS |
1755 | }; |
1756 | |
1757 | static const struct wmi_device_id ideapad_wmi_ids[] = { |
1758 | { "26CAB2E5-5CF1-46AE-AAC3-4A12B6BA50E6" , &ideapad_wmi_context_esc }, /* Yoga 3 */ |
1759 | { "56322276-8493-4CE8-A783-98C991274F5E" , &ideapad_wmi_context_esc }, /* Yoga 700 */ |
1760 | { "8FC0DE0C-B4E4-43FD-B0F3-8871711C1294" , &ideapad_wmi_context_fn_keys }, /* Legion 5 */ |
1761 | {}, |
1762 | }; |
1763 | MODULE_DEVICE_TABLE(wmi, ideapad_wmi_ids); |
1764 | |
1765 | static struct wmi_driver ideapad_wmi_driver = { |
1766 | .driver = { |
1767 | .name = "ideapad_wmi" , |
1768 | }, |
1769 | .id_table = ideapad_wmi_ids, |
1770 | .probe = ideapad_wmi_probe, |
1771 | .notify = ideapad_wmi_notify, |
1772 | }; |
1773 | |
1774 | static int ideapad_wmi_driver_register(void) |
1775 | { |
1776 | return wmi_driver_register(&ideapad_wmi_driver); |
1777 | } |
1778 | |
1779 | static void ideapad_wmi_driver_unregister(void) |
1780 | { |
1781 | return wmi_driver_unregister(driver: &ideapad_wmi_driver); |
1782 | } |
1783 | |
1784 | #else |
1785 | static inline int ideapad_wmi_driver_register(void) { return 0; } |
1786 | static inline void ideapad_wmi_driver_unregister(void) { } |
1787 | #endif |
1788 | |
1789 | /* |
1790 | * ACPI driver |
1791 | */ |
1792 | static int ideapad_acpi_add(struct platform_device *pdev) |
1793 | { |
1794 | struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); |
1795 | struct ideapad_private *priv; |
1796 | acpi_status status; |
1797 | unsigned long cfg; |
1798 | int err, i; |
1799 | |
1800 | if (!adev || eval_int(handle: adev->handle, name: "_CFG" , res: &cfg)) |
1801 | return -ENODEV; |
1802 | |
1803 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
1804 | if (!priv) |
1805 | return -ENOMEM; |
1806 | |
1807 | dev_set_drvdata(dev: &pdev->dev, data: priv); |
1808 | |
1809 | priv->cfg = cfg; |
1810 | priv->adev = adev; |
1811 | priv->platform_device = pdev; |
1812 | |
1813 | ideapad_check_features(priv); |
1814 | |
1815 | err = ideapad_sysfs_init(priv); |
1816 | if (err) |
1817 | return err; |
1818 | |
1819 | ideapad_debugfs_init(priv); |
1820 | |
1821 | err = ideapad_input_init(priv); |
1822 | if (err) |
1823 | goto input_failed; |
1824 | |
1825 | err = ideapad_kbd_bl_init(priv); |
1826 | if (err) { |
1827 | if (err != -ENODEV) |
1828 | dev_warn(&pdev->dev, "Could not set up keyboard backlight LED: %d\n" , err); |
1829 | else |
1830 | dev_info(&pdev->dev, "Keyboard backlight control not available\n" ); |
1831 | } |
1832 | |
1833 | /* |
1834 | * On some models without a hw-switch (the yoga 2 13 at least) |
1835 | * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work. |
1836 | */ |
1837 | if (!priv->features.hw_rfkill_switch) |
1838 | write_ec_cmd(handle: priv->adev->handle, cmd: VPCCMD_W_RF, data: 1); |
1839 | |
1840 | for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) |
1841 | if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg)) |
1842 | ideapad_register_rfkill(priv, dev: i); |
1843 | |
1844 | ideapad_sync_rfk_state(priv); |
1845 | ideapad_sync_touchpad_state(priv, send_events: false); |
1846 | |
1847 | err = ideapad_dytc_profile_init(priv); |
1848 | if (err) { |
1849 | if (err != -ENODEV) |
1850 | dev_warn(&pdev->dev, "Could not set up DYTC interface: %d\n" , err); |
1851 | else |
1852 | dev_info(&pdev->dev, "DYTC interface is not available\n" ); |
1853 | } |
1854 | |
1855 | if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { |
1856 | err = ideapad_backlight_init(priv); |
1857 | if (err && err != -ENODEV) |
1858 | goto backlight_failed; |
1859 | } |
1860 | |
1861 | status = acpi_install_notify_handler(device: adev->handle, |
1862 | ACPI_DEVICE_NOTIFY, |
1863 | handler: ideapad_acpi_notify, context: priv); |
1864 | if (ACPI_FAILURE(status)) { |
1865 | err = -EIO; |
1866 | goto notification_failed; |
1867 | } |
1868 | |
1869 | err = ideapad_shared_init(priv); |
1870 | if (err) |
1871 | goto shared_init_failed; |
1872 | |
1873 | return 0; |
1874 | |
1875 | shared_init_failed: |
1876 | acpi_remove_notify_handler(device: priv->adev->handle, |
1877 | ACPI_DEVICE_NOTIFY, |
1878 | handler: ideapad_acpi_notify); |
1879 | |
1880 | notification_failed: |
1881 | ideapad_backlight_exit(priv); |
1882 | |
1883 | backlight_failed: |
1884 | ideapad_dytc_profile_exit(priv); |
1885 | |
1886 | for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) |
1887 | ideapad_unregister_rfkill(priv, dev: i); |
1888 | |
1889 | ideapad_kbd_bl_exit(priv); |
1890 | ideapad_input_exit(priv); |
1891 | |
1892 | input_failed: |
1893 | ideapad_debugfs_exit(priv); |
1894 | ideapad_sysfs_exit(priv); |
1895 | |
1896 | return err; |
1897 | } |
1898 | |
1899 | static void ideapad_acpi_remove(struct platform_device *pdev) |
1900 | { |
1901 | struct ideapad_private *priv = dev_get_drvdata(dev: &pdev->dev); |
1902 | int i; |
1903 | |
1904 | ideapad_shared_exit(priv); |
1905 | |
1906 | acpi_remove_notify_handler(device: priv->adev->handle, |
1907 | ACPI_DEVICE_NOTIFY, |
1908 | handler: ideapad_acpi_notify); |
1909 | |
1910 | ideapad_backlight_exit(priv); |
1911 | ideapad_dytc_profile_exit(priv); |
1912 | |
1913 | for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) |
1914 | ideapad_unregister_rfkill(priv, dev: i); |
1915 | |
1916 | ideapad_kbd_bl_exit(priv); |
1917 | ideapad_input_exit(priv); |
1918 | ideapad_debugfs_exit(priv); |
1919 | ideapad_sysfs_exit(priv); |
1920 | } |
1921 | |
1922 | #ifdef CONFIG_PM_SLEEP |
1923 | static int ideapad_acpi_resume(struct device *dev) |
1924 | { |
1925 | struct ideapad_private *priv = dev_get_drvdata(dev); |
1926 | |
1927 | ideapad_sync_rfk_state(priv); |
1928 | ideapad_sync_touchpad_state(priv, send_events: false); |
1929 | |
1930 | if (priv->dytc) |
1931 | dytc_profile_refresh(priv); |
1932 | |
1933 | return 0; |
1934 | } |
1935 | #endif |
1936 | static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume); |
1937 | |
1938 | static const struct acpi_device_id ideapad_device_ids[] = { |
1939 | {"VPC2004" , 0}, |
1940 | {"" , 0}, |
1941 | }; |
1942 | MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); |
1943 | |
1944 | static struct platform_driver ideapad_acpi_driver = { |
1945 | .probe = ideapad_acpi_add, |
1946 | .remove_new = ideapad_acpi_remove, |
1947 | .driver = { |
1948 | .name = "ideapad_acpi" , |
1949 | .pm = &ideapad_pm, |
1950 | .acpi_match_table = ACPI_PTR(ideapad_device_ids), |
1951 | }, |
1952 | }; |
1953 | |
1954 | static int __init ideapad_laptop_init(void) |
1955 | { |
1956 | int err; |
1957 | |
1958 | err = ideapad_wmi_driver_register(); |
1959 | if (err) |
1960 | return err; |
1961 | |
1962 | err = platform_driver_register(&ideapad_acpi_driver); |
1963 | if (err) { |
1964 | ideapad_wmi_driver_unregister(); |
1965 | return err; |
1966 | } |
1967 | |
1968 | return 0; |
1969 | } |
1970 | module_init(ideapad_laptop_init) |
1971 | |
1972 | static void __exit ideapad_laptop_exit(void) |
1973 | { |
1974 | ideapad_wmi_driver_unregister(); |
1975 | platform_driver_unregister(&ideapad_acpi_driver); |
1976 | } |
1977 | module_exit(ideapad_laptop_exit) |
1978 | |
1979 | MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>" ); |
1980 | MODULE_DESCRIPTION("IdeaPad ACPI Extras" ); |
1981 | MODULE_LICENSE("GPL" ); |
1982 | |