blob: 39a72a8aeb8ce3f57512093676630b36524b9ba6 [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
Ike Panhc923de842011-09-06 02:32:01 +0800287static void ideapad_sync_rfk_state(struct ideapad_private *priv)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100288{
Ike Panhc2b7266b2010-10-01 15:39:49 +0800289 unsigned long hw_blocked;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100290 int i;
291
Ike Panhc2be1dc22011-09-06 02:31:53 +0800292 if (read_ec_data(ideapad_handle, VPCCMD_R_RF, &hw_blocked))
Ike Panhc2b7266b2010-10-01 15:39:49 +0800293 return;
294 hw_blocked = !hw_blocked;
295
Ike Panhcc1f73652010-12-13 18:01:12 +0800296 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
David Woodhousece326322010-08-11 17:59:35 +0100297 if (priv->rfk[i])
298 rfkill_set_hw_state(priv->rfk[i], hw_blocked);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100299}
300
Ike Panhca4b5a272010-12-13 18:00:48 +0800301static int __devinit ideapad_register_rfkill(struct acpi_device *adevice,
302 int dev)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100303{
David Woodhousece326322010-08-11 17:59:35 +0100304 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100305 int ret;
Ike Panhc2b7266b2010-10-01 15:39:49 +0800306 unsigned long sw_blocked;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100307
Ike Panhcbfa97b72010-10-01 15:40:22 +0800308 if (no_bt_rfkill &&
309 (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) {
310 /* Force to enable bluetooth when no_bt_rfkill=1 */
Ike Panhcc1f73652010-12-13 18:01:12 +0800311 write_ec_cmd(ideapad_handle,
Ike Panhcbfa97b72010-10-01 15:40:22 +0800312 ideapad_rfk_data[dev].opcode, 1);
313 return 0;
314 }
315
Ike Panhc2b7266b2010-10-01 15:39:49 +0800316 priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, &adevice->dev,
317 ideapad_rfk_data[dev].type, &ideapad_rfk_ops,
David Woodhousece326322010-08-11 17:59:35 +0100318 (void *)(long)dev);
319 if (!priv->rfk[dev])
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100320 return -ENOMEM;
321
Ike Panhcc1f73652010-12-13 18:01:12 +0800322 if (read_ec_data(ideapad_handle, ideapad_rfk_data[dev].opcode-1,
Ike Panhc2b7266b2010-10-01 15:39:49 +0800323 &sw_blocked)) {
324 rfkill_init_sw_state(priv->rfk[dev], 0);
325 } else {
326 sw_blocked = !sw_blocked;
327 rfkill_init_sw_state(priv->rfk[dev], sw_blocked);
328 }
329
David Woodhousece326322010-08-11 17:59:35 +0100330 ret = rfkill_register(priv->rfk[dev]);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100331 if (ret) {
David Woodhousece326322010-08-11 17:59:35 +0100332 rfkill_destroy(priv->rfk[dev]);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100333 return ret;
334 }
335 return 0;
336}
337
Ike Panhca4ecbb82011-06-30 19:50:52 +0800338static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100339{
David Woodhousece326322010-08-11 17:59:35 +0100340 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
341
342 if (!priv->rfk[dev])
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100343 return;
344
David Woodhousece326322010-08-11 17:59:35 +0100345 rfkill_unregister(priv->rfk[dev]);
346 rfkill_destroy(priv->rfk[dev]);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100347}
348
Ike Panhc98ee6912010-12-13 18:00:15 +0800349/*
350 * Platform device
351 */
Ike Panhc8693ae82010-12-13 18:01:01 +0800352static int __devinit ideapad_platform_init(struct ideapad_private *priv)
Ike Panhc98ee6912010-12-13 18:00:15 +0800353{
354 int result;
355
Ike Panhc8693ae82010-12-13 18:01:01 +0800356 priv->platform_device = platform_device_alloc("ideapad", -1);
357 if (!priv->platform_device)
Ike Panhc98ee6912010-12-13 18:00:15 +0800358 return -ENOMEM;
Ike Panhc8693ae82010-12-13 18:01:01 +0800359 platform_set_drvdata(priv->platform_device, priv);
Ike Panhc98ee6912010-12-13 18:00:15 +0800360
Ike Panhc8693ae82010-12-13 18:01:01 +0800361 result = platform_device_add(priv->platform_device);
Ike Panhc98ee6912010-12-13 18:00:15 +0800362 if (result)
363 goto fail_platform_device;
364
Ike Panhc8693ae82010-12-13 18:01:01 +0800365 result = sysfs_create_group(&priv->platform_device->dev.kobj,
Ike Panhcc9f718d2010-12-13 18:00:27 +0800366 &ideapad_attribute_group);
367 if (result)
368 goto fail_sysfs;
Ike Panhc98ee6912010-12-13 18:00:15 +0800369 return 0;
370
Ike Panhcc9f718d2010-12-13 18:00:27 +0800371fail_sysfs:
Ike Panhc8693ae82010-12-13 18:01:01 +0800372 platform_device_del(priv->platform_device);
Ike Panhc98ee6912010-12-13 18:00:15 +0800373fail_platform_device:
Ike Panhc8693ae82010-12-13 18:01:01 +0800374 platform_device_put(priv->platform_device);
Ike Panhc98ee6912010-12-13 18:00:15 +0800375 return result;
376}
377
Ike Panhc8693ae82010-12-13 18:01:01 +0800378static void ideapad_platform_exit(struct ideapad_private *priv)
Ike Panhc98ee6912010-12-13 18:00:15 +0800379{
Ike Panhc8693ae82010-12-13 18:01:01 +0800380 sysfs_remove_group(&priv->platform_device->dev.kobj,
Ike Panhcc9f718d2010-12-13 18:00:27 +0800381 &ideapad_attribute_group);
Ike Panhc8693ae82010-12-13 18:01:01 +0800382 platform_device_unregister(priv->platform_device);
Ike Panhc98ee6912010-12-13 18:00:15 +0800383}
Ike Panhc98ee6912010-12-13 18:00:15 +0800384
Ike Panhcf63409a2010-12-13 18:00:38 +0800385/*
386 * input device
387 */
388static const struct key_entry ideapad_keymap[] = {
Ike Panhcf43d9ec2011-09-06 02:32:10 +0800389 { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } },
390 { KE_KEY, 13, { KEY_WLAN } },
391 { KE_KEY, 16, { KEY_PROG1 } },
392 { KE_KEY, 17, { KEY_PROG2 } },
Ike Panhcf63409a2010-12-13 18:00:38 +0800393 { KE_END, 0 },
394};
395
Ike Panhc8693ae82010-12-13 18:01:01 +0800396static int __devinit ideapad_input_init(struct ideapad_private *priv)
Ike Panhcf63409a2010-12-13 18:00:38 +0800397{
398 struct input_dev *inputdev;
399 int error;
400
401 inputdev = input_allocate_device();
402 if (!inputdev) {
403 pr_info("Unable to allocate input device\n");
404 return -ENOMEM;
405 }
406
407 inputdev->name = "Ideapad extra buttons";
408 inputdev->phys = "ideapad/input0";
409 inputdev->id.bustype = BUS_HOST;
Ike Panhc8693ae82010-12-13 18:01:01 +0800410 inputdev->dev.parent = &priv->platform_device->dev;
Ike Panhcf63409a2010-12-13 18:00:38 +0800411
412 error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL);
413 if (error) {
414 pr_err("Unable to setup input device keymap\n");
415 goto err_free_dev;
416 }
417
418 error = input_register_device(inputdev);
419 if (error) {
420 pr_err("Unable to register input device\n");
421 goto err_free_keymap;
422 }
423
Ike Panhc8693ae82010-12-13 18:01:01 +0800424 priv->inputdev = inputdev;
Ike Panhcf63409a2010-12-13 18:00:38 +0800425 return 0;
426
427err_free_keymap:
428 sparse_keymap_free(inputdev);
429err_free_dev:
430 input_free_device(inputdev);
431 return error;
432}
433
Axel Lin7451a552011-07-27 15:27:34 +0800434static void ideapad_input_exit(struct ideapad_private *priv)
Ike Panhcf63409a2010-12-13 18:00:38 +0800435{
Ike Panhc8693ae82010-12-13 18:01:01 +0800436 sparse_keymap_free(priv->inputdev);
437 input_unregister_device(priv->inputdev);
438 priv->inputdev = NULL;
Ike Panhcf63409a2010-12-13 18:00:38 +0800439}
440
Ike Panhc8693ae82010-12-13 18:01:01 +0800441static void ideapad_input_report(struct ideapad_private *priv,
442 unsigned long scancode)
Ike Panhcf63409a2010-12-13 18:00:38 +0800443{
Ike Panhc8693ae82010-12-13 18:01:01 +0800444 sparse_keymap_report_event(priv->inputdev, scancode, 1, true);
Ike Panhcf63409a2010-12-13 18:00:38 +0800445}
Ike Panhcf63409a2010-12-13 18:00:38 +0800446
Ike Panhcf43d9ec2011-09-06 02:32:10 +0800447static void ideapad_input_novokey(struct ideapad_private *priv)
448{
449 unsigned long long_pressed;
450
451 if (read_ec_data(ideapad_handle, VPCCMD_R_NOVO, &long_pressed))
452 return;
453 if (long_pressed)
454 ideapad_input_report(priv, 17);
455 else
456 ideapad_input_report(priv, 16);
457}
458
Ike Panhca4b5a272010-12-13 18:00:48 +0800459/*
Ike Panhca4ecbb82011-06-30 19:50:52 +0800460 * backlight
461 */
462static int ideapad_backlight_get_brightness(struct backlight_device *blightdev)
463{
464 unsigned long now;
465
Ike Panhc2be1dc22011-09-06 02:31:53 +0800466 if (read_ec_data(ideapad_handle, VPCCMD_R_BL, &now))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800467 return -EIO;
468 return now;
469}
470
471static int ideapad_backlight_update_status(struct backlight_device *blightdev)
472{
Ike Panhc2be1dc22011-09-06 02:31:53 +0800473 if (write_ec_cmd(ideapad_handle, VPCCMD_W_BL,
474 blightdev->props.brightness))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800475 return -EIO;
Ike Panhc2be1dc22011-09-06 02:31:53 +0800476 if (write_ec_cmd(ideapad_handle, VPCCMD_W_BL_POWER,
Ike Panhca4ecbb82011-06-30 19:50:52 +0800477 blightdev->props.power == FB_BLANK_POWERDOWN ? 0 : 1))
478 return -EIO;
479
480 return 0;
481}
482
483static const struct backlight_ops ideapad_backlight_ops = {
484 .get_brightness = ideapad_backlight_get_brightness,
485 .update_status = ideapad_backlight_update_status,
486};
487
488static int ideapad_backlight_init(struct ideapad_private *priv)
489{
490 struct backlight_device *blightdev;
491 struct backlight_properties props;
492 unsigned long max, now, power;
493
Ike Panhc2be1dc22011-09-06 02:31:53 +0800494 if (read_ec_data(ideapad_handle, VPCCMD_R_BL_MAX, &max))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800495 return -EIO;
Ike Panhc2be1dc22011-09-06 02:31:53 +0800496 if (read_ec_data(ideapad_handle, VPCCMD_R_BL, &now))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800497 return -EIO;
Ike Panhc2be1dc22011-09-06 02:31:53 +0800498 if (read_ec_data(ideapad_handle, VPCCMD_R_BL_POWER, &power))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800499 return -EIO;
500
501 memset(&props, 0, sizeof(struct backlight_properties));
502 props.max_brightness = max;
503 props.type = BACKLIGHT_PLATFORM;
504 blightdev = backlight_device_register("ideapad",
505 &priv->platform_device->dev,
506 priv,
507 &ideapad_backlight_ops,
508 &props);
509 if (IS_ERR(blightdev)) {
510 pr_err("Could not register backlight device\n");
511 return PTR_ERR(blightdev);
512 }
513
514 priv->blightdev = blightdev;
515 blightdev->props.brightness = now;
516 blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
517 backlight_update_status(blightdev);
518
519 return 0;
520}
521
522static void ideapad_backlight_exit(struct ideapad_private *priv)
523{
524 if (priv->blightdev)
525 backlight_device_unregister(priv->blightdev);
526 priv->blightdev = NULL;
527}
528
529static void ideapad_backlight_notify_power(struct ideapad_private *priv)
530{
531 unsigned long power;
532 struct backlight_device *blightdev = priv->blightdev;
533
Rene Bollfordd4afc772011-10-23 09:56:42 +0200534 if (!blightdev)
535 return;
Ike Panhc2be1dc22011-09-06 02:31:53 +0800536 if (read_ec_data(ideapad_handle, VPCCMD_R_BL_POWER, &power))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800537 return;
538 blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
539}
540
541static void ideapad_backlight_notify_brightness(struct ideapad_private *priv)
542{
543 unsigned long now;
544
545 /* if we control brightness via acpi video driver */
546 if (priv->blightdev == NULL) {
Ike Panhc2be1dc22011-09-06 02:31:53 +0800547 read_ec_data(ideapad_handle, VPCCMD_R_BL, &now);
Ike Panhca4ecbb82011-06-30 19:50:52 +0800548 return;
549 }
550
551 backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY);
552}
553
554/*
Ike Panhca4b5a272010-12-13 18:00:48 +0800555 * module init/exit
556 */
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100557static const struct acpi_device_id ideapad_device_ids[] = {
558 { "VPC2004", 0},
559 { "", 0},
560};
561MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
562
Ike Panhca4b5a272010-12-13 18:00:48 +0800563static int __devinit ideapad_acpi_add(struct acpi_device *adevice)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100564{
Ike Panhc3371f482011-06-30 19:50:40 +0800565 int ret, i;
566 unsigned long cfg;
David Woodhousece326322010-08-11 17:59:35 +0100567 struct ideapad_private *priv;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100568
Ike Panhc3371f482011-06-30 19:50:40 +0800569 if (read_method_int(adevice->handle, "_CFG", (int *)&cfg))
Ike Panhc6f8371c2010-10-01 15:39:14 +0800570 return -ENODEV;
571
David Woodhousece326322010-08-11 17:59:35 +0100572 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
573 if (!priv)
574 return -ENOMEM;
Ike Panhcc9f718d2010-12-13 18:00:27 +0800575 dev_set_drvdata(&adevice->dev, priv);
Ike Panhcc1f73652010-12-13 18:01:12 +0800576 ideapad_handle = adevice->handle;
Ike Panhc3371f482011-06-30 19:50:40 +0800577 priv->cfg = cfg;
Ike Panhc98ee6912010-12-13 18:00:15 +0800578
Ike Panhc8693ae82010-12-13 18:01:01 +0800579 ret = ideapad_platform_init(priv);
Ike Panhc98ee6912010-12-13 18:00:15 +0800580 if (ret)
581 goto platform_failed;
David Woodhousece326322010-08-11 17:59:35 +0100582
Ike Panhc8693ae82010-12-13 18:01:01 +0800583 ret = ideapad_input_init(priv);
Ike Panhcf63409a2010-12-13 18:00:38 +0800584 if (ret)
585 goto input_failed;
586
Ike Panhcc1f73652010-12-13 18:01:12 +0800587 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) {
Ike Panhc3371f482011-06-30 19:50:40 +0800588 if (test_bit(ideapad_rfk_data[i].cfgbit, &cfg))
Ike Panhcc9f718d2010-12-13 18:00:27 +0800589 ideapad_register_rfkill(adevice, i);
Ike Panhcc1f73652010-12-13 18:01:12 +0800590 else
591 priv->rfk[i] = NULL;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100592 }
Ike Panhc923de842011-09-06 02:32:01 +0800593 ideapad_sync_rfk_state(priv);
Ike Panhcc9f718d2010-12-13 18:00:27 +0800594
Ike Panhca4ecbb82011-06-30 19:50:52 +0800595 if (!acpi_video_backlight_support()) {
596 ret = ideapad_backlight_init(priv);
597 if (ret && ret != -ENODEV)
598 goto backlight_failed;
599 }
600
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100601 return 0;
Ike Panhc98ee6912010-12-13 18:00:15 +0800602
Ike Panhca4ecbb82011-06-30 19:50:52 +0800603backlight_failed:
604 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
605 ideapad_unregister_rfkill(adevice, i);
Axel Lin7451a552011-07-27 15:27:34 +0800606 ideapad_input_exit(priv);
Ike Panhcf63409a2010-12-13 18:00:38 +0800607input_failed:
Ike Panhc8693ae82010-12-13 18:01:01 +0800608 ideapad_platform_exit(priv);
Ike Panhc98ee6912010-12-13 18:00:15 +0800609platform_failed:
610 kfree(priv);
611 return ret;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100612}
613
Ike Panhca4b5a272010-12-13 18:00:48 +0800614static int __devexit ideapad_acpi_remove(struct acpi_device *adevice, int type)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100615{
David Woodhousece326322010-08-11 17:59:35 +0100616 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100617 int i;
David Woodhousece326322010-08-11 17:59:35 +0100618
Ike Panhca4ecbb82011-06-30 19:50:52 +0800619 ideapad_backlight_exit(priv);
Ike Panhcc1f73652010-12-13 18:01:12 +0800620 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
David Woodhousece326322010-08-11 17:59:35 +0100621 ideapad_unregister_rfkill(adevice, i);
Ike Panhc8693ae82010-12-13 18:01:01 +0800622 ideapad_input_exit(priv);
623 ideapad_platform_exit(priv);
David Woodhousece326322010-08-11 17:59:35 +0100624 dev_set_drvdata(&adevice->dev, NULL);
625 kfree(priv);
Ike Panhcc9f718d2010-12-13 18:00:27 +0800626
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100627 return 0;
628}
629
David Woodhousece326322010-08-11 17:59:35 +0100630static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100631{
Ike Panhc8693ae82010-12-13 18:01:01 +0800632 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
Ike Panhc8e7d3542010-10-01 15:39:05 +0800633 acpi_handle handle = adevice->handle;
634 unsigned long vpc1, vpc2, vpc_bit;
635
Ike Panhc2be1dc22011-09-06 02:31:53 +0800636 if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1))
Ike Panhc8e7d3542010-10-01 15:39:05 +0800637 return;
Ike Panhc2be1dc22011-09-06 02:31:53 +0800638 if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2))
Ike Panhc8e7d3542010-10-01 15:39:05 +0800639 return;
640
641 vpc1 = (vpc2 << 8) | vpc1;
642 for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) {
643 if (test_bit(vpc_bit, &vpc1)) {
Ike Panhca4ecbb82011-06-30 19:50:52 +0800644 switch (vpc_bit) {
645 case 9:
Ike Panhc923de842011-09-06 02:32:01 +0800646 ideapad_sync_rfk_state(priv);
Ike Panhca4ecbb82011-06-30 19:50:52 +0800647 break;
648 case 4:
649 ideapad_backlight_notify_brightness(priv);
650 break;
Ike Panhcf43d9ec2011-09-06 02:32:10 +0800651 case 3:
652 ideapad_input_novokey(priv);
653 break;
Ike Panhca4ecbb82011-06-30 19:50:52 +0800654 case 2:
655 ideapad_backlight_notify_power(priv);
656 break;
657 default:
Ike Panhc8693ae82010-12-13 18:01:01 +0800658 ideapad_input_report(priv, vpc_bit);
Ike Panhca4ecbb82011-06-30 19:50:52 +0800659 }
Ike Panhc8e7d3542010-10-01 15:39:05 +0800660 }
661 }
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100662}
663
664static struct acpi_driver ideapad_acpi_driver = {
665 .name = "ideapad_acpi",
666 .class = "IdeaPad",
667 .ids = ideapad_device_ids,
668 .ops.add = ideapad_acpi_add,
669 .ops.remove = ideapad_acpi_remove,
670 .ops.notify = ideapad_acpi_notify,
671 .owner = THIS_MODULE,
672};
673
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100674static int __init ideapad_acpi_module_init(void)
675{
Ike Panhca4b5a272010-12-13 18:00:48 +0800676 return acpi_bus_register_driver(&ideapad_acpi_driver);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100677}
678
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100679static void __exit ideapad_acpi_module_exit(void)
680{
681 acpi_bus_unregister_driver(&ideapad_acpi_driver);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100682}
683
684MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
685MODULE_DESCRIPTION("IdeaPad ACPI Extras");
686MODULE_LICENSE("GPL");
687
688module_init(ideapad_acpi_module_init);
689module_exit(ideapad_acpi_module_exit);