blob: 0a1ceaa9062ea285fcfb45246c1eaf7af331e1db [file] [log] [blame]
David Woodhouse58ac7aa2010-08-10 23:44:05 +01001/*
Ike Panhca4b5a272010-12-13 18:00:48 +08002 * ideapad-laptop.c - Lenovo IdeaPad ACPI Extras
David Woodhouse58ac7aa2010-08-10 23:44:05 +01003 *
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
Joe Perches9ab23982011-03-29 15:21:43 -070023#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
24
David Woodhouse58ac7aa2010-08-10 23:44:05 +010025#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>
Ike Panhc98ee6912010-12-13 18:00:15 +080032#include <linux/platform_device.h>
Ike Panhcf63409a2010-12-13 18:00:38 +080033#include <linux/input.h>
34#include <linux/input/sparse-keymap.h>
Ike Panhca4ecbb82011-06-30 19:50:52 +080035#include <linux/backlight.h>
36#include <linux/fb.h>
David Woodhouse58ac7aa2010-08-10 23:44:05 +010037
Ike Panhcc1f73652010-12-13 18:01:12 +080038#define IDEAPAD_RFKILL_DEV_NUM (3)
David Woodhouse58ac7aa2010-08-10 23:44:05 +010039
Ike Panhc3371f482011-06-30 19:50:40 +080040#define CFG_BT_BIT (16)
41#define CFG_3G_BIT (17)
42#define CFG_WIFI_BIT (18)
Ike Panhca84511f2011-06-30 19:50:47 +080043#define CFG_CAMERA_BIT (19)
Ike Panhc3371f482011-06-30 19:50:40 +080044
Ike Panhc2be1dc22011-09-06 02:31:53 +080045enum {
46 VPCCMD_R_VPC1 = 0x10,
47 VPCCMD_R_BL_MAX,
48 VPCCMD_R_BL,
49 VPCCMD_W_BL,
50 VPCCMD_R_WIFI,
51 VPCCMD_W_WIFI,
52 VPCCMD_R_BT,
53 VPCCMD_W_BT,
54 VPCCMD_R_BL_POWER,
55 VPCCMD_R_NOVO,
56 VPCCMD_R_VPC2,
57 VPCCMD_R_TOUCHPAD,
58 VPCCMD_W_TOUCHPAD,
59 VPCCMD_R_CAMERA,
60 VPCCMD_W_CAMERA,
61 VPCCMD_R_3G,
62 VPCCMD_W_3G,
63 VPCCMD_R_ODD, /* 0x21 */
64 VPCCMD_R_RF = 0x23,
65 VPCCMD_W_RF,
66 VPCCMD_W_BL_POWER = 0x33,
67};
68
David Woodhousece326322010-08-11 17:59:35 +010069struct ideapad_private {
Ike Panhcc1f73652010-12-13 18:01:12 +080070 struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM];
Ike Panhc98ee6912010-12-13 18:00:15 +080071 struct platform_device *platform_device;
Ike Panhcf63409a2010-12-13 18:00:38 +080072 struct input_dev *inputdev;
Ike Panhca4ecbb82011-06-30 19:50:52 +080073 struct backlight_device *blightdev;
Ike Panhc3371f482011-06-30 19:50:40 +080074 unsigned long cfg;
David Woodhouse58ac7aa2010-08-10 23:44:05 +010075};
76
Ike Panhcc1f73652010-12-13 18:01:12 +080077static acpi_handle ideapad_handle;
Ike Panhcbfa97b72010-10-01 15:40:22 +080078static bool no_bt_rfkill;
79module_param(no_bt_rfkill, bool, 0444);
80MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth.");
81
Ike Panhc6a09f212010-10-01 15:38:46 +080082/*
83 * ACPI Helpers
84 */
85#define IDEAPAD_EC_TIMEOUT (100) /* in ms */
86
87static int read_method_int(acpi_handle handle, const char *method, int *val)
88{
89 acpi_status status;
90 unsigned long long result;
91
92 status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
93 if (ACPI_FAILURE(status)) {
94 *val = -1;
95 return -1;
96 } else {
97 *val = result;
98 return 0;
99 }
100}
101
102static int method_vpcr(acpi_handle handle, int cmd, int *ret)
103{
104 acpi_status status;
105 unsigned long long result;
106 struct acpi_object_list params;
107 union acpi_object in_obj;
108
109 params.count = 1;
110 params.pointer = &in_obj;
111 in_obj.type = ACPI_TYPE_INTEGER;
112 in_obj.integer.value = cmd;
113
114 status = acpi_evaluate_integer(handle, "VPCR", &params, &result);
115
116 if (ACPI_FAILURE(status)) {
117 *ret = -1;
118 return -1;
119 } else {
120 *ret = result;
121 return 0;
122 }
123}
124
125static int method_vpcw(acpi_handle handle, int cmd, int data)
126{
127 struct acpi_object_list params;
128 union acpi_object in_obj[2];
129 acpi_status status;
130
131 params.count = 2;
132 params.pointer = in_obj;
133 in_obj[0].type = ACPI_TYPE_INTEGER;
134 in_obj[0].integer.value = cmd;
135 in_obj[1].type = ACPI_TYPE_INTEGER;
136 in_obj[1].integer.value = data;
137
138 status = acpi_evaluate_object(handle, "VPCW", &params, NULL);
139 if (status != AE_OK)
140 return -1;
141 return 0;
142}
143
144static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data)
145{
146 int val;
147 unsigned long int end_jiffies;
148
149 if (method_vpcw(handle, 1, cmd))
150 return -1;
151
152 for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
153 time_before(jiffies, end_jiffies);) {
154 schedule();
155 if (method_vpcr(handle, 1, &val))
156 return -1;
157 if (val == 0) {
158 if (method_vpcr(handle, 0, &val))
159 return -1;
160 *data = val;
161 return 0;
162 }
163 }
164 pr_err("timeout in read_ec_cmd\n");
165 return -1;
166}
167
168static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data)
169{
170 int val;
171 unsigned long int end_jiffies;
172
173 if (method_vpcw(handle, 0, data))
174 return -1;
175 if (method_vpcw(handle, 1, cmd))
176 return -1;
177
178 for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
179 time_before(jiffies, end_jiffies);) {
180 schedule();
181 if (method_vpcr(handle, 1, &val))
182 return -1;
183 if (val == 0)
184 return 0;
185 }
186 pr_err("timeout in write_ec_cmd\n");
187 return -1;
188}
Ike Panhc6a09f212010-10-01 15:38:46 +0800189
Ike Panhca4b5a272010-12-13 18:00:48 +0800190/*
Ike Panhc3371f482011-06-30 19:50:40 +0800191 * sysfs
Ike Panhca4b5a272010-12-13 18:00:48 +0800192 */
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100193static ssize_t show_ideapad_cam(struct device *dev,
194 struct device_attribute *attr,
195 char *buf)
196{
Ike Panhc26c81d52010-10-01 15:39:40 +0800197 unsigned long result;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100198
Ike Panhc2be1dc22011-09-06 02:31:53 +0800199 if (read_ec_data(ideapad_handle, VPCCMD_R_CAMERA, &result))
Ike Panhc26c81d52010-10-01 15:39:40 +0800200 return sprintf(buf, "-1\n");
201 return sprintf(buf, "%lu\n", result);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100202}
203
204static ssize_t store_ideapad_cam(struct device *dev,
205 struct device_attribute *attr,
206 const char *buf, size_t count)
207{
208 int ret, state;
209
210 if (!count)
211 return 0;
212 if (sscanf(buf, "%i", &state) != 1)
213 return -EINVAL;
Ike Panhc2be1dc22011-09-06 02:31:53 +0800214 ret = write_ec_cmd(ideapad_handle, VPCCMD_W_CAMERA, state);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100215 if (ret < 0)
216 return ret;
217 return count;
218}
219
220static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam);
221
Ike Panhc3371f482011-06-30 19:50:40 +0800222static ssize_t show_ideapad_cfg(struct device *dev,
223 struct device_attribute *attr,
224 char *buf)
225{
226 struct ideapad_private *priv = dev_get_drvdata(dev);
227
228 return sprintf(buf, "0x%.8lX\n", priv->cfg);
229}
230
231static DEVICE_ATTR(cfg, 0444, show_ideapad_cfg, NULL);
232
233static struct attribute *ideapad_attributes[] = {
234 &dev_attr_camera_power.attr,
235 &dev_attr_cfg.attr,
236 NULL
237};
238
Ike Panhca84511f2011-06-30 19:50:47 +0800239static mode_t ideapad_is_visible(struct kobject *kobj,
240 struct attribute *attr,
241 int idx)
242{
243 struct device *dev = container_of(kobj, struct device, kobj);
244 struct ideapad_private *priv = dev_get_drvdata(dev);
245 bool supported;
246
247 if (attr == &dev_attr_camera_power.attr)
248 supported = test_bit(CFG_CAMERA_BIT, &(priv->cfg));
249 else
250 supported = true;
251
252 return supported ? attr->mode : 0;
253}
254
Ike Panhc3371f482011-06-30 19:50:40 +0800255static struct attribute_group ideapad_attribute_group = {
Ike Panhca84511f2011-06-30 19:50:47 +0800256 .is_visible = ideapad_is_visible,
Ike Panhc3371f482011-06-30 19:50:40 +0800257 .attrs = ideapad_attributes
258};
259
Ike Panhca4b5a272010-12-13 18:00:48 +0800260/*
261 * Rfkill
262 */
Ike Panhcc1f73652010-12-13 18:01:12 +0800263struct ideapad_rfk_data {
264 char *name;
265 int cfgbit;
266 int opcode;
267 int type;
268};
269
270const struct ideapad_rfk_data ideapad_rfk_data[] = {
Ike Panhc2be1dc22011-09-06 02:31:53 +0800271 { "ideapad_wlan", CFG_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN },
272 { "ideapad_bluetooth", CFG_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH },
273 { "ideapad_3g", CFG_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN },
Ike Panhcc1f73652010-12-13 18:01:12 +0800274};
275
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100276static int ideapad_rfk_set(void *data, bool blocked)
277{
Ike Panhcc1f73652010-12-13 18:01:12 +0800278 unsigned long opcode = (unsigned long)data;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100279
Ike Panhcc1f73652010-12-13 18:01:12 +0800280 return write_ec_cmd(ideapad_handle, opcode, !blocked);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100281}
282
283static struct rfkill_ops ideapad_rfk_ops = {
284 .set_block = ideapad_rfk_set,
285};
286
David Woodhousece326322010-08-11 17:59:35 +0100287static void ideapad_sync_rfk_state(struct acpi_device *adevice)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100288{
David Woodhousece326322010-08-11 17:59:35 +0100289 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
Ike Panhc2b7266b2010-10-01 15:39:49 +0800290 unsigned long hw_blocked;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100291 int i;
292
Ike Panhc2be1dc22011-09-06 02:31:53 +0800293 if (read_ec_data(ideapad_handle, VPCCMD_R_RF, &hw_blocked))
Ike Panhc2b7266b2010-10-01 15:39:49 +0800294 return;
295 hw_blocked = !hw_blocked;
296
Ike Panhcc1f73652010-12-13 18:01:12 +0800297 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
David Woodhousece326322010-08-11 17:59:35 +0100298 if (priv->rfk[i])
299 rfkill_set_hw_state(priv->rfk[i], hw_blocked);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100300}
301
Ike Panhca4b5a272010-12-13 18:00:48 +0800302static int __devinit ideapad_register_rfkill(struct acpi_device *adevice,
303 int dev)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100304{
David Woodhousece326322010-08-11 17:59:35 +0100305 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100306 int ret;
Ike Panhc2b7266b2010-10-01 15:39:49 +0800307 unsigned long sw_blocked;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100308
Ike Panhcbfa97b72010-10-01 15:40:22 +0800309 if (no_bt_rfkill &&
310 (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) {
311 /* Force to enable bluetooth when no_bt_rfkill=1 */
Ike Panhcc1f73652010-12-13 18:01:12 +0800312 write_ec_cmd(ideapad_handle,
Ike Panhcbfa97b72010-10-01 15:40:22 +0800313 ideapad_rfk_data[dev].opcode, 1);
314 return 0;
315 }
316
Ike Panhc2b7266b2010-10-01 15:39:49 +0800317 priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, &adevice->dev,
318 ideapad_rfk_data[dev].type, &ideapad_rfk_ops,
David Woodhousece326322010-08-11 17:59:35 +0100319 (void *)(long)dev);
320 if (!priv->rfk[dev])
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100321 return -ENOMEM;
322
Ike Panhcc1f73652010-12-13 18:01:12 +0800323 if (read_ec_data(ideapad_handle, ideapad_rfk_data[dev].opcode-1,
Ike Panhc2b7266b2010-10-01 15:39:49 +0800324 &sw_blocked)) {
325 rfkill_init_sw_state(priv->rfk[dev], 0);
326 } else {
327 sw_blocked = !sw_blocked;
328 rfkill_init_sw_state(priv->rfk[dev], sw_blocked);
329 }
330
David Woodhousece326322010-08-11 17:59:35 +0100331 ret = rfkill_register(priv->rfk[dev]);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100332 if (ret) {
David Woodhousece326322010-08-11 17:59:35 +0100333 rfkill_destroy(priv->rfk[dev]);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100334 return ret;
335 }
336 return 0;
337}
338
Ike Panhca4ecbb82011-06-30 19:50:52 +0800339static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100340{
David Woodhousece326322010-08-11 17:59:35 +0100341 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
342
343 if (!priv->rfk[dev])
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100344 return;
345
David Woodhousece326322010-08-11 17:59:35 +0100346 rfkill_unregister(priv->rfk[dev]);
347 rfkill_destroy(priv->rfk[dev]);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100348}
349
Ike Panhc98ee6912010-12-13 18:00:15 +0800350/*
351 * Platform device
352 */
Ike Panhc8693ae82010-12-13 18:01:01 +0800353static int __devinit ideapad_platform_init(struct ideapad_private *priv)
Ike Panhc98ee6912010-12-13 18:00:15 +0800354{
355 int result;
356
Ike Panhc8693ae82010-12-13 18:01:01 +0800357 priv->platform_device = platform_device_alloc("ideapad", -1);
358 if (!priv->platform_device)
Ike Panhc98ee6912010-12-13 18:00:15 +0800359 return -ENOMEM;
Ike Panhc8693ae82010-12-13 18:01:01 +0800360 platform_set_drvdata(priv->platform_device, priv);
Ike Panhc98ee6912010-12-13 18:00:15 +0800361
Ike Panhc8693ae82010-12-13 18:01:01 +0800362 result = platform_device_add(priv->platform_device);
Ike Panhc98ee6912010-12-13 18:00:15 +0800363 if (result)
364 goto fail_platform_device;
365
Ike Panhc8693ae82010-12-13 18:01:01 +0800366 result = sysfs_create_group(&priv->platform_device->dev.kobj,
Ike Panhcc9f718d2010-12-13 18:00:27 +0800367 &ideapad_attribute_group);
368 if (result)
369 goto fail_sysfs;
Ike Panhc98ee6912010-12-13 18:00:15 +0800370 return 0;
371
Ike Panhcc9f718d2010-12-13 18:00:27 +0800372fail_sysfs:
Ike Panhc8693ae82010-12-13 18:01:01 +0800373 platform_device_del(priv->platform_device);
Ike Panhc98ee6912010-12-13 18:00:15 +0800374fail_platform_device:
Ike Panhc8693ae82010-12-13 18:01:01 +0800375 platform_device_put(priv->platform_device);
Ike Panhc98ee6912010-12-13 18:00:15 +0800376 return result;
377}
378
Ike Panhc8693ae82010-12-13 18:01:01 +0800379static void ideapad_platform_exit(struct ideapad_private *priv)
Ike Panhc98ee6912010-12-13 18:00:15 +0800380{
Ike Panhc8693ae82010-12-13 18:01:01 +0800381 sysfs_remove_group(&priv->platform_device->dev.kobj,
Ike Panhcc9f718d2010-12-13 18:00:27 +0800382 &ideapad_attribute_group);
Ike Panhc8693ae82010-12-13 18:01:01 +0800383 platform_device_unregister(priv->platform_device);
Ike Panhc98ee6912010-12-13 18:00:15 +0800384}
Ike Panhc98ee6912010-12-13 18:00:15 +0800385
Ike Panhcf63409a2010-12-13 18:00:38 +0800386/*
387 * input device
388 */
389static const struct key_entry ideapad_keymap[] = {
390 { KE_KEY, 0x06, { KEY_SWITCHVIDEOMODE } },
391 { KE_KEY, 0x0D, { KEY_WLAN } },
392 { KE_END, 0 },
393};
394
Ike Panhc8693ae82010-12-13 18:01:01 +0800395static int __devinit ideapad_input_init(struct ideapad_private *priv)
Ike Panhcf63409a2010-12-13 18:00:38 +0800396{
397 struct input_dev *inputdev;
398 int error;
399
400 inputdev = input_allocate_device();
401 if (!inputdev) {
402 pr_info("Unable to allocate input device\n");
403 return -ENOMEM;
404 }
405
406 inputdev->name = "Ideapad extra buttons";
407 inputdev->phys = "ideapad/input0";
408 inputdev->id.bustype = BUS_HOST;
Ike Panhc8693ae82010-12-13 18:01:01 +0800409 inputdev->dev.parent = &priv->platform_device->dev;
Ike Panhcf63409a2010-12-13 18:00:38 +0800410
411 error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL);
412 if (error) {
413 pr_err("Unable to setup input device keymap\n");
414 goto err_free_dev;
415 }
416
417 error = input_register_device(inputdev);
418 if (error) {
419 pr_err("Unable to register input device\n");
420 goto err_free_keymap;
421 }
422
Ike Panhc8693ae82010-12-13 18:01:01 +0800423 priv->inputdev = inputdev;
Ike Panhcf63409a2010-12-13 18:00:38 +0800424 return 0;
425
426err_free_keymap:
427 sparse_keymap_free(inputdev);
428err_free_dev:
429 input_free_device(inputdev);
430 return error;
431}
432
Axel Lin7451a552011-07-27 15:27:34 +0800433static void ideapad_input_exit(struct ideapad_private *priv)
Ike Panhcf63409a2010-12-13 18:00:38 +0800434{
Ike Panhc8693ae82010-12-13 18:01:01 +0800435 sparse_keymap_free(priv->inputdev);
436 input_unregister_device(priv->inputdev);
437 priv->inputdev = NULL;
Ike Panhcf63409a2010-12-13 18:00:38 +0800438}
439
Ike Panhc8693ae82010-12-13 18:01:01 +0800440static void ideapad_input_report(struct ideapad_private *priv,
441 unsigned long scancode)
Ike Panhcf63409a2010-12-13 18:00:38 +0800442{
Ike Panhc8693ae82010-12-13 18:01:01 +0800443 sparse_keymap_report_event(priv->inputdev, scancode, 1, true);
Ike Panhcf63409a2010-12-13 18:00:38 +0800444}
Ike Panhcf63409a2010-12-13 18:00:38 +0800445
Ike Panhca4b5a272010-12-13 18:00:48 +0800446/*
Ike Panhca4ecbb82011-06-30 19:50:52 +0800447 * backlight
448 */
449static int ideapad_backlight_get_brightness(struct backlight_device *blightdev)
450{
451 unsigned long now;
452
Ike Panhc2be1dc22011-09-06 02:31:53 +0800453 if (read_ec_data(ideapad_handle, VPCCMD_R_BL, &now))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800454 return -EIO;
455 return now;
456}
457
458static int ideapad_backlight_update_status(struct backlight_device *blightdev)
459{
Ike Panhc2be1dc22011-09-06 02:31:53 +0800460 if (write_ec_cmd(ideapad_handle, VPCCMD_W_BL,
461 blightdev->props.brightness))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800462 return -EIO;
Ike Panhc2be1dc22011-09-06 02:31:53 +0800463 if (write_ec_cmd(ideapad_handle, VPCCMD_W_BL_POWER,
Ike Panhca4ecbb82011-06-30 19:50:52 +0800464 blightdev->props.power == FB_BLANK_POWERDOWN ? 0 : 1))
465 return -EIO;
466
467 return 0;
468}
469
470static const struct backlight_ops ideapad_backlight_ops = {
471 .get_brightness = ideapad_backlight_get_brightness,
472 .update_status = ideapad_backlight_update_status,
473};
474
475static int ideapad_backlight_init(struct ideapad_private *priv)
476{
477 struct backlight_device *blightdev;
478 struct backlight_properties props;
479 unsigned long max, now, power;
480
Ike Panhc2be1dc22011-09-06 02:31:53 +0800481 if (read_ec_data(ideapad_handle, VPCCMD_R_BL_MAX, &max))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800482 return -EIO;
Ike Panhc2be1dc22011-09-06 02:31:53 +0800483 if (read_ec_data(ideapad_handle, VPCCMD_R_BL, &now))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800484 return -EIO;
Ike Panhc2be1dc22011-09-06 02:31:53 +0800485 if (read_ec_data(ideapad_handle, VPCCMD_R_BL_POWER, &power))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800486 return -EIO;
487
488 memset(&props, 0, sizeof(struct backlight_properties));
489 props.max_brightness = max;
490 props.type = BACKLIGHT_PLATFORM;
491 blightdev = backlight_device_register("ideapad",
492 &priv->platform_device->dev,
493 priv,
494 &ideapad_backlight_ops,
495 &props);
496 if (IS_ERR(blightdev)) {
497 pr_err("Could not register backlight device\n");
498 return PTR_ERR(blightdev);
499 }
500
501 priv->blightdev = blightdev;
502 blightdev->props.brightness = now;
503 blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
504 backlight_update_status(blightdev);
505
506 return 0;
507}
508
509static void ideapad_backlight_exit(struct ideapad_private *priv)
510{
511 if (priv->blightdev)
512 backlight_device_unregister(priv->blightdev);
513 priv->blightdev = NULL;
514}
515
516static void ideapad_backlight_notify_power(struct ideapad_private *priv)
517{
518 unsigned long power;
519 struct backlight_device *blightdev = priv->blightdev;
520
Rene Bollfordd4afc772011-10-23 09:56:42 +0200521 if (!blightdev)
522 return;
Ike Panhc2be1dc22011-09-06 02:31:53 +0800523 if (read_ec_data(ideapad_handle, VPCCMD_R_BL_POWER, &power))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800524 return;
525 blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
526}
527
528static void ideapad_backlight_notify_brightness(struct ideapad_private *priv)
529{
530 unsigned long now;
531
532 /* if we control brightness via acpi video driver */
533 if (priv->blightdev == NULL) {
Ike Panhc2be1dc22011-09-06 02:31:53 +0800534 read_ec_data(ideapad_handle, VPCCMD_R_BL, &now);
Ike Panhca4ecbb82011-06-30 19:50:52 +0800535 return;
536 }
537
538 backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY);
539}
540
541/*
Ike Panhca4b5a272010-12-13 18:00:48 +0800542 * module init/exit
543 */
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100544static const struct acpi_device_id ideapad_device_ids[] = {
545 { "VPC2004", 0},
546 { "", 0},
547};
548MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
549
Ike Panhca4b5a272010-12-13 18:00:48 +0800550static int __devinit ideapad_acpi_add(struct acpi_device *adevice)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100551{
Ike Panhc3371f482011-06-30 19:50:40 +0800552 int ret, i;
553 unsigned long cfg;
David Woodhousece326322010-08-11 17:59:35 +0100554 struct ideapad_private *priv;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100555
Ike Panhc3371f482011-06-30 19:50:40 +0800556 if (read_method_int(adevice->handle, "_CFG", (int *)&cfg))
Ike Panhc6f8371c2010-10-01 15:39:14 +0800557 return -ENODEV;
558
David Woodhousece326322010-08-11 17:59:35 +0100559 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
560 if (!priv)
561 return -ENOMEM;
Ike Panhcc9f718d2010-12-13 18:00:27 +0800562 dev_set_drvdata(&adevice->dev, priv);
Ike Panhcc1f73652010-12-13 18:01:12 +0800563 ideapad_handle = adevice->handle;
Ike Panhc3371f482011-06-30 19:50:40 +0800564 priv->cfg = cfg;
Ike Panhc98ee6912010-12-13 18:00:15 +0800565
Ike Panhc8693ae82010-12-13 18:01:01 +0800566 ret = ideapad_platform_init(priv);
Ike Panhc98ee6912010-12-13 18:00:15 +0800567 if (ret)
568 goto platform_failed;
David Woodhousece326322010-08-11 17:59:35 +0100569
Ike Panhc8693ae82010-12-13 18:01:01 +0800570 ret = ideapad_input_init(priv);
Ike Panhcf63409a2010-12-13 18:00:38 +0800571 if (ret)
572 goto input_failed;
573
Ike Panhcc1f73652010-12-13 18:01:12 +0800574 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) {
Ike Panhc3371f482011-06-30 19:50:40 +0800575 if (test_bit(ideapad_rfk_data[i].cfgbit, &cfg))
Ike Panhcc9f718d2010-12-13 18:00:27 +0800576 ideapad_register_rfkill(adevice, i);
Ike Panhcc1f73652010-12-13 18:01:12 +0800577 else
578 priv->rfk[i] = NULL;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100579 }
David Woodhousece326322010-08-11 17:59:35 +0100580 ideapad_sync_rfk_state(adevice);
Ike Panhcc9f718d2010-12-13 18:00:27 +0800581
Ike Panhca4ecbb82011-06-30 19:50:52 +0800582 if (!acpi_video_backlight_support()) {
583 ret = ideapad_backlight_init(priv);
584 if (ret && ret != -ENODEV)
585 goto backlight_failed;
586 }
587
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100588 return 0;
Ike Panhc98ee6912010-12-13 18:00:15 +0800589
Ike Panhca4ecbb82011-06-30 19:50:52 +0800590backlight_failed:
591 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
592 ideapad_unregister_rfkill(adevice, i);
Axel Lin7451a552011-07-27 15:27:34 +0800593 ideapad_input_exit(priv);
Ike Panhcf63409a2010-12-13 18:00:38 +0800594input_failed:
Ike Panhc8693ae82010-12-13 18:01:01 +0800595 ideapad_platform_exit(priv);
Ike Panhc98ee6912010-12-13 18:00:15 +0800596platform_failed:
597 kfree(priv);
598 return ret;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100599}
600
Ike Panhca4b5a272010-12-13 18:00:48 +0800601static int __devexit ideapad_acpi_remove(struct acpi_device *adevice, int type)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100602{
David Woodhousece326322010-08-11 17:59:35 +0100603 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100604 int i;
David Woodhousece326322010-08-11 17:59:35 +0100605
Ike Panhca4ecbb82011-06-30 19:50:52 +0800606 ideapad_backlight_exit(priv);
Ike Panhcc1f73652010-12-13 18:01:12 +0800607 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
David Woodhousece326322010-08-11 17:59:35 +0100608 ideapad_unregister_rfkill(adevice, i);
Ike Panhc8693ae82010-12-13 18:01:01 +0800609 ideapad_input_exit(priv);
610 ideapad_platform_exit(priv);
David Woodhousece326322010-08-11 17:59:35 +0100611 dev_set_drvdata(&adevice->dev, NULL);
612 kfree(priv);
Ike Panhcc9f718d2010-12-13 18:00:27 +0800613
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100614 return 0;
615}
616
David Woodhousece326322010-08-11 17:59:35 +0100617static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100618{
Ike Panhc8693ae82010-12-13 18:01:01 +0800619 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
Ike Panhc8e7d3542010-10-01 15:39:05 +0800620 acpi_handle handle = adevice->handle;
621 unsigned long vpc1, vpc2, vpc_bit;
622
Ike Panhc2be1dc22011-09-06 02:31:53 +0800623 if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1))
Ike Panhc8e7d3542010-10-01 15:39:05 +0800624 return;
Ike Panhc2be1dc22011-09-06 02:31:53 +0800625 if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2))
Ike Panhc8e7d3542010-10-01 15:39:05 +0800626 return;
627
628 vpc1 = (vpc2 << 8) | vpc1;
629 for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) {
630 if (test_bit(vpc_bit, &vpc1)) {
Ike Panhca4ecbb82011-06-30 19:50:52 +0800631 switch (vpc_bit) {
632 case 9:
Ike Panhc8e7d3542010-10-01 15:39:05 +0800633 ideapad_sync_rfk_state(adevice);
Ike Panhca4ecbb82011-06-30 19:50:52 +0800634 break;
635 case 4:
636 ideapad_backlight_notify_brightness(priv);
637 break;
638 case 2:
639 ideapad_backlight_notify_power(priv);
640 break;
641 default:
Ike Panhc8693ae82010-12-13 18:01:01 +0800642 ideapad_input_report(priv, vpc_bit);
Ike Panhca4ecbb82011-06-30 19:50:52 +0800643 }
Ike Panhc8e7d3542010-10-01 15:39:05 +0800644 }
645 }
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100646}
647
648static struct acpi_driver ideapad_acpi_driver = {
649 .name = "ideapad_acpi",
650 .class = "IdeaPad",
651 .ids = ideapad_device_ids,
652 .ops.add = ideapad_acpi_add,
653 .ops.remove = ideapad_acpi_remove,
654 .ops.notify = ideapad_acpi_notify,
655 .owner = THIS_MODULE,
656};
657
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100658static int __init ideapad_acpi_module_init(void)
659{
Ike Panhca4b5a272010-12-13 18:00:48 +0800660 return acpi_bus_register_driver(&ideapad_acpi_driver);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100661}
662
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100663static void __exit ideapad_acpi_module_exit(void)
664{
665 acpi_bus_unregister_driver(&ideapad_acpi_driver);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100666}
667
668MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
669MODULE_DESCRIPTION("IdeaPad ACPI Extras");
670MODULE_LICENSE("GPL");
671
672module_init(ideapad_acpi_module_init);
673module_exit(ideapad_acpi_module_exit);