]>
Commit | Line | Data |
---|---|---|
58ac7aa0 | 1 | /* |
a4b5a279 | 2 | * ideapad-laptop.c - Lenovo IdeaPad ACPI Extras |
58ac7aa0 DW |
3 | * |
4 | * Copyright © 2010 Intel Corporation | |
5 | * Copyright © 2010 David Woodhouse <dwmw2@infradead.org> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; either version 2 of the License, or | |
10 | * (at your option) any later version. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with this program; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
20 | * 02110-1301, USA. | |
21 | */ | |
22 | ||
9ab23989 JP |
23 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
24 | ||
58ac7aa0 DW |
25 | #include <linux/kernel.h> |
26 | #include <linux/module.h> | |
27 | #include <linux/init.h> | |
28 | #include <linux/types.h> | |
8b48463f | 29 | #include <linux/acpi.h> |
58ac7aa0 | 30 | #include <linux/rfkill.h> |
98ee6919 | 31 | #include <linux/platform_device.h> |
f63409ae IP |
32 | #include <linux/input.h> |
33 | #include <linux/input/sparse-keymap.h> | |
a4ecbb8a IP |
34 | #include <linux/backlight.h> |
35 | #include <linux/fb.h> | |
773e3206 IP |
36 | #include <linux/debugfs.h> |
37 | #include <linux/seq_file.h> | |
07a4a4fc | 38 | #include <linux/i8042.h> |
85093f79 | 39 | #include <linux/dmi.h> |
b3facd7b | 40 | #include <linux/device.h> |
26bff5f0 | 41 | #include <acpi/video.h> |
58ac7aa0 | 42 | |
c1f73658 | 43 | #define IDEAPAD_RFKILL_DEV_NUM (3) |
58ac7aa0 | 44 | |
ade50296 HWT |
45 | #define BM_CONSERVATION_BIT (5) |
46 | ||
3371f481 IP |
47 | #define CFG_BT_BIT (16) |
48 | #define CFG_3G_BIT (17) | |
49 | #define CFG_WIFI_BIT (18) | |
a84511f7 | 50 | #define CFG_CAMERA_BIT (19) |
3371f481 | 51 | |
74caab99 | 52 | #if IS_ENABLED(CONFIG_ACPI_WMI) |
2d98e0b9 AB |
53 | static const char *const ideapad_wmi_fnesc_events[] = { |
54 | "26CAB2E5-5CF1-46AE-AAC3-4A12B6BA50E6", /* Yoga 3 */ | |
55 | "56322276-8493-4CE8-A783-98C991274F5E", /* Yoga 700 */ | |
56 | }; | |
74caab99 AB |
57 | #endif |
58 | ||
ade50296 HWT |
59 | enum { |
60 | BMCMD_CONSERVATION_ON = 3, | |
61 | BMCMD_CONSERVATION_OFF = 5, | |
62 | }; | |
63 | ||
2be1dc21 IP |
64 | enum { |
65 | VPCCMD_R_VPC1 = 0x10, | |
66 | VPCCMD_R_BL_MAX, | |
67 | VPCCMD_R_BL, | |
68 | VPCCMD_W_BL, | |
69 | VPCCMD_R_WIFI, | |
70 | VPCCMD_W_WIFI, | |
71 | VPCCMD_R_BT, | |
72 | VPCCMD_W_BT, | |
73 | VPCCMD_R_BL_POWER, | |
74 | VPCCMD_R_NOVO, | |
75 | VPCCMD_R_VPC2, | |
76 | VPCCMD_R_TOUCHPAD, | |
77 | VPCCMD_W_TOUCHPAD, | |
78 | VPCCMD_R_CAMERA, | |
79 | VPCCMD_W_CAMERA, | |
80 | VPCCMD_R_3G, | |
81 | VPCCMD_W_3G, | |
82 | VPCCMD_R_ODD, /* 0x21 */ | |
0c7bbeb9 MM |
83 | VPCCMD_W_FAN, |
84 | VPCCMD_R_RF, | |
2be1dc21 | 85 | VPCCMD_W_RF, |
0c7bbeb9 | 86 | VPCCMD_R_FAN = 0x2B, |
296f9fe0 | 87 | VPCCMD_R_SPECIAL_BUTTONS = 0x31, |
2be1dc21 IP |
88 | VPCCMD_W_BL_POWER = 0x33, |
89 | }; | |
90 | ||
331e0ea2 ZR |
91 | struct ideapad_rfk_priv { |
92 | int dev; | |
93 | struct ideapad_private *priv; | |
94 | }; | |
95 | ||
ce326329 | 96 | struct ideapad_private { |
469f6434 | 97 | struct acpi_device *adev; |
c1f73658 | 98 | struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM]; |
331e0ea2 | 99 | struct ideapad_rfk_priv rfk_priv[IDEAPAD_RFKILL_DEV_NUM]; |
98ee6919 | 100 | struct platform_device *platform_device; |
f63409ae | 101 | struct input_dev *inputdev; |
a4ecbb8a | 102 | struct backlight_device *blightdev; |
773e3206 | 103 | struct dentry *debug; |
3371f481 | 104 | unsigned long cfg; |
ce363c2b | 105 | bool has_hw_rfkill_switch; |
2d98e0b9 | 106 | const char *fnesc_guid; |
58ac7aa0 DW |
107 | }; |
108 | ||
bfa97b7d IP |
109 | static bool no_bt_rfkill; |
110 | module_param(no_bt_rfkill, bool, 0444); | |
111 | MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); | |
112 | ||
6a09f21d IP |
113 | /* |
114 | * ACPI Helpers | |
115 | */ | |
116 | #define IDEAPAD_EC_TIMEOUT (100) /* in ms */ | |
117 | ||
118 | static int read_method_int(acpi_handle handle, const char *method, int *val) | |
119 | { | |
120 | acpi_status status; | |
121 | unsigned long long result; | |
122 | ||
123 | status = acpi_evaluate_integer(handle, (char *)method, NULL, &result); | |
124 | if (ACPI_FAILURE(status)) { | |
125 | *val = -1; | |
126 | return -1; | |
127 | } else { | |
128 | *val = result; | |
129 | return 0; | |
130 | } | |
131 | } | |
132 | ||
ade50296 HWT |
133 | static int method_gbmd(acpi_handle handle, unsigned long *ret) |
134 | { | |
135 | int result, val; | |
136 | ||
137 | result = read_method_int(handle, "GBMD", &val); | |
138 | *ret = val; | |
139 | return result; | |
140 | } | |
141 | ||
142 | static int method_sbmc(acpi_handle handle, int cmd) | |
143 | { | |
144 | acpi_status status; | |
145 | ||
146 | status = acpi_execute_simple_method(handle, "SBMC", cmd); | |
147 | return ACPI_FAILURE(status) ? -1 : 0; | |
148 | } | |
149 | ||
6a09f21d IP |
150 | static int method_vpcr(acpi_handle handle, int cmd, int *ret) |
151 | { | |
152 | acpi_status status; | |
153 | unsigned long long result; | |
154 | struct acpi_object_list params; | |
155 | union acpi_object in_obj; | |
156 | ||
157 | params.count = 1; | |
158 | params.pointer = &in_obj; | |
159 | in_obj.type = ACPI_TYPE_INTEGER; | |
160 | in_obj.integer.value = cmd; | |
161 | ||
162 | status = acpi_evaluate_integer(handle, "VPCR", ¶ms, &result); | |
163 | ||
164 | if (ACPI_FAILURE(status)) { | |
165 | *ret = -1; | |
166 | return -1; | |
167 | } else { | |
168 | *ret = result; | |
169 | return 0; | |
170 | } | |
171 | } | |
172 | ||
173 | static int method_vpcw(acpi_handle handle, int cmd, int data) | |
174 | { | |
175 | struct acpi_object_list params; | |
176 | union acpi_object in_obj[2]; | |
177 | acpi_status status; | |
178 | ||
179 | params.count = 2; | |
180 | params.pointer = in_obj; | |
181 | in_obj[0].type = ACPI_TYPE_INTEGER; | |
182 | in_obj[0].integer.value = cmd; | |
183 | in_obj[1].type = ACPI_TYPE_INTEGER; | |
184 | in_obj[1].integer.value = data; | |
185 | ||
186 | status = acpi_evaluate_object(handle, "VPCW", ¶ms, NULL); | |
187 | if (status != AE_OK) | |
188 | return -1; | |
189 | return 0; | |
190 | } | |
191 | ||
192 | static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data) | |
193 | { | |
194 | int val; | |
195 | unsigned long int end_jiffies; | |
196 | ||
197 | if (method_vpcw(handle, 1, cmd)) | |
198 | return -1; | |
199 | ||
200 | for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; | |
201 | time_before(jiffies, end_jiffies);) { | |
202 | schedule(); | |
203 | if (method_vpcr(handle, 1, &val)) | |
204 | return -1; | |
205 | if (val == 0) { | |
206 | if (method_vpcr(handle, 0, &val)) | |
207 | return -1; | |
208 | *data = val; | |
209 | return 0; | |
210 | } | |
211 | } | |
212 | pr_err("timeout in read_ec_cmd\n"); | |
213 | return -1; | |
214 | } | |
215 | ||
216 | static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data) | |
217 | { | |
218 | int val; | |
219 | unsigned long int end_jiffies; | |
220 | ||
221 | if (method_vpcw(handle, 0, data)) | |
222 | return -1; | |
223 | if (method_vpcw(handle, 1, cmd)) | |
224 | return -1; | |
225 | ||
226 | for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; | |
227 | time_before(jiffies, end_jiffies);) { | |
228 | schedule(); | |
229 | if (method_vpcr(handle, 1, &val)) | |
230 | return -1; | |
231 | if (val == 0) | |
232 | return 0; | |
233 | } | |
234 | pr_err("timeout in write_ec_cmd\n"); | |
235 | return -1; | |
236 | } | |
6a09f21d | 237 | |
773e3206 IP |
238 | /* |
239 | * debugfs | |
240 | */ | |
773e3206 IP |
241 | static int debugfs_status_show(struct seq_file *s, void *data) |
242 | { | |
331e0ea2 | 243 | struct ideapad_private *priv = s->private; |
773e3206 IP |
244 | unsigned long value; |
245 | ||
331e0ea2 ZR |
246 | if (!priv) |
247 | return -EINVAL; | |
248 | ||
249 | if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &value)) | |
773e3206 | 250 | seq_printf(s, "Backlight max:\t%lu\n", value); |
331e0ea2 | 251 | if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL, &value)) |
773e3206 | 252 | seq_printf(s, "Backlight now:\t%lu\n", value); |
331e0ea2 | 253 | if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &value)) |
773e3206 IP |
254 | seq_printf(s, "BL power value:\t%s\n", value ? "On" : "Off"); |
255 | seq_printf(s, "=====================\n"); | |
256 | ||
331e0ea2 | 257 | if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value)) |
773e3206 IP |
258 | seq_printf(s, "Radio status:\t%s(%lu)\n", |
259 | value ? "On" : "Off", value); | |
331e0ea2 | 260 | if (!read_ec_data(priv->adev->handle, VPCCMD_R_WIFI, &value)) |
773e3206 IP |
261 | seq_printf(s, "Wifi status:\t%s(%lu)\n", |
262 | value ? "On" : "Off", value); | |
331e0ea2 | 263 | if (!read_ec_data(priv->adev->handle, VPCCMD_R_BT, &value)) |
773e3206 IP |
264 | seq_printf(s, "BT status:\t%s(%lu)\n", |
265 | value ? "On" : "Off", value); | |
331e0ea2 | 266 | if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value)) |
773e3206 IP |
267 | seq_printf(s, "3G status:\t%s(%lu)\n", |
268 | value ? "On" : "Off", value); | |
269 | seq_printf(s, "=====================\n"); | |
270 | ||
331e0ea2 | 271 | if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) |
773e3206 IP |
272 | seq_printf(s, "Touchpad status:%s(%lu)\n", |
273 | value ? "On" : "Off", value); | |
331e0ea2 | 274 | if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value)) |
773e3206 IP |
275 | seq_printf(s, "Camera status:\t%s(%lu)\n", |
276 | value ? "On" : "Off", value); | |
ade50296 HWT |
277 | seq_puts(s, "=====================\n"); |
278 | ||
279 | if (!method_gbmd(priv->adev->handle, &value)) { | |
280 | seq_printf(s, "Conservation mode:\t%s(%lu)\n", | |
281 | test_bit(BM_CONSERVATION_BIT, &value) ? "On" : "Off", | |
282 | value); | |
283 | } | |
773e3206 IP |
284 | |
285 | return 0; | |
286 | } | |
287 | ||
288 | static int debugfs_status_open(struct inode *inode, struct file *file) | |
289 | { | |
331e0ea2 | 290 | return single_open(file, debugfs_status_show, inode->i_private); |
773e3206 IP |
291 | } |
292 | ||
293 | static const struct file_operations debugfs_status_fops = { | |
294 | .owner = THIS_MODULE, | |
295 | .open = debugfs_status_open, | |
296 | .read = seq_read, | |
297 | .llseek = seq_lseek, | |
298 | .release = single_release, | |
299 | }; | |
300 | ||
301 | static int debugfs_cfg_show(struct seq_file *s, void *data) | |
302 | { | |
331e0ea2 ZR |
303 | struct ideapad_private *priv = s->private; |
304 | ||
305 | if (!priv) { | |
773e3206 IP |
306 | seq_printf(s, "cfg: N/A\n"); |
307 | } else { | |
308 | seq_printf(s, "cfg: 0x%.8lX\n\nCapability: ", | |
331e0ea2 ZR |
309 | priv->cfg); |
310 | if (test_bit(CFG_BT_BIT, &priv->cfg)) | |
773e3206 | 311 | seq_printf(s, "Bluetooth "); |
331e0ea2 | 312 | if (test_bit(CFG_3G_BIT, &priv->cfg)) |
773e3206 | 313 | seq_printf(s, "3G "); |
331e0ea2 | 314 | if (test_bit(CFG_WIFI_BIT, &priv->cfg)) |
773e3206 | 315 | seq_printf(s, "Wireless "); |
331e0ea2 | 316 | if (test_bit(CFG_CAMERA_BIT, &priv->cfg)) |
773e3206 IP |
317 | seq_printf(s, "Camera "); |
318 | seq_printf(s, "\nGraphic: "); | |
331e0ea2 | 319 | switch ((priv->cfg)&0x700) { |
773e3206 IP |
320 | case 0x100: |
321 | seq_printf(s, "Intel"); | |
322 | break; | |
323 | case 0x200: | |
324 | seq_printf(s, "ATI"); | |
325 | break; | |
326 | case 0x300: | |
327 | seq_printf(s, "Nvidia"); | |
328 | break; | |
329 | case 0x400: | |
330 | seq_printf(s, "Intel and ATI"); | |
331 | break; | |
332 | case 0x500: | |
333 | seq_printf(s, "Intel and Nvidia"); | |
334 | break; | |
335 | } | |
336 | seq_printf(s, "\n"); | |
337 | } | |
338 | return 0; | |
339 | } | |
340 | ||
341 | static int debugfs_cfg_open(struct inode *inode, struct file *file) | |
342 | { | |
331e0ea2 | 343 | return single_open(file, debugfs_cfg_show, inode->i_private); |
773e3206 IP |
344 | } |
345 | ||
346 | static const struct file_operations debugfs_cfg_fops = { | |
347 | .owner = THIS_MODULE, | |
348 | .open = debugfs_cfg_open, | |
349 | .read = seq_read, | |
350 | .llseek = seq_lseek, | |
351 | .release = single_release, | |
352 | }; | |
353 | ||
b859f159 | 354 | static int ideapad_debugfs_init(struct ideapad_private *priv) |
773e3206 IP |
355 | { |
356 | struct dentry *node; | |
357 | ||
358 | priv->debug = debugfs_create_dir("ideapad", NULL); | |
359 | if (priv->debug == NULL) { | |
360 | pr_err("failed to create debugfs directory"); | |
361 | goto errout; | |
362 | } | |
363 | ||
331e0ea2 | 364 | node = debugfs_create_file("cfg", S_IRUGO, priv->debug, priv, |
773e3206 IP |
365 | &debugfs_cfg_fops); |
366 | if (!node) { | |
367 | pr_err("failed to create cfg in debugfs"); | |
368 | goto errout; | |
369 | } | |
370 | ||
331e0ea2 | 371 | node = debugfs_create_file("status", S_IRUGO, priv->debug, priv, |
773e3206 IP |
372 | &debugfs_status_fops); |
373 | if (!node) { | |
a5c3892f | 374 | pr_err("failed to create status in debugfs"); |
773e3206 IP |
375 | goto errout; |
376 | } | |
377 | ||
378 | return 0; | |
379 | ||
380 | errout: | |
381 | return -ENOMEM; | |
382 | } | |
383 | ||
384 | static void ideapad_debugfs_exit(struct ideapad_private *priv) | |
385 | { | |
386 | debugfs_remove_recursive(priv->debug); | |
387 | priv->debug = NULL; | |
388 | } | |
389 | ||
a4b5a279 | 390 | /* |
3371f481 | 391 | * sysfs |
a4b5a279 | 392 | */ |
58ac7aa0 DW |
393 | static ssize_t show_ideapad_cam(struct device *dev, |
394 | struct device_attribute *attr, | |
395 | char *buf) | |
396 | { | |
26c81d5c | 397 | unsigned long result; |
331e0ea2 | 398 | struct ideapad_private *priv = dev_get_drvdata(dev); |
58ac7aa0 | 399 | |
331e0ea2 | 400 | if (read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result)) |
26c81d5c IP |
401 | return sprintf(buf, "-1\n"); |
402 | return sprintf(buf, "%lu\n", result); | |
58ac7aa0 DW |
403 | } |
404 | ||
405 | static ssize_t store_ideapad_cam(struct device *dev, | |
406 | struct device_attribute *attr, | |
407 | const char *buf, size_t count) | |
408 | { | |
409 | int ret, state; | |
331e0ea2 | 410 | struct ideapad_private *priv = dev_get_drvdata(dev); |
58ac7aa0 DW |
411 | |
412 | if (!count) | |
413 | return 0; | |
414 | if (sscanf(buf, "%i", &state) != 1) | |
415 | return -EINVAL; | |
331e0ea2 | 416 | ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state); |
58ac7aa0 | 417 | if (ret < 0) |
0c7bbeb9 | 418 | return -EIO; |
58ac7aa0 DW |
419 | return count; |
420 | } | |
421 | ||
422 | static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam); | |
423 | ||
0c7bbeb9 MM |
424 | static ssize_t show_ideapad_fan(struct device *dev, |
425 | struct device_attribute *attr, | |
426 | char *buf) | |
427 | { | |
428 | unsigned long result; | |
331e0ea2 | 429 | struct ideapad_private *priv = dev_get_drvdata(dev); |
0c7bbeb9 | 430 | |
331e0ea2 | 431 | if (read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result)) |
0c7bbeb9 MM |
432 | return sprintf(buf, "-1\n"); |
433 | return sprintf(buf, "%lu\n", result); | |
434 | } | |
435 | ||
436 | static ssize_t store_ideapad_fan(struct device *dev, | |
437 | struct device_attribute *attr, | |
438 | const char *buf, size_t count) | |
439 | { | |
440 | int ret, state; | |
331e0ea2 | 441 | struct ideapad_private *priv = dev_get_drvdata(dev); |
0c7bbeb9 MM |
442 | |
443 | if (!count) | |
444 | return 0; | |
445 | if (sscanf(buf, "%i", &state) != 1) | |
446 | return -EINVAL; | |
447 | if (state < 0 || state > 4 || state == 3) | |
448 | return -EINVAL; | |
331e0ea2 | 449 | ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state); |
0c7bbeb9 MM |
450 | if (ret < 0) |
451 | return -EIO; | |
452 | return count; | |
453 | } | |
454 | ||
455 | static DEVICE_ATTR(fan_mode, 0644, show_ideapad_fan, store_ideapad_fan); | |
456 | ||
36ac0d43 RRS |
457 | static ssize_t touchpad_show(struct device *dev, |
458 | struct device_attribute *attr, | |
459 | char *buf) | |
460 | { | |
461 | struct ideapad_private *priv = dev_get_drvdata(dev); | |
462 | unsigned long result; | |
463 | ||
464 | if (read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result)) | |
465 | return sprintf(buf, "-1\n"); | |
466 | return sprintf(buf, "%lu\n", result); | |
467 | } | |
468 | ||
46936fd6 AB |
469 | /* Switch to RO for now: It might be revisited in the future */ |
470 | static ssize_t __maybe_unused touchpad_store(struct device *dev, | |
471 | struct device_attribute *attr, | |
472 | const char *buf, size_t count) | |
36ac0d43 RRS |
473 | { |
474 | struct ideapad_private *priv = dev_get_drvdata(dev); | |
475 | bool state; | |
476 | int ret; | |
477 | ||
478 | ret = kstrtobool(buf, &state); | |
479 | if (ret) | |
480 | return ret; | |
481 | ||
482 | ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, state); | |
483 | if (ret < 0) | |
484 | return -EIO; | |
485 | return count; | |
486 | } | |
487 | ||
7f363145 | 488 | static DEVICE_ATTR_RO(touchpad); |
36ac0d43 | 489 | |
ade50296 HWT |
490 | static ssize_t conservation_mode_show(struct device *dev, |
491 | struct device_attribute *attr, | |
492 | char *buf) | |
493 | { | |
494 | struct ideapad_private *priv = dev_get_drvdata(dev); | |
495 | unsigned long result; | |
496 | ||
497 | if (method_gbmd(priv->adev->handle, &result)) | |
498 | return sprintf(buf, "-1\n"); | |
499 | return sprintf(buf, "%u\n", test_bit(BM_CONSERVATION_BIT, &result)); | |
500 | } | |
501 | ||
502 | static ssize_t conservation_mode_store(struct device *dev, | |
503 | struct device_attribute *attr, | |
504 | const char *buf, size_t count) | |
505 | { | |
506 | struct ideapad_private *priv = dev_get_drvdata(dev); | |
507 | bool state; | |
508 | int ret; | |
509 | ||
510 | ret = kstrtobool(buf, &state); | |
511 | if (ret) | |
512 | return ret; | |
513 | ||
514 | ret = method_sbmc(priv->adev->handle, state ? | |
515 | BMCMD_CONSERVATION_ON : | |
516 | BMCMD_CONSERVATION_OFF); | |
517 | if (ret < 0) | |
518 | return -EIO; | |
519 | return count; | |
520 | } | |
521 | ||
522 | static DEVICE_ATTR_RW(conservation_mode); | |
523 | ||
3371f481 IP |
524 | static struct attribute *ideapad_attributes[] = { |
525 | &dev_attr_camera_power.attr, | |
0c7bbeb9 | 526 | &dev_attr_fan_mode.attr, |
36ac0d43 | 527 | &dev_attr_touchpad.attr, |
ade50296 | 528 | &dev_attr_conservation_mode.attr, |
3371f481 IP |
529 | NULL |
530 | }; | |
531 | ||
587a1f16 | 532 | static umode_t ideapad_is_visible(struct kobject *kobj, |
a84511f7 IP |
533 | struct attribute *attr, |
534 | int idx) | |
535 | { | |
536 | struct device *dev = container_of(kobj, struct device, kobj); | |
537 | struct ideapad_private *priv = dev_get_drvdata(dev); | |
538 | bool supported; | |
539 | ||
540 | if (attr == &dev_attr_camera_power.attr) | |
541 | supported = test_bit(CFG_CAMERA_BIT, &(priv->cfg)); | |
0c7bbeb9 MM |
542 | else if (attr == &dev_attr_fan_mode.attr) { |
543 | unsigned long value; | |
331e0ea2 ZR |
544 | supported = !read_ec_data(priv->adev->handle, VPCCMD_R_FAN, |
545 | &value); | |
ade50296 HWT |
546 | } else if (attr == &dev_attr_conservation_mode.attr) { |
547 | supported = acpi_has_method(priv->adev->handle, "GBMD") && | |
548 | acpi_has_method(priv->adev->handle, "SBMC"); | |
0c7bbeb9 | 549 | } else |
a84511f7 IP |
550 | supported = true; |
551 | ||
552 | return supported ? attr->mode : 0; | |
553 | } | |
554 | ||
49458e83 | 555 | static const struct attribute_group ideapad_attribute_group = { |
a84511f7 | 556 | .is_visible = ideapad_is_visible, |
3371f481 IP |
557 | .attrs = ideapad_attributes |
558 | }; | |
559 | ||
a4b5a279 IP |
560 | /* |
561 | * Rfkill | |
562 | */ | |
c1f73658 IP |
563 | struct ideapad_rfk_data { |
564 | char *name; | |
565 | int cfgbit; | |
566 | int opcode; | |
567 | int type; | |
568 | }; | |
569 | ||
b3d94d70 | 570 | static const struct ideapad_rfk_data ideapad_rfk_data[] = { |
2be1dc21 IP |
571 | { "ideapad_wlan", CFG_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN }, |
572 | { "ideapad_bluetooth", CFG_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH }, | |
573 | { "ideapad_3g", CFG_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN }, | |
c1f73658 IP |
574 | }; |
575 | ||
58ac7aa0 DW |
576 | static int ideapad_rfk_set(void *data, bool blocked) |
577 | { | |
331e0ea2 | 578 | struct ideapad_rfk_priv *priv = data; |
4b200b46 | 579 | int opcode = ideapad_rfk_data[priv->dev].opcode; |
fa08359e | 580 | |
4b200b46 | 581 | return write_ec_cmd(priv->priv->adev->handle, opcode, !blocked); |
58ac7aa0 DW |
582 | } |
583 | ||
3d59dfcd | 584 | static const struct rfkill_ops ideapad_rfk_ops = { |
58ac7aa0 DW |
585 | .set_block = ideapad_rfk_set, |
586 | }; | |
587 | ||
923de84a | 588 | static void ideapad_sync_rfk_state(struct ideapad_private *priv) |
58ac7aa0 | 589 | { |
ce363c2b | 590 | unsigned long hw_blocked = 0; |
58ac7aa0 DW |
591 | int i; |
592 | ||
ce363c2b HG |
593 | if (priv->has_hw_rfkill_switch) { |
594 | if (read_ec_data(priv->adev->handle, VPCCMD_R_RF, &hw_blocked)) | |
595 | return; | |
596 | hw_blocked = !hw_blocked; | |
597 | } | |
58ac7aa0 | 598 | |
c1f73658 | 599 | for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) |
ce326329 | 600 | if (priv->rfk[i]) |
2b7266bd | 601 | rfkill_set_hw_state(priv->rfk[i], hw_blocked); |
58ac7aa0 DW |
602 | } |
603 | ||
75a11f11 | 604 | static int ideapad_register_rfkill(struct ideapad_private *priv, int dev) |
58ac7aa0 DW |
605 | { |
606 | int ret; | |
2b7266bd | 607 | unsigned long sw_blocked; |
58ac7aa0 | 608 | |
bfa97b7d IP |
609 | if (no_bt_rfkill && |
610 | (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) { | |
611 | /* Force to enable bluetooth when no_bt_rfkill=1 */ | |
331e0ea2 | 612 | write_ec_cmd(priv->adev->handle, |
bfa97b7d IP |
613 | ideapad_rfk_data[dev].opcode, 1); |
614 | return 0; | |
615 | } | |
331e0ea2 ZR |
616 | priv->rfk_priv[dev].dev = dev; |
617 | priv->rfk_priv[dev].priv = priv; | |
bfa97b7d | 618 | |
75a11f11 | 619 | priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, |
b5c37b79 | 620 | &priv->platform_device->dev, |
75a11f11 ZR |
621 | ideapad_rfk_data[dev].type, |
622 | &ideapad_rfk_ops, | |
331e0ea2 | 623 | &priv->rfk_priv[dev]); |
ce326329 | 624 | if (!priv->rfk[dev]) |
58ac7aa0 DW |
625 | return -ENOMEM; |
626 | ||
331e0ea2 | 627 | if (read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode-1, |
2b7266bd IP |
628 | &sw_blocked)) { |
629 | rfkill_init_sw_state(priv->rfk[dev], 0); | |
630 | } else { | |
631 | sw_blocked = !sw_blocked; | |
632 | rfkill_init_sw_state(priv->rfk[dev], sw_blocked); | |
633 | } | |
634 | ||
ce326329 | 635 | ret = rfkill_register(priv->rfk[dev]); |
58ac7aa0 | 636 | if (ret) { |
ce326329 | 637 | rfkill_destroy(priv->rfk[dev]); |
58ac7aa0 DW |
638 | return ret; |
639 | } | |
640 | return 0; | |
641 | } | |
642 | ||
75a11f11 | 643 | static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev) |
58ac7aa0 | 644 | { |
ce326329 | 645 | if (!priv->rfk[dev]) |
58ac7aa0 DW |
646 | return; |
647 | ||
ce326329 DW |
648 | rfkill_unregister(priv->rfk[dev]); |
649 | rfkill_destroy(priv->rfk[dev]); | |
58ac7aa0 DW |
650 | } |
651 | ||
98ee6919 IP |
652 | /* |
653 | * Platform device | |
654 | */ | |
b5c37b79 | 655 | static int ideapad_sysfs_init(struct ideapad_private *priv) |
98ee6919 | 656 | { |
b5c37b79 | 657 | return sysfs_create_group(&priv->platform_device->dev.kobj, |
c9f718d0 | 658 | &ideapad_attribute_group); |
98ee6919 IP |
659 | } |
660 | ||
b5c37b79 | 661 | static void ideapad_sysfs_exit(struct ideapad_private *priv) |
98ee6919 | 662 | { |
8693ae84 | 663 | sysfs_remove_group(&priv->platform_device->dev.kobj, |
c9f718d0 | 664 | &ideapad_attribute_group); |
98ee6919 | 665 | } |
98ee6919 | 666 | |
f63409ae IP |
667 | /* |
668 | * input device | |
669 | */ | |
670 | static const struct key_entry ideapad_keymap[] = { | |
f43d9ec0 | 671 | { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } }, |
296f9fe0 | 672 | { KE_KEY, 7, { KEY_CAMERA } }, |
48f67d62 | 673 | { KE_KEY, 8, { KEY_MICMUTE } }, |
296f9fe0 | 674 | { KE_KEY, 11, { KEY_F16 } }, |
f43d9ec0 IP |
675 | { KE_KEY, 13, { KEY_WLAN } }, |
676 | { KE_KEY, 16, { KEY_PROG1 } }, | |
677 | { KE_KEY, 17, { KEY_PROG2 } }, | |
296f9fe0 MM |
678 | { KE_KEY, 64, { KEY_PROG3 } }, |
679 | { KE_KEY, 65, { KEY_PROG4 } }, | |
07a4a4fc MM |
680 | { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, |
681 | { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, | |
74caab99 AB |
682 | { KE_KEY, 128, { KEY_ESC } }, |
683 | ||
f63409ae IP |
684 | { KE_END, 0 }, |
685 | }; | |
686 | ||
b859f159 | 687 | static int ideapad_input_init(struct ideapad_private *priv) |
f63409ae IP |
688 | { |
689 | struct input_dev *inputdev; | |
690 | int error; | |
691 | ||
692 | inputdev = input_allocate_device(); | |
b222cca6 | 693 | if (!inputdev) |
f63409ae | 694 | return -ENOMEM; |
f63409ae IP |
695 | |
696 | inputdev->name = "Ideapad extra buttons"; | |
697 | inputdev->phys = "ideapad/input0"; | |
698 | inputdev->id.bustype = BUS_HOST; | |
8693ae84 | 699 | inputdev->dev.parent = &priv->platform_device->dev; |
f63409ae IP |
700 | |
701 | error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); | |
702 | if (error) { | |
703 | pr_err("Unable to setup input device keymap\n"); | |
704 | goto err_free_dev; | |
705 | } | |
706 | ||
707 | error = input_register_device(inputdev); | |
708 | if (error) { | |
709 | pr_err("Unable to register input device\n"); | |
c973d4b5 | 710 | goto err_free_dev; |
f63409ae IP |
711 | } |
712 | ||
8693ae84 | 713 | priv->inputdev = inputdev; |
f63409ae IP |
714 | return 0; |
715 | ||
f63409ae IP |
716 | err_free_dev: |
717 | input_free_device(inputdev); | |
718 | return error; | |
719 | } | |
720 | ||
7451a55a | 721 | static void ideapad_input_exit(struct ideapad_private *priv) |
f63409ae | 722 | { |
8693ae84 IP |
723 | input_unregister_device(priv->inputdev); |
724 | priv->inputdev = NULL; | |
f63409ae IP |
725 | } |
726 | ||
8693ae84 IP |
727 | static void ideapad_input_report(struct ideapad_private *priv, |
728 | unsigned long scancode) | |
f63409ae | 729 | { |
8693ae84 | 730 | sparse_keymap_report_event(priv->inputdev, scancode, 1, true); |
f63409ae | 731 | } |
f63409ae | 732 | |
f43d9ec0 IP |
733 | static void ideapad_input_novokey(struct ideapad_private *priv) |
734 | { | |
735 | unsigned long long_pressed; | |
736 | ||
331e0ea2 | 737 | if (read_ec_data(priv->adev->handle, VPCCMD_R_NOVO, &long_pressed)) |
f43d9ec0 IP |
738 | return; |
739 | if (long_pressed) | |
740 | ideapad_input_report(priv, 17); | |
741 | else | |
742 | ideapad_input_report(priv, 16); | |
743 | } | |
744 | ||
296f9fe0 MM |
745 | static void ideapad_check_special_buttons(struct ideapad_private *priv) |
746 | { | |
747 | unsigned long bit, value; | |
748 | ||
331e0ea2 | 749 | read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value); |
296f9fe0 MM |
750 | |
751 | for (bit = 0; bit < 16; bit++) { | |
752 | if (test_bit(bit, &value)) { | |
753 | switch (bit) { | |
a1ec56ed MM |
754 | case 0: /* Z580 */ |
755 | case 6: /* Z570 */ | |
296f9fe0 MM |
756 | /* Thermal Management button */ |
757 | ideapad_input_report(priv, 65); | |
758 | break; | |
759 | case 1: | |
760 | /* OneKey Theater button */ | |
761 | ideapad_input_report(priv, 64); | |
762 | break; | |
a1ec56ed MM |
763 | default: |
764 | pr_info("Unknown special button: %lu\n", bit); | |
765 | break; | |
296f9fe0 MM |
766 | } |
767 | } | |
768 | } | |
769 | } | |
770 | ||
a4ecbb8a IP |
771 | /* |
772 | * backlight | |
773 | */ | |
774 | static int ideapad_backlight_get_brightness(struct backlight_device *blightdev) | |
775 | { | |
331e0ea2 | 776 | struct ideapad_private *priv = bl_get_data(blightdev); |
a4ecbb8a IP |
777 | unsigned long now; |
778 | ||
331e0ea2 ZR |
779 | if (!priv) |
780 | return -EINVAL; | |
781 | ||
782 | if (read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now)) | |
a4ecbb8a IP |
783 | return -EIO; |
784 | return now; | |
785 | } | |
786 | ||
787 | static int ideapad_backlight_update_status(struct backlight_device *blightdev) | |
788 | { | |
331e0ea2 ZR |
789 | struct ideapad_private *priv = bl_get_data(blightdev); |
790 | ||
791 | if (!priv) | |
792 | return -EINVAL; | |
793 | ||
794 | if (write_ec_cmd(priv->adev->handle, VPCCMD_W_BL, | |
2be1dc21 | 795 | blightdev->props.brightness)) |
a4ecbb8a | 796 | return -EIO; |
331e0ea2 | 797 | if (write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER, |
a4ecbb8a IP |
798 | blightdev->props.power == FB_BLANK_POWERDOWN ? 0 : 1)) |
799 | return -EIO; | |
800 | ||
801 | return 0; | |
802 | } | |
803 | ||
804 | static const struct backlight_ops ideapad_backlight_ops = { | |
805 | .get_brightness = ideapad_backlight_get_brightness, | |
806 | .update_status = ideapad_backlight_update_status, | |
807 | }; | |
808 | ||
809 | static int ideapad_backlight_init(struct ideapad_private *priv) | |
810 | { | |
811 | struct backlight_device *blightdev; | |
812 | struct backlight_properties props; | |
813 | unsigned long max, now, power; | |
814 | ||
331e0ea2 | 815 | if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max)) |
a4ecbb8a | 816 | return -EIO; |
331e0ea2 | 817 | if (read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now)) |
a4ecbb8a | 818 | return -EIO; |
331e0ea2 | 819 | if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) |
a4ecbb8a IP |
820 | return -EIO; |
821 | ||
822 | memset(&props, 0, sizeof(struct backlight_properties)); | |
823 | props.max_brightness = max; | |
824 | props.type = BACKLIGHT_PLATFORM; | |
825 | blightdev = backlight_device_register("ideapad", | |
826 | &priv->platform_device->dev, | |
827 | priv, | |
828 | &ideapad_backlight_ops, | |
829 | &props); | |
830 | if (IS_ERR(blightdev)) { | |
831 | pr_err("Could not register backlight device\n"); | |
832 | return PTR_ERR(blightdev); | |
833 | } | |
834 | ||
835 | priv->blightdev = blightdev; | |
836 | blightdev->props.brightness = now; | |
837 | blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; | |
838 | backlight_update_status(blightdev); | |
839 | ||
840 | return 0; | |
841 | } | |
842 | ||
843 | static void ideapad_backlight_exit(struct ideapad_private *priv) | |
844 | { | |
00981810 | 845 | backlight_device_unregister(priv->blightdev); |
a4ecbb8a IP |
846 | priv->blightdev = NULL; |
847 | } | |
848 | ||
849 | static void ideapad_backlight_notify_power(struct ideapad_private *priv) | |
850 | { | |
851 | unsigned long power; | |
852 | struct backlight_device *blightdev = priv->blightdev; | |
853 | ||
d4afc775 RB |
854 | if (!blightdev) |
855 | return; | |
331e0ea2 | 856 | if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) |
a4ecbb8a IP |
857 | return; |
858 | blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; | |
859 | } | |
860 | ||
861 | static void ideapad_backlight_notify_brightness(struct ideapad_private *priv) | |
862 | { | |
863 | unsigned long now; | |
864 | ||
865 | /* if we control brightness via acpi video driver */ | |
866 | if (priv->blightdev == NULL) { | |
331e0ea2 | 867 | read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); |
a4ecbb8a IP |
868 | return; |
869 | } | |
870 | ||
871 | backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY); | |
872 | } | |
873 | ||
a4b5a279 IP |
874 | /* |
875 | * module init/exit | |
876 | */ | |
75a11f11 | 877 | static void ideapad_sync_touchpad_state(struct ideapad_private *priv) |
07a4a4fc | 878 | { |
07a4a4fc MM |
879 | unsigned long value; |
880 | ||
881 | /* Without reading from EC touchpad LED doesn't switch state */ | |
75a11f11 | 882 | if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) { |
07a4a4fc MM |
883 | /* Some IdeaPads don't really turn off touchpad - they only |
884 | * switch the LED state. We (de)activate KBC AUX port to turn | |
885 | * touchpad off and on. We send KEY_TOUCHPAD_OFF and | |
886 | * KEY_TOUCHPAD_ON to not to get out of sync with LED */ | |
887 | unsigned char param; | |
888 | i8042_command(¶m, value ? I8042_CMD_AUX_ENABLE : | |
889 | I8042_CMD_AUX_DISABLE); | |
890 | ideapad_input_report(priv, value ? 67 : 66); | |
891 | } | |
892 | } | |
893 | ||
b5c37b79 ZR |
894 | static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) |
895 | { | |
896 | struct ideapad_private *priv = data; | |
897 | unsigned long vpc1, vpc2, vpc_bit; | |
898 | ||
899 | if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1)) | |
900 | return; | |
901 | if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2)) | |
902 | return; | |
903 | ||
904 | vpc1 = (vpc2 << 8) | vpc1; | |
905 | for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) { | |
906 | if (test_bit(vpc_bit, &vpc1)) { | |
907 | switch (vpc_bit) { | |
908 | case 9: | |
909 | ideapad_sync_rfk_state(priv); | |
910 | break; | |
911 | case 13: | |
912 | case 11: | |
48f67d62 | 913 | case 8: |
b5c37b79 ZR |
914 | case 7: |
915 | case 6: | |
916 | ideapad_input_report(priv, vpc_bit); | |
917 | break; | |
918 | case 5: | |
919 | ideapad_sync_touchpad_state(priv); | |
920 | break; | |
921 | case 4: | |
922 | ideapad_backlight_notify_brightness(priv); | |
923 | break; | |
924 | case 3: | |
925 | ideapad_input_novokey(priv); | |
926 | break; | |
927 | case 2: | |
928 | ideapad_backlight_notify_power(priv); | |
929 | break; | |
930 | case 0: | |
931 | ideapad_check_special_buttons(priv); | |
932 | break; | |
3cfd956b HWT |
933 | case 1: |
934 | /* Some IdeaPads report event 1 every ~20 | |
935 | * seconds while on battery power; some | |
936 | * report this when changing to/from tablet | |
937 | * mode. Squelch this event. | |
938 | */ | |
939 | break; | |
b5c37b79 ZR |
940 | default: |
941 | pr_info("Unknown event: %lu\n", vpc_bit); | |
942 | } | |
943 | } | |
944 | } | |
945 | } | |
946 | ||
74caab99 AB |
947 | #if IS_ENABLED(CONFIG_ACPI_WMI) |
948 | static void ideapad_wmi_notify(u32 value, void *context) | |
949 | { | |
950 | switch (value) { | |
951 | case 128: | |
952 | ideapad_input_report(context, value); | |
953 | break; | |
954 | default: | |
955 | pr_info("Unknown WMI event %u\n", value); | |
956 | } | |
957 | } | |
958 | #endif | |
959 | ||
ce363c2b HG |
960 | /* |
961 | * Some ideapads don't have a hardware rfkill switch, reading VPCCMD_R_RF | |
962 | * always results in 0 on these models, causing ideapad_laptop to wrongly | |
963 | * report all radios as hardware-blocked. | |
964 | */ | |
b3d94d70 | 965 | static const struct dmi_system_id no_hw_rfkill_list[] = { |
9b071a43 PC |
966 | { |
967 | .ident = "Lenovo G40-30", | |
968 | .matches = { | |
969 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
970 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G40-30"), | |
971 | }, | |
972 | }, | |
4fa9dabc DT |
973 | { |
974 | .ident = "Lenovo G50-30", | |
975 | .matches = { | |
976 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
977 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G50-30"), | |
978 | }, | |
979 | }, | |
710c059c YJ |
980 | { |
981 | .ident = "Lenovo V310-14IKB", | |
982 | .matches = { | |
983 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
984 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-14IKB"), | |
985 | }, | |
986 | }, | |
987 | { | |
988 | .ident = "Lenovo V310-14ISK", | |
989 | .matches = { | |
990 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
991 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-14ISK"), | |
992 | }, | |
993 | }, | |
994 | { | |
995 | .ident = "Lenovo V310-15IKB", | |
996 | .matches = { | |
997 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
998 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-15IKB"), | |
999 | }, | |
1000 | }, | |
ccc7179f AS |
1001 | { |
1002 | .ident = "Lenovo V310-15ISK", | |
1003 | .matches = { | |
5e8f42aa AS |
1004 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), |
1005 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-15ISK"), | |
ccc7179f AS |
1006 | }, |
1007 | }, | |
0df4b805 SE |
1008 | { |
1009 | .ident = "Lenovo V510-15IKB", | |
1010 | .matches = { | |
1011 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1012 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V510-15IKB"), | |
1013 | }, | |
1014 | }, | |
710c059c YJ |
1015 | { |
1016 | .ident = "Lenovo ideapad 300-15IBR", | |
1017 | .matches = { | |
1018 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1019 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 300-15IBR"), | |
1020 | }, | |
1021 | }, | |
1022 | { | |
1023 | .ident = "Lenovo ideapad 300-15IKB", | |
1024 | .matches = { | |
1025 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1026 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 300-15IKB"), | |
1027 | }, | |
1028 | }, | |
1029 | { | |
1030 | .ident = "Lenovo ideapad 300S-11IBR", | |
1031 | .matches = { | |
1032 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1033 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 300S-11BR"), | |
1034 | }, | |
1035 | }, | |
1036 | { | |
1037 | .ident = "Lenovo ideapad 310-15ABR", | |
1038 | .matches = { | |
1039 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1040 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15ABR"), | |
1041 | }, | |
1042 | }, | |
1043 | { | |
1044 | .ident = "Lenovo ideapad 310-15IAP", | |
1045 | .matches = { | |
1046 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1047 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15IAP"), | |
1048 | }, | |
1049 | }, | |
1f3bc53d SR |
1050 | { |
1051 | .ident = "Lenovo ideapad 310-15IKB", | |
1052 | .matches = { | |
5e8f42aa AS |
1053 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), |
1054 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15IKB"), | |
1f3bc53d SR |
1055 | }, |
1056 | }, | |
710c059c YJ |
1057 | { |
1058 | .ident = "Lenovo ideapad 310-15ISK", | |
1059 | .matches = { | |
1060 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1061 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15ISK"), | |
1062 | }, | |
1063 | }, | |
1064 | { | |
1065 | .ident = "Lenovo ideapad Y700-14ISK", | |
1066 | .matches = { | |
1067 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1068 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-14ISK"), | |
1069 | }, | |
1070 | }, | |
e2970468 | 1071 | { |
1072 | .ident = "Lenovo ideapad Y700-15ACZ", | |
1073 | .matches = { | |
1074 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1075 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-15ACZ"), | |
1076 | }, | |
1077 | }, | |
4db9675d JD |
1078 | { |
1079 | .ident = "Lenovo ideapad Y700-15ISK", | |
1080 | .matches = { | |
1081 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1082 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-15ISK"), | |
1083 | }, | |
1084 | }, | |
1085 | { | |
1086 | .ident = "Lenovo ideapad Y700 Touch-15ISK", | |
1087 | .matches = { | |
1088 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1089 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700 Touch-15ISK"), | |
1090 | }, | |
1091 | }, | |
edde316a JB |
1092 | { |
1093 | .ident = "Lenovo ideapad Y700-17ISK", | |
1094 | .matches = { | |
1095 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1096 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-17ISK"), | |
1097 | }, | |
1098 | }, | |
5d9f40b5 OL |
1099 | { |
1100 | .ident = "Lenovo Legion Y520-15IKBN", | |
1101 | .matches = { | |
1102 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1103 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Y520-15IKBN"), | |
1104 | }, | |
1105 | }, | |
b2f2fe20 OL |
1106 | { |
1107 | .ident = "Lenovo Legion Y720-15IKBN", | |
1108 | .matches = { | |
1109 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1110 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Y720-15IKBN"), | |
1111 | }, | |
1112 | }, | |
85093f79 | 1113 | { |
ce363c2b | 1114 | .ident = "Lenovo Yoga 2 11 / 13 / Pro", |
85093f79 HG |
1115 | .matches = { |
1116 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
ce363c2b | 1117 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Yoga 2"), |
85093f79 HG |
1118 | }, |
1119 | }, | |
fa92a31b | 1120 | { |
c789fffc | 1121 | .ident = "Lenovo Yoga 2 11 / 13 / Pro", |
fa92a31b HG |
1122 | .matches = { |
1123 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
c789fffc | 1124 | DMI_MATCH(DMI_BOARD_NAME, "Yoga2"), |
fa92a31b HG |
1125 | }, |
1126 | }, | |
6d212b8a | 1127 | { |
c789fffc | 1128 | .ident = "Lenovo Yoga 3 1170 / 1470", |
6d212b8a SK |
1129 | .matches = { |
1130 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
c789fffc | 1131 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Yoga 3"), |
6d212b8a SK |
1132 | }, |
1133 | }, | |
725c7f61 SM |
1134 | { |
1135 | .ident = "Lenovo Yoga 3 Pro 1370", | |
1136 | .matches = { | |
1137 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
c789fffc | 1138 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 3"), |
725c7f61 SM |
1139 | }, |
1140 | }, | |
6b31de3e JB |
1141 | { |
1142 | .ident = "Lenovo Yoga 700", | |
1143 | .matches = { | |
1144 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1145 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 700"), | |
1146 | }, | |
1147 | }, | |
f71c882d HG |
1148 | { |
1149 | .ident = "Lenovo Yoga 900", | |
1150 | .matches = { | |
1151 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1152 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 900"), | |
1153 | }, | |
1154 | }, | |
446647d4 MW |
1155 | { |
1156 | .ident = "Lenovo Yoga 900", | |
1157 | .matches = { | |
1158 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1159 | DMI_MATCH(DMI_BOARD_NAME, "VIUU4"), | |
1160 | }, | |
1161 | }, | |
40c30bbf BM |
1162 | { |
1163 | .ident = "Lenovo YOGA 910-13IKB", | |
1164 | .matches = { | |
1165 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
1166 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 910-13IKB"), | |
1167 | }, | |
1168 | }, | |
85093f79 HG |
1169 | {} |
1170 | }; | |
1171 | ||
b5c37b79 | 1172 | static int ideapad_acpi_add(struct platform_device *pdev) |
58ac7aa0 | 1173 | { |
3371f481 | 1174 | int ret, i; |
57f9616b | 1175 | int cfg; |
ce326329 | 1176 | struct ideapad_private *priv; |
b5c37b79 ZR |
1177 | struct acpi_device *adev; |
1178 | ||
1179 | ret = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev); | |
1180 | if (ret) | |
1181 | return -ENODEV; | |
58ac7aa0 | 1182 | |
469f6434 | 1183 | if (read_method_int(adev->handle, "_CFG", &cfg)) |
6f8371c0 IP |
1184 | return -ENODEV; |
1185 | ||
b3facd7b | 1186 | priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); |
ce326329 DW |
1187 | if (!priv) |
1188 | return -ENOMEM; | |
b5c37b79 ZR |
1189 | |
1190 | dev_set_drvdata(&pdev->dev, priv); | |
3371f481 | 1191 | priv->cfg = cfg; |
469f6434 | 1192 | priv->adev = adev; |
b5c37b79 | 1193 | priv->platform_device = pdev; |
ce363c2b | 1194 | priv->has_hw_rfkill_switch = !dmi_check_system(no_hw_rfkill_list); |
98ee6919 | 1195 | |
b5c37b79 | 1196 | ret = ideapad_sysfs_init(priv); |
98ee6919 | 1197 | if (ret) |
b3facd7b | 1198 | return ret; |
ce326329 | 1199 | |
773e3206 IP |
1200 | ret = ideapad_debugfs_init(priv); |
1201 | if (ret) | |
1202 | goto debugfs_failed; | |
1203 | ||
8693ae84 | 1204 | ret = ideapad_input_init(priv); |
f63409ae IP |
1205 | if (ret) |
1206 | goto input_failed; | |
1207 | ||
ce363c2b HG |
1208 | /* |
1209 | * On some models without a hw-switch (the yoga 2 13 at least) | |
1210 | * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work. | |
1211 | */ | |
1212 | if (!priv->has_hw_rfkill_switch) | |
1213 | write_ec_cmd(priv->adev->handle, VPCCMD_W_RF, 1); | |
1214 | ||
1215 | for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) | |
1216 | if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg)) | |
1217 | ideapad_register_rfkill(priv, i); | |
1218 | ||
923de84a | 1219 | ideapad_sync_rfk_state(priv); |
75a11f11 | 1220 | ideapad_sync_touchpad_state(priv); |
c9f718d0 | 1221 | |
26bff5f0 | 1222 | if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { |
a4ecbb8a IP |
1223 | ret = ideapad_backlight_init(priv); |
1224 | if (ret && ret != -ENODEV) | |
1225 | goto backlight_failed; | |
1226 | } | |
b5c37b79 ZR |
1227 | ret = acpi_install_notify_handler(adev->handle, |
1228 | ACPI_DEVICE_NOTIFY, ideapad_acpi_notify, priv); | |
1229 | if (ret) | |
1230 | goto notification_failed; | |
2d98e0b9 | 1231 | |
74caab99 | 1232 | #if IS_ENABLED(CONFIG_ACPI_WMI) |
2d98e0b9 AB |
1233 | for (i = 0; i < ARRAY_SIZE(ideapad_wmi_fnesc_events); i++) { |
1234 | ret = wmi_install_notify_handler(ideapad_wmi_fnesc_events[i], | |
1235 | ideapad_wmi_notify, priv); | |
1236 | if (ret == AE_OK) { | |
1237 | priv->fnesc_guid = ideapad_wmi_fnesc_events[i]; | |
1238 | break; | |
1239 | } | |
1240 | } | |
74caab99 AB |
1241 | if (ret != AE_OK && ret != AE_NOT_EXIST) |
1242 | goto notification_failed_wmi; | |
1243 | #endif | |
a4ecbb8a | 1244 | |
58ac7aa0 | 1245 | return 0; |
74caab99 AB |
1246 | #if IS_ENABLED(CONFIG_ACPI_WMI) |
1247 | notification_failed_wmi: | |
1248 | acpi_remove_notify_handler(priv->adev->handle, | |
1249 | ACPI_DEVICE_NOTIFY, ideapad_acpi_notify); | |
1250 | #endif | |
b5c37b79 ZR |
1251 | notification_failed: |
1252 | ideapad_backlight_exit(priv); | |
a4ecbb8a IP |
1253 | backlight_failed: |
1254 | for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) | |
75a11f11 | 1255 | ideapad_unregister_rfkill(priv, i); |
7451a55a | 1256 | ideapad_input_exit(priv); |
f63409ae | 1257 | input_failed: |
773e3206 IP |
1258 | ideapad_debugfs_exit(priv); |
1259 | debugfs_failed: | |
b5c37b79 | 1260 | ideapad_sysfs_exit(priv); |
98ee6919 | 1261 | return ret; |
58ac7aa0 DW |
1262 | } |
1263 | ||
b5c37b79 | 1264 | static int ideapad_acpi_remove(struct platform_device *pdev) |
58ac7aa0 | 1265 | { |
b5c37b79 | 1266 | struct ideapad_private *priv = dev_get_drvdata(&pdev->dev); |
58ac7aa0 | 1267 | int i; |
ce326329 | 1268 | |
74caab99 | 1269 | #if IS_ENABLED(CONFIG_ACPI_WMI) |
2d98e0b9 AB |
1270 | if (priv->fnesc_guid) |
1271 | wmi_remove_notify_handler(priv->fnesc_guid); | |
74caab99 | 1272 | #endif |
b5c37b79 ZR |
1273 | acpi_remove_notify_handler(priv->adev->handle, |
1274 | ACPI_DEVICE_NOTIFY, ideapad_acpi_notify); | |
a4ecbb8a | 1275 | ideapad_backlight_exit(priv); |
c1f73658 | 1276 | for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) |
75a11f11 | 1277 | ideapad_unregister_rfkill(priv, i); |
8693ae84 | 1278 | ideapad_input_exit(priv); |
773e3206 | 1279 | ideapad_debugfs_exit(priv); |
b5c37b79 ZR |
1280 | ideapad_sysfs_exit(priv); |
1281 | dev_set_drvdata(&pdev->dev, NULL); | |
c9f718d0 | 1282 | |
58ac7aa0 DW |
1283 | return 0; |
1284 | } | |
1285 | ||
11fa8da5 | 1286 | #ifdef CONFIG_PM_SLEEP |
07a4a4fc MM |
1287 | static int ideapad_acpi_resume(struct device *device) |
1288 | { | |
75a11f11 ZR |
1289 | struct ideapad_private *priv; |
1290 | ||
1291 | if (!device) | |
1292 | return -EINVAL; | |
1293 | priv = dev_get_drvdata(device); | |
1294 | ||
1295 | ideapad_sync_rfk_state(priv); | |
1296 | ideapad_sync_touchpad_state(priv); | |
07a4a4fc MM |
1297 | return 0; |
1298 | } | |
11fa8da5 | 1299 | #endif |
b5c37b79 | 1300 | static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume); |
07a4a4fc | 1301 | |
b5c37b79 ZR |
1302 | static const struct acpi_device_id ideapad_device_ids[] = { |
1303 | { "VPC2004", 0}, | |
1304 | { "", 0}, | |
58ac7aa0 | 1305 | }; |
b5c37b79 ZR |
1306 | MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); |
1307 | ||
1308 | static struct platform_driver ideapad_acpi_driver = { | |
1309 | .probe = ideapad_acpi_add, | |
1310 | .remove = ideapad_acpi_remove, | |
1311 | .driver = { | |
1312 | .name = "ideapad_acpi", | |
b5c37b79 ZR |
1313 | .pm = &ideapad_pm, |
1314 | .acpi_match_table = ACPI_PTR(ideapad_device_ids), | |
1315 | }, | |
1316 | }; | |
1317 | ||
1318 | module_platform_driver(ideapad_acpi_driver); | |
58ac7aa0 DW |
1319 | |
1320 | MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); | |
1321 | MODULE_DESCRIPTION("IdeaPad ACPI Extras"); | |
1322 | MODULE_LICENSE("GPL"); |