ideapad_laptop: introduce struct acpi_device pointer to ideapad_private structure
[deliverable/linux.git] / drivers / platform / x86 / ideapad-laptop.c
CommitLineData
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>
29#include <acpi/acpi_bus.h>
30#include <acpi/acpi_drivers.h>
31#include <linux/rfkill.h>
98ee6919 32#include <linux/platform_device.h>
f63409ae
IP
33#include <linux/input.h>
34#include <linux/input/sparse-keymap.h>
a4ecbb8a
IP
35#include <linux/backlight.h>
36#include <linux/fb.h>
773e3206
IP
37#include <linux/debugfs.h>
38#include <linux/seq_file.h>
07a4a4fc 39#include <linux/i8042.h>
58ac7aa0 40
c1f73658 41#define IDEAPAD_RFKILL_DEV_NUM (3)
58ac7aa0 42
3371f481
IP
43#define CFG_BT_BIT (16)
44#define CFG_3G_BIT (17)
45#define CFG_WIFI_BIT (18)
a84511f7 46#define CFG_CAMERA_BIT (19)
3371f481 47
2be1dc21
IP
48enum {
49 VPCCMD_R_VPC1 = 0x10,
50 VPCCMD_R_BL_MAX,
51 VPCCMD_R_BL,
52 VPCCMD_W_BL,
53 VPCCMD_R_WIFI,
54 VPCCMD_W_WIFI,
55 VPCCMD_R_BT,
56 VPCCMD_W_BT,
57 VPCCMD_R_BL_POWER,
58 VPCCMD_R_NOVO,
59 VPCCMD_R_VPC2,
60 VPCCMD_R_TOUCHPAD,
61 VPCCMD_W_TOUCHPAD,
62 VPCCMD_R_CAMERA,
63 VPCCMD_W_CAMERA,
64 VPCCMD_R_3G,
65 VPCCMD_W_3G,
66 VPCCMD_R_ODD, /* 0x21 */
0c7bbeb9
MM
67 VPCCMD_W_FAN,
68 VPCCMD_R_RF,
2be1dc21 69 VPCCMD_W_RF,
0c7bbeb9 70 VPCCMD_R_FAN = 0x2B,
296f9fe0 71 VPCCMD_R_SPECIAL_BUTTONS = 0x31,
2be1dc21
IP
72 VPCCMD_W_BL_POWER = 0x33,
73};
74
ce326329 75struct ideapad_private {
469f6434 76 struct acpi_device *adev;
c1f73658 77 struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM];
98ee6919 78 struct platform_device *platform_device;
f63409ae 79 struct input_dev *inputdev;
a4ecbb8a 80 struct backlight_device *blightdev;
773e3206 81 struct dentry *debug;
3371f481 82 unsigned long cfg;
58ac7aa0
DW
83};
84
c1f73658 85static acpi_handle ideapad_handle;
773e3206 86static struct ideapad_private *ideapad_priv;
bfa97b7d
IP
87static bool no_bt_rfkill;
88module_param(no_bt_rfkill, bool, 0444);
89MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth.");
90
6a09f21d
IP
91/*
92 * ACPI Helpers
93 */
94#define IDEAPAD_EC_TIMEOUT (100) /* in ms */
95
96static int read_method_int(acpi_handle handle, const char *method, int *val)
97{
98 acpi_status status;
99 unsigned long long result;
100
101 status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
102 if (ACPI_FAILURE(status)) {
103 *val = -1;
104 return -1;
105 } else {
106 *val = result;
107 return 0;
108 }
109}
110
111static int method_vpcr(acpi_handle handle, int cmd, int *ret)
112{
113 acpi_status status;
114 unsigned long long result;
115 struct acpi_object_list params;
116 union acpi_object in_obj;
117
118 params.count = 1;
119 params.pointer = &in_obj;
120 in_obj.type = ACPI_TYPE_INTEGER;
121 in_obj.integer.value = cmd;
122
123 status = acpi_evaluate_integer(handle, "VPCR", &params, &result);
124
125 if (ACPI_FAILURE(status)) {
126 *ret = -1;
127 return -1;
128 } else {
129 *ret = result;
130 return 0;
131 }
132}
133
134static int method_vpcw(acpi_handle handle, int cmd, int data)
135{
136 struct acpi_object_list params;
137 union acpi_object in_obj[2];
138 acpi_status status;
139
140 params.count = 2;
141 params.pointer = in_obj;
142 in_obj[0].type = ACPI_TYPE_INTEGER;
143 in_obj[0].integer.value = cmd;
144 in_obj[1].type = ACPI_TYPE_INTEGER;
145 in_obj[1].integer.value = data;
146
147 status = acpi_evaluate_object(handle, "VPCW", &params, NULL);
148 if (status != AE_OK)
149 return -1;
150 return 0;
151}
152
153static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data)
154{
155 int val;
156 unsigned long int end_jiffies;
157
158 if (method_vpcw(handle, 1, cmd))
159 return -1;
160
161 for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
162 time_before(jiffies, end_jiffies);) {
163 schedule();
164 if (method_vpcr(handle, 1, &val))
165 return -1;
166 if (val == 0) {
167 if (method_vpcr(handle, 0, &val))
168 return -1;
169 *data = val;
170 return 0;
171 }
172 }
173 pr_err("timeout in read_ec_cmd\n");
174 return -1;
175}
176
177static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data)
178{
179 int val;
180 unsigned long int end_jiffies;
181
182 if (method_vpcw(handle, 0, data))
183 return -1;
184 if (method_vpcw(handle, 1, cmd))
185 return -1;
186
187 for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
188 time_before(jiffies, end_jiffies);) {
189 schedule();
190 if (method_vpcr(handle, 1, &val))
191 return -1;
192 if (val == 0)
193 return 0;
194 }
195 pr_err("timeout in write_ec_cmd\n");
196 return -1;
197}
6a09f21d 198
773e3206
IP
199/*
200 * debugfs
201 */
773e3206
IP
202static int debugfs_status_show(struct seq_file *s, void *data)
203{
204 unsigned long value;
205
206 if (!read_ec_data(ideapad_handle, VPCCMD_R_BL_MAX, &value))
207 seq_printf(s, "Backlight max:\t%lu\n", value);
208 if (!read_ec_data(ideapad_handle, VPCCMD_R_BL, &value))
209 seq_printf(s, "Backlight now:\t%lu\n", value);
210 if (!read_ec_data(ideapad_handle, VPCCMD_R_BL_POWER, &value))
211 seq_printf(s, "BL power value:\t%s\n", value ? "On" : "Off");
212 seq_printf(s, "=====================\n");
213
214 if (!read_ec_data(ideapad_handle, VPCCMD_R_RF, &value))
215 seq_printf(s, "Radio status:\t%s(%lu)\n",
216 value ? "On" : "Off", value);
217 if (!read_ec_data(ideapad_handle, VPCCMD_R_WIFI, &value))
218 seq_printf(s, "Wifi status:\t%s(%lu)\n",
219 value ? "On" : "Off", value);
220 if (!read_ec_data(ideapad_handle, VPCCMD_R_BT, &value))
221 seq_printf(s, "BT status:\t%s(%lu)\n",
222 value ? "On" : "Off", value);
223 if (!read_ec_data(ideapad_handle, VPCCMD_R_3G, &value))
224 seq_printf(s, "3G status:\t%s(%lu)\n",
225 value ? "On" : "Off", value);
226 seq_printf(s, "=====================\n");
227
228 if (!read_ec_data(ideapad_handle, VPCCMD_R_TOUCHPAD, &value))
229 seq_printf(s, "Touchpad status:%s(%lu)\n",
230 value ? "On" : "Off", value);
231 if (!read_ec_data(ideapad_handle, VPCCMD_R_CAMERA, &value))
232 seq_printf(s, "Camera status:\t%s(%lu)\n",
233 value ? "On" : "Off", value);
234
235 return 0;
236}
237
238static int debugfs_status_open(struct inode *inode, struct file *file)
239{
240 return single_open(file, debugfs_status_show, NULL);
241}
242
243static const struct file_operations debugfs_status_fops = {
244 .owner = THIS_MODULE,
245 .open = debugfs_status_open,
246 .read = seq_read,
247 .llseek = seq_lseek,
248 .release = single_release,
249};
250
251static int debugfs_cfg_show(struct seq_file *s, void *data)
252{
253 if (!ideapad_priv) {
254 seq_printf(s, "cfg: N/A\n");
255 } else {
256 seq_printf(s, "cfg: 0x%.8lX\n\nCapability: ",
257 ideapad_priv->cfg);
258 if (test_bit(CFG_BT_BIT, &ideapad_priv->cfg))
259 seq_printf(s, "Bluetooth ");
260 if (test_bit(CFG_3G_BIT, &ideapad_priv->cfg))
261 seq_printf(s, "3G ");
262 if (test_bit(CFG_WIFI_BIT, &ideapad_priv->cfg))
263 seq_printf(s, "Wireless ");
264 if (test_bit(CFG_CAMERA_BIT, &ideapad_priv->cfg))
265 seq_printf(s, "Camera ");
266 seq_printf(s, "\nGraphic: ");
267 switch ((ideapad_priv->cfg)&0x700) {
268 case 0x100:
269 seq_printf(s, "Intel");
270 break;
271 case 0x200:
272 seq_printf(s, "ATI");
273 break;
274 case 0x300:
275 seq_printf(s, "Nvidia");
276 break;
277 case 0x400:
278 seq_printf(s, "Intel and ATI");
279 break;
280 case 0x500:
281 seq_printf(s, "Intel and Nvidia");
282 break;
283 }
284 seq_printf(s, "\n");
285 }
286 return 0;
287}
288
289static int debugfs_cfg_open(struct inode *inode, struct file *file)
290{
291 return single_open(file, debugfs_cfg_show, NULL);
292}
293
294static const struct file_operations debugfs_cfg_fops = {
295 .owner = THIS_MODULE,
296 .open = debugfs_cfg_open,
297 .read = seq_read,
298 .llseek = seq_lseek,
299 .release = single_release,
300};
301
b859f159 302static int ideapad_debugfs_init(struct ideapad_private *priv)
773e3206
IP
303{
304 struct dentry *node;
305
306 priv->debug = debugfs_create_dir("ideapad", NULL);
307 if (priv->debug == NULL) {
308 pr_err("failed to create debugfs directory");
309 goto errout;
310 }
311
312 node = debugfs_create_file("cfg", S_IRUGO, priv->debug, NULL,
313 &debugfs_cfg_fops);
314 if (!node) {
315 pr_err("failed to create cfg in debugfs");
316 goto errout;
317 }
318
319 node = debugfs_create_file("status", S_IRUGO, priv->debug, NULL,
320 &debugfs_status_fops);
321 if (!node) {
a5c3892f 322 pr_err("failed to create status in debugfs");
773e3206
IP
323 goto errout;
324 }
325
326 return 0;
327
328errout:
329 return -ENOMEM;
330}
331
332static void ideapad_debugfs_exit(struct ideapad_private *priv)
333{
334 debugfs_remove_recursive(priv->debug);
335 priv->debug = NULL;
336}
337
a4b5a279 338/*
3371f481 339 * sysfs
a4b5a279 340 */
58ac7aa0
DW
341static ssize_t show_ideapad_cam(struct device *dev,
342 struct device_attribute *attr,
343 char *buf)
344{
26c81d5c 345 unsigned long result;
58ac7aa0 346
2be1dc21 347 if (read_ec_data(ideapad_handle, VPCCMD_R_CAMERA, &result))
26c81d5c
IP
348 return sprintf(buf, "-1\n");
349 return sprintf(buf, "%lu\n", result);
58ac7aa0
DW
350}
351
352static ssize_t store_ideapad_cam(struct device *dev,
353 struct device_attribute *attr,
354 const char *buf, size_t count)
355{
356 int ret, state;
357
358 if (!count)
359 return 0;
360 if (sscanf(buf, "%i", &state) != 1)
361 return -EINVAL;
2be1dc21 362 ret = write_ec_cmd(ideapad_handle, VPCCMD_W_CAMERA, state);
58ac7aa0 363 if (ret < 0)
0c7bbeb9 364 return -EIO;
58ac7aa0
DW
365 return count;
366}
367
368static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam);
369
0c7bbeb9
MM
370static ssize_t show_ideapad_fan(struct device *dev,
371 struct device_attribute *attr,
372 char *buf)
373{
374 unsigned long result;
375
376 if (read_ec_data(ideapad_handle, VPCCMD_R_FAN, &result))
377 return sprintf(buf, "-1\n");
378 return sprintf(buf, "%lu\n", result);
379}
380
381static ssize_t store_ideapad_fan(struct device *dev,
382 struct device_attribute *attr,
383 const char *buf, size_t count)
384{
385 int ret, state;
386
387 if (!count)
388 return 0;
389 if (sscanf(buf, "%i", &state) != 1)
390 return -EINVAL;
391 if (state < 0 || state > 4 || state == 3)
392 return -EINVAL;
393 ret = write_ec_cmd(ideapad_handle, VPCCMD_W_FAN, state);
394 if (ret < 0)
395 return -EIO;
396 return count;
397}
398
399static DEVICE_ATTR(fan_mode, 0644, show_ideapad_fan, store_ideapad_fan);
400
3371f481
IP
401static struct attribute *ideapad_attributes[] = {
402 &dev_attr_camera_power.attr,
0c7bbeb9 403 &dev_attr_fan_mode.attr,
3371f481
IP
404 NULL
405};
406
587a1f16 407static umode_t ideapad_is_visible(struct kobject *kobj,
a84511f7
IP
408 struct attribute *attr,
409 int idx)
410{
411 struct device *dev = container_of(kobj, struct device, kobj);
412 struct ideapad_private *priv = dev_get_drvdata(dev);
413 bool supported;
414
415 if (attr == &dev_attr_camera_power.attr)
416 supported = test_bit(CFG_CAMERA_BIT, &(priv->cfg));
0c7bbeb9
MM
417 else if (attr == &dev_attr_fan_mode.attr) {
418 unsigned long value;
419 supported = !read_ec_data(ideapad_handle, VPCCMD_R_FAN, &value);
420 } else
a84511f7
IP
421 supported = true;
422
423 return supported ? attr->mode : 0;
424}
425
3371f481 426static struct attribute_group ideapad_attribute_group = {
a84511f7 427 .is_visible = ideapad_is_visible,
3371f481
IP
428 .attrs = ideapad_attributes
429};
430
a4b5a279
IP
431/*
432 * Rfkill
433 */
c1f73658
IP
434struct ideapad_rfk_data {
435 char *name;
436 int cfgbit;
437 int opcode;
438 int type;
439};
440
441const struct ideapad_rfk_data ideapad_rfk_data[] = {
2be1dc21
IP
442 { "ideapad_wlan", CFG_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN },
443 { "ideapad_bluetooth", CFG_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH },
444 { "ideapad_3g", CFG_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN },
c1f73658
IP
445};
446
58ac7aa0
DW
447static int ideapad_rfk_set(void *data, bool blocked)
448{
c1f73658 449 unsigned long opcode = (unsigned long)data;
fa08359e 450
c1f73658 451 return write_ec_cmd(ideapad_handle, opcode, !blocked);
58ac7aa0
DW
452}
453
454static struct rfkill_ops ideapad_rfk_ops = {
455 .set_block = ideapad_rfk_set,
456};
457
923de84a 458static void ideapad_sync_rfk_state(struct ideapad_private *priv)
58ac7aa0 459{
2b7266bd 460 unsigned long hw_blocked;
58ac7aa0
DW
461 int i;
462
2be1dc21 463 if (read_ec_data(ideapad_handle, VPCCMD_R_RF, &hw_blocked))
58ac7aa0 464 return;
2b7266bd 465 hw_blocked = !hw_blocked;
58ac7aa0 466
c1f73658 467 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
ce326329 468 if (priv->rfk[i])
2b7266bd 469 rfkill_set_hw_state(priv->rfk[i], hw_blocked);
58ac7aa0
DW
470}
471
469f6434 472static int ideapad_register_rfkill(struct acpi_device *adev, int dev)
58ac7aa0 473{
469f6434 474 struct ideapad_private *priv = dev_get_drvdata(&adev->dev);
58ac7aa0 475 int ret;
2b7266bd 476 unsigned long sw_blocked;
58ac7aa0 477
bfa97b7d
IP
478 if (no_bt_rfkill &&
479 (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) {
480 /* Force to enable bluetooth when no_bt_rfkill=1 */
c1f73658 481 write_ec_cmd(ideapad_handle,
bfa97b7d
IP
482 ideapad_rfk_data[dev].opcode, 1);
483 return 0;
484 }
485
469f6434 486 priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, &adev->dev,
2b7266bd 487 ideapad_rfk_data[dev].type, &ideapad_rfk_ops,
ce326329
DW
488 (void *)(long)dev);
489 if (!priv->rfk[dev])
58ac7aa0
DW
490 return -ENOMEM;
491
c1f73658 492 if (read_ec_data(ideapad_handle, ideapad_rfk_data[dev].opcode-1,
2b7266bd
IP
493 &sw_blocked)) {
494 rfkill_init_sw_state(priv->rfk[dev], 0);
495 } else {
496 sw_blocked = !sw_blocked;
497 rfkill_init_sw_state(priv->rfk[dev], sw_blocked);
498 }
499
ce326329 500 ret = rfkill_register(priv->rfk[dev]);
58ac7aa0 501 if (ret) {
ce326329 502 rfkill_destroy(priv->rfk[dev]);
58ac7aa0
DW
503 return ret;
504 }
505 return 0;
506}
507
469f6434 508static void ideapad_unregister_rfkill(struct acpi_device *adev, int dev)
58ac7aa0 509{
469f6434 510 struct ideapad_private *priv = dev_get_drvdata(&adev->dev);
ce326329
DW
511
512 if (!priv->rfk[dev])
58ac7aa0
DW
513 return;
514
ce326329
DW
515 rfkill_unregister(priv->rfk[dev]);
516 rfkill_destroy(priv->rfk[dev]);
58ac7aa0
DW
517}
518
98ee6919
IP
519/*
520 * Platform device
521 */
b859f159 522static int ideapad_platform_init(struct ideapad_private *priv)
98ee6919
IP
523{
524 int result;
525
8693ae84
IP
526 priv->platform_device = platform_device_alloc("ideapad", -1);
527 if (!priv->platform_device)
98ee6919 528 return -ENOMEM;
8693ae84 529 platform_set_drvdata(priv->platform_device, priv);
98ee6919 530
8693ae84 531 result = platform_device_add(priv->platform_device);
98ee6919
IP
532 if (result)
533 goto fail_platform_device;
534
8693ae84 535 result = sysfs_create_group(&priv->platform_device->dev.kobj,
c9f718d0
IP
536 &ideapad_attribute_group);
537 if (result)
538 goto fail_sysfs;
98ee6919
IP
539 return 0;
540
c9f718d0 541fail_sysfs:
8693ae84 542 platform_device_del(priv->platform_device);
98ee6919 543fail_platform_device:
8693ae84 544 platform_device_put(priv->platform_device);
98ee6919
IP
545 return result;
546}
547
8693ae84 548static void ideapad_platform_exit(struct ideapad_private *priv)
98ee6919 549{
8693ae84 550 sysfs_remove_group(&priv->platform_device->dev.kobj,
c9f718d0 551 &ideapad_attribute_group);
8693ae84 552 platform_device_unregister(priv->platform_device);
98ee6919 553}
98ee6919 554
f63409ae
IP
555/*
556 * input device
557 */
558static const struct key_entry ideapad_keymap[] = {
f43d9ec0 559 { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } },
296f9fe0
MM
560 { KE_KEY, 7, { KEY_CAMERA } },
561 { KE_KEY, 11, { KEY_F16 } },
f43d9ec0
IP
562 { KE_KEY, 13, { KEY_WLAN } },
563 { KE_KEY, 16, { KEY_PROG1 } },
564 { KE_KEY, 17, { KEY_PROG2 } },
296f9fe0
MM
565 { KE_KEY, 64, { KEY_PROG3 } },
566 { KE_KEY, 65, { KEY_PROG4 } },
07a4a4fc
MM
567 { KE_KEY, 66, { KEY_TOUCHPAD_OFF } },
568 { KE_KEY, 67, { KEY_TOUCHPAD_ON } },
f63409ae
IP
569 { KE_END, 0 },
570};
571
b859f159 572static int ideapad_input_init(struct ideapad_private *priv)
f63409ae
IP
573{
574 struct input_dev *inputdev;
575 int error;
576
577 inputdev = input_allocate_device();
578 if (!inputdev) {
579 pr_info("Unable to allocate input device\n");
580 return -ENOMEM;
581 }
582
583 inputdev->name = "Ideapad extra buttons";
584 inputdev->phys = "ideapad/input0";
585 inputdev->id.bustype = BUS_HOST;
8693ae84 586 inputdev->dev.parent = &priv->platform_device->dev;
f63409ae
IP
587
588 error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL);
589 if (error) {
590 pr_err("Unable to setup input device keymap\n");
591 goto err_free_dev;
592 }
593
594 error = input_register_device(inputdev);
595 if (error) {
596 pr_err("Unable to register input device\n");
597 goto err_free_keymap;
598 }
599
8693ae84 600 priv->inputdev = inputdev;
f63409ae
IP
601 return 0;
602
603err_free_keymap:
604 sparse_keymap_free(inputdev);
605err_free_dev:
606 input_free_device(inputdev);
607 return error;
608}
609
7451a55a 610static void ideapad_input_exit(struct ideapad_private *priv)
f63409ae 611{
8693ae84
IP
612 sparse_keymap_free(priv->inputdev);
613 input_unregister_device(priv->inputdev);
614 priv->inputdev = NULL;
f63409ae
IP
615}
616
8693ae84
IP
617static void ideapad_input_report(struct ideapad_private *priv,
618 unsigned long scancode)
f63409ae 619{
8693ae84 620 sparse_keymap_report_event(priv->inputdev, scancode, 1, true);
f63409ae 621}
f63409ae 622
f43d9ec0
IP
623static void ideapad_input_novokey(struct ideapad_private *priv)
624{
625 unsigned long long_pressed;
626
627 if (read_ec_data(ideapad_handle, VPCCMD_R_NOVO, &long_pressed))
628 return;
629 if (long_pressed)
630 ideapad_input_report(priv, 17);
631 else
632 ideapad_input_report(priv, 16);
633}
634
296f9fe0
MM
635static void ideapad_check_special_buttons(struct ideapad_private *priv)
636{
637 unsigned long bit, value;
638
639 read_ec_data(ideapad_handle, VPCCMD_R_SPECIAL_BUTTONS, &value);
640
641 for (bit = 0; bit < 16; bit++) {
642 if (test_bit(bit, &value)) {
643 switch (bit) {
a1ec56ed
MM
644 case 0: /* Z580 */
645 case 6: /* Z570 */
296f9fe0
MM
646 /* Thermal Management button */
647 ideapad_input_report(priv, 65);
648 break;
649 case 1:
650 /* OneKey Theater button */
651 ideapad_input_report(priv, 64);
652 break;
a1ec56ed
MM
653 default:
654 pr_info("Unknown special button: %lu\n", bit);
655 break;
296f9fe0
MM
656 }
657 }
658 }
659}
660
a4ecbb8a
IP
661/*
662 * backlight
663 */
664static int ideapad_backlight_get_brightness(struct backlight_device *blightdev)
665{
666 unsigned long now;
667
2be1dc21 668 if (read_ec_data(ideapad_handle, VPCCMD_R_BL, &now))
a4ecbb8a
IP
669 return -EIO;
670 return now;
671}
672
673static int ideapad_backlight_update_status(struct backlight_device *blightdev)
674{
2be1dc21
IP
675 if (write_ec_cmd(ideapad_handle, VPCCMD_W_BL,
676 blightdev->props.brightness))
a4ecbb8a 677 return -EIO;
2be1dc21 678 if (write_ec_cmd(ideapad_handle, VPCCMD_W_BL_POWER,
a4ecbb8a
IP
679 blightdev->props.power == FB_BLANK_POWERDOWN ? 0 : 1))
680 return -EIO;
681
682 return 0;
683}
684
685static const struct backlight_ops ideapad_backlight_ops = {
686 .get_brightness = ideapad_backlight_get_brightness,
687 .update_status = ideapad_backlight_update_status,
688};
689
690static int ideapad_backlight_init(struct ideapad_private *priv)
691{
692 struct backlight_device *blightdev;
693 struct backlight_properties props;
694 unsigned long max, now, power;
695
2be1dc21 696 if (read_ec_data(ideapad_handle, VPCCMD_R_BL_MAX, &max))
a4ecbb8a 697 return -EIO;
2be1dc21 698 if (read_ec_data(ideapad_handle, VPCCMD_R_BL, &now))
a4ecbb8a 699 return -EIO;
2be1dc21 700 if (read_ec_data(ideapad_handle, VPCCMD_R_BL_POWER, &power))
a4ecbb8a
IP
701 return -EIO;
702
703 memset(&props, 0, sizeof(struct backlight_properties));
704 props.max_brightness = max;
705 props.type = BACKLIGHT_PLATFORM;
706 blightdev = backlight_device_register("ideapad",
707 &priv->platform_device->dev,
708 priv,
709 &ideapad_backlight_ops,
710 &props);
711 if (IS_ERR(blightdev)) {
712 pr_err("Could not register backlight device\n");
713 return PTR_ERR(blightdev);
714 }
715
716 priv->blightdev = blightdev;
717 blightdev->props.brightness = now;
718 blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
719 backlight_update_status(blightdev);
720
721 return 0;
722}
723
724static void ideapad_backlight_exit(struct ideapad_private *priv)
725{
726 if (priv->blightdev)
727 backlight_device_unregister(priv->blightdev);
728 priv->blightdev = NULL;
729}
730
731static void ideapad_backlight_notify_power(struct ideapad_private *priv)
732{
733 unsigned long power;
734 struct backlight_device *blightdev = priv->blightdev;
735
d4afc775
RB
736 if (!blightdev)
737 return;
2be1dc21 738 if (read_ec_data(ideapad_handle, VPCCMD_R_BL_POWER, &power))
a4ecbb8a
IP
739 return;
740 blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
741}
742
743static void ideapad_backlight_notify_brightness(struct ideapad_private *priv)
744{
745 unsigned long now;
746
747 /* if we control brightness via acpi video driver */
748 if (priv->blightdev == NULL) {
2be1dc21 749 read_ec_data(ideapad_handle, VPCCMD_R_BL, &now);
a4ecbb8a
IP
750 return;
751 }
752
753 backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY);
754}
755
a4b5a279
IP
756/*
757 * module init/exit
758 */
58ac7aa0
DW
759static const struct acpi_device_id ideapad_device_ids[] = {
760 { "VPC2004", 0},
761 { "", 0},
762};
763MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
764
469f6434 765static void ideapad_sync_touchpad_state(struct acpi_device *adev)
07a4a4fc 766{
469f6434 767 struct ideapad_private *priv = dev_get_drvdata(&adev->dev);
07a4a4fc
MM
768 unsigned long value;
769
770 /* Without reading from EC touchpad LED doesn't switch state */
469f6434 771 if (!read_ec_data(adev->handle, VPCCMD_R_TOUCHPAD, &value)) {
07a4a4fc
MM
772 /* Some IdeaPads don't really turn off touchpad - they only
773 * switch the LED state. We (de)activate KBC AUX port to turn
774 * touchpad off and on. We send KEY_TOUCHPAD_OFF and
775 * KEY_TOUCHPAD_ON to not to get out of sync with LED */
776 unsigned char param;
777 i8042_command(&param, value ? I8042_CMD_AUX_ENABLE :
778 I8042_CMD_AUX_DISABLE);
779 ideapad_input_report(priv, value ? 67 : 66);
780 }
781}
782
469f6434 783static int ideapad_acpi_add(struct acpi_device *adev)
58ac7aa0 784{
3371f481 785 int ret, i;
57f9616b 786 int cfg;
ce326329 787 struct ideapad_private *priv;
58ac7aa0 788
469f6434 789 if (read_method_int(adev->handle, "_CFG", &cfg))
6f8371c0
IP
790 return -ENODEV;
791
ce326329
DW
792 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
793 if (!priv)
794 return -ENOMEM;
469f6434 795 dev_set_drvdata(&adev->dev, priv);
773e3206 796 ideapad_priv = priv;
469f6434 797 ideapad_handle = adev->handle;
3371f481 798 priv->cfg = cfg;
469f6434 799 priv->adev = adev;
98ee6919 800
8693ae84 801 ret = ideapad_platform_init(priv);
98ee6919
IP
802 if (ret)
803 goto platform_failed;
ce326329 804
773e3206
IP
805 ret = ideapad_debugfs_init(priv);
806 if (ret)
807 goto debugfs_failed;
808
8693ae84 809 ret = ideapad_input_init(priv);
f63409ae
IP
810 if (ret)
811 goto input_failed;
812
c1f73658 813 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) {
57f9616b 814 if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg))
469f6434 815 ideapad_register_rfkill(adev, i);
c1f73658
IP
816 else
817 priv->rfk[i] = NULL;
58ac7aa0 818 }
923de84a 819 ideapad_sync_rfk_state(priv);
469f6434 820 ideapad_sync_touchpad_state(adev);
c9f718d0 821
a4ecbb8a
IP
822 if (!acpi_video_backlight_support()) {
823 ret = ideapad_backlight_init(priv);
824 if (ret && ret != -ENODEV)
825 goto backlight_failed;
826 }
827
58ac7aa0 828 return 0;
98ee6919 829
a4ecbb8a
IP
830backlight_failed:
831 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
469f6434 832 ideapad_unregister_rfkill(adev, i);
7451a55a 833 ideapad_input_exit(priv);
f63409ae 834input_failed:
773e3206
IP
835 ideapad_debugfs_exit(priv);
836debugfs_failed:
8693ae84 837 ideapad_platform_exit(priv);
98ee6919
IP
838platform_failed:
839 kfree(priv);
840 return ret;
58ac7aa0
DW
841}
842
469f6434 843static int ideapad_acpi_remove(struct acpi_device *adev)
58ac7aa0 844{
469f6434 845 struct ideapad_private *priv = dev_get_drvdata(&adev->dev);
58ac7aa0 846 int i;
ce326329 847
a4ecbb8a 848 ideapad_backlight_exit(priv);
c1f73658 849 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
469f6434 850 ideapad_unregister_rfkill(adev, i);
8693ae84 851 ideapad_input_exit(priv);
773e3206 852 ideapad_debugfs_exit(priv);
8693ae84 853 ideapad_platform_exit(priv);
469f6434 854 dev_set_drvdata(&adev->dev, NULL);
ce326329 855 kfree(priv);
c9f718d0 856
58ac7aa0
DW
857 return 0;
858}
859
469f6434 860static void ideapad_acpi_notify(struct acpi_device *adev, u32 event)
58ac7aa0 861{
469f6434
ZR
862 struct ideapad_private *priv = dev_get_drvdata(&adev->dev);
863 acpi_handle handle = adev->handle;
8e7d3543
IP
864 unsigned long vpc1, vpc2, vpc_bit;
865
2be1dc21 866 if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1))
8e7d3543 867 return;
2be1dc21 868 if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2))
8e7d3543
IP
869 return;
870
871 vpc1 = (vpc2 << 8) | vpc1;
872 for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) {
873 if (test_bit(vpc_bit, &vpc1)) {
a4ecbb8a
IP
874 switch (vpc_bit) {
875 case 9:
923de84a 876 ideapad_sync_rfk_state(priv);
a4ecbb8a 877 break;
20a769c1 878 case 13:
296f9fe0
MM
879 case 11:
880 case 7:
20a769c1
IP
881 case 6:
882 ideapad_input_report(priv, vpc_bit);
883 break;
07a4a4fc 884 case 5:
469f6434 885 ideapad_sync_touchpad_state(adev);
07a4a4fc 886 break;
a4ecbb8a
IP
887 case 4:
888 ideapad_backlight_notify_brightness(priv);
889 break;
f43d9ec0
IP
890 case 3:
891 ideapad_input_novokey(priv);
892 break;
a4ecbb8a
IP
893 case 2:
894 ideapad_backlight_notify_power(priv);
895 break;
296f9fe0
MM
896 case 0:
897 ideapad_check_special_buttons(priv);
898 break;
a4ecbb8a 899 default:
20a769c1 900 pr_info("Unknown event: %lu\n", vpc_bit);
a4ecbb8a 901 }
8e7d3543
IP
902 }
903 }
58ac7aa0
DW
904}
905
11fa8da5 906#ifdef CONFIG_PM_SLEEP
07a4a4fc
MM
907static int ideapad_acpi_resume(struct device *device)
908{
909 ideapad_sync_rfk_state(ideapad_priv);
910 ideapad_sync_touchpad_state(to_acpi_device(device));
911 return 0;
912}
913
914static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume);
11fa8da5 915#endif
07a4a4fc 916
58ac7aa0
DW
917static struct acpi_driver ideapad_acpi_driver = {
918 .name = "ideapad_acpi",
919 .class = "IdeaPad",
920 .ids = ideapad_device_ids,
921 .ops.add = ideapad_acpi_add,
922 .ops.remove = ideapad_acpi_remove,
923 .ops.notify = ideapad_acpi_notify,
11fa8da5 924#ifdef CONFIG_PM_SLEEP
07a4a4fc 925 .drv.pm = &ideapad_pm,
11fa8da5 926#endif
58ac7aa0
DW
927 .owner = THIS_MODULE,
928};
26953f78 929module_acpi_driver(ideapad_acpi_driver);
58ac7aa0
DW
930
931MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
932MODULE_DESCRIPTION("IdeaPad ACPI Extras");
933MODULE_LICENSE("GPL");
This page took 0.26697 seconds and 5 git commands to generate.