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