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