157ac3b05SIke Panhc /*
257ac3b05SIke Panhc  *  ideapad_acpi.c - Lenovo IdeaPad ACPI Extras
357ac3b05SIke Panhc  *
457ac3b05SIke Panhc  *  Copyright © 2010 Intel Corporation
557ac3b05SIke Panhc  *  Copyright © 2010 David Woodhouse <dwmw2@infradead.org>
657ac3b05SIke Panhc  *
757ac3b05SIke Panhc  *  This program is free software; you can redistribute it and/or modify
857ac3b05SIke Panhc  *  it under the terms of the GNU General Public License as published by
957ac3b05SIke Panhc  *  the Free Software Foundation; either version 2 of the License, or
1057ac3b05SIke Panhc  *  (at your option) any later version.
1157ac3b05SIke Panhc  *
1257ac3b05SIke Panhc  *  This program is distributed in the hope that it will be useful,
1357ac3b05SIke Panhc  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
1457ac3b05SIke Panhc  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1557ac3b05SIke Panhc  *  GNU General Public License for more details.
1657ac3b05SIke Panhc  *
1757ac3b05SIke Panhc  *  You should have received a copy of the GNU General Public License
1857ac3b05SIke Panhc  *  along with this program; if not, write to the Free Software
1957ac3b05SIke Panhc  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
2057ac3b05SIke Panhc  *  02110-1301, USA.
2157ac3b05SIke Panhc  */
2257ac3b05SIke Panhc 
2357ac3b05SIke Panhc #include <linux/kernel.h>
2457ac3b05SIke Panhc #include <linux/module.h>
2557ac3b05SIke Panhc #include <linux/init.h>
2657ac3b05SIke Panhc #include <linux/types.h>
2757ac3b05SIke Panhc #include <acpi/acpi_bus.h>
2857ac3b05SIke Panhc #include <acpi/acpi_drivers.h>
2957ac3b05SIke Panhc #include <linux/rfkill.h>
3098ee6919SIke Panhc #include <linux/platform_device.h>
31f63409aeSIke Panhc #include <linux/input.h>
32f63409aeSIke Panhc #include <linux/input/sparse-keymap.h>
3357ac3b05SIke Panhc 
3457ac3b05SIke Panhc #define IDEAPAD_DEV_CAMERA	0
3557ac3b05SIke Panhc #define IDEAPAD_DEV_WLAN	1
3657ac3b05SIke Panhc #define IDEAPAD_DEV_BLUETOOTH	2
3757ac3b05SIke Panhc #define IDEAPAD_DEV_3G		3
3857ac3b05SIke Panhc #define IDEAPAD_DEV_KILLSW	4
3957ac3b05SIke Panhc 
4057ac3b05SIke Panhc struct ideapad_private {
4157ac3b05SIke Panhc 	acpi_handle handle;
4257ac3b05SIke Panhc 	struct rfkill *rfk[5];
4398ee6919SIke Panhc 	struct platform_device *platform_device;
44f63409aeSIke Panhc 	struct input_dev *inputdev;
4557ac3b05SIke Panhc } *ideapad_priv;
4657ac3b05SIke Panhc 
4757ac3b05SIke Panhc static struct {
4857ac3b05SIke Panhc 	char *name;
4957ac3b05SIke Panhc 	int cfgbit;
5057ac3b05SIke Panhc 	int opcode;
5157ac3b05SIke Panhc 	int type;
5257ac3b05SIke Panhc } ideapad_rfk_data[] = {
5357ac3b05SIke Panhc 	{ "ideapad_camera",	19, 0x1E, NUM_RFKILL_TYPES },
5457ac3b05SIke Panhc 	{ "ideapad_wlan",	18, 0x15, RFKILL_TYPE_WLAN },
5557ac3b05SIke Panhc 	{ "ideapad_bluetooth",	16, 0x17, RFKILL_TYPE_BLUETOOTH },
5657ac3b05SIke Panhc 	{ "ideapad_3g",		17, 0x20, RFKILL_TYPE_WWAN },
5757ac3b05SIke Panhc 	{ "ideapad_killsw",	0,  0,    RFKILL_TYPE_WLAN }
5857ac3b05SIke Panhc };
5957ac3b05SIke Panhc 
60bfa97b7dSIke Panhc static bool no_bt_rfkill;
61bfa97b7dSIke Panhc module_param(no_bt_rfkill, bool, 0444);
62bfa97b7dSIke Panhc MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth.");
63bfa97b7dSIke Panhc 
6457ac3b05SIke Panhc /*
6557ac3b05SIke Panhc  * ACPI Helpers
6657ac3b05SIke Panhc  */
6757ac3b05SIke Panhc #define IDEAPAD_EC_TIMEOUT (100) /* in ms */
6857ac3b05SIke Panhc 
6957ac3b05SIke Panhc static int read_method_int(acpi_handle handle, const char *method, int *val)
7057ac3b05SIke Panhc {
7157ac3b05SIke Panhc 	acpi_status status;
7257ac3b05SIke Panhc 	unsigned long long result;
7357ac3b05SIke Panhc 
7457ac3b05SIke Panhc 	status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
7557ac3b05SIke Panhc 	if (ACPI_FAILURE(status)) {
7657ac3b05SIke Panhc 		*val = -1;
7757ac3b05SIke Panhc 		return -1;
7857ac3b05SIke Panhc 	} else {
7957ac3b05SIke Panhc 		*val = result;
8057ac3b05SIke Panhc 		return 0;
8157ac3b05SIke Panhc 	}
8257ac3b05SIke Panhc }
8357ac3b05SIke Panhc 
8457ac3b05SIke Panhc static int method_vpcr(acpi_handle handle, int cmd, int *ret)
8557ac3b05SIke Panhc {
8657ac3b05SIke Panhc 	acpi_status status;
8757ac3b05SIke Panhc 	unsigned long long result;
8857ac3b05SIke Panhc 	struct acpi_object_list params;
8957ac3b05SIke Panhc 	union acpi_object in_obj;
9057ac3b05SIke Panhc 
9157ac3b05SIke Panhc 	params.count = 1;
9257ac3b05SIke Panhc 	params.pointer = &in_obj;
9357ac3b05SIke Panhc 	in_obj.type = ACPI_TYPE_INTEGER;
9457ac3b05SIke Panhc 	in_obj.integer.value = cmd;
9557ac3b05SIke Panhc 
9657ac3b05SIke Panhc 	status = acpi_evaluate_integer(handle, "VPCR", &params, &result);
9757ac3b05SIke Panhc 
9857ac3b05SIke Panhc 	if (ACPI_FAILURE(status)) {
9957ac3b05SIke Panhc 		*ret = -1;
10057ac3b05SIke Panhc 		return -1;
10157ac3b05SIke Panhc 	} else {
10257ac3b05SIke Panhc 		*ret = result;
10357ac3b05SIke Panhc 		return 0;
10457ac3b05SIke Panhc 	}
10557ac3b05SIke Panhc }
10657ac3b05SIke Panhc 
10757ac3b05SIke Panhc static int method_vpcw(acpi_handle handle, int cmd, int data)
10857ac3b05SIke Panhc {
10957ac3b05SIke Panhc 	struct acpi_object_list params;
11057ac3b05SIke Panhc 	union acpi_object in_obj[2];
11157ac3b05SIke Panhc 	acpi_status status;
11257ac3b05SIke Panhc 
11357ac3b05SIke Panhc 	params.count = 2;
11457ac3b05SIke Panhc 	params.pointer = in_obj;
11557ac3b05SIke Panhc 	in_obj[0].type = ACPI_TYPE_INTEGER;
11657ac3b05SIke Panhc 	in_obj[0].integer.value = cmd;
11757ac3b05SIke Panhc 	in_obj[1].type = ACPI_TYPE_INTEGER;
11857ac3b05SIke Panhc 	in_obj[1].integer.value = data;
11957ac3b05SIke Panhc 
12057ac3b05SIke Panhc 	status = acpi_evaluate_object(handle, "VPCW", &params, NULL);
12157ac3b05SIke Panhc 	if (status != AE_OK)
12257ac3b05SIke Panhc 		return -1;
12357ac3b05SIke Panhc 	return 0;
12457ac3b05SIke Panhc }
12557ac3b05SIke Panhc 
12657ac3b05SIke Panhc static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data)
12757ac3b05SIke Panhc {
12857ac3b05SIke Panhc 	int val;
12957ac3b05SIke Panhc 	unsigned long int end_jiffies;
13057ac3b05SIke Panhc 
13157ac3b05SIke Panhc 	if (method_vpcw(handle, 1, cmd))
13257ac3b05SIke Panhc 		return -1;
13357ac3b05SIke Panhc 
13457ac3b05SIke Panhc 	for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
13557ac3b05SIke Panhc 	     time_before(jiffies, end_jiffies);) {
13657ac3b05SIke Panhc 		schedule();
13757ac3b05SIke Panhc 		if (method_vpcr(handle, 1, &val))
13857ac3b05SIke Panhc 			return -1;
13957ac3b05SIke Panhc 		if (val == 0) {
14057ac3b05SIke Panhc 			if (method_vpcr(handle, 0, &val))
14157ac3b05SIke Panhc 				return -1;
14257ac3b05SIke Panhc 			*data = val;
14357ac3b05SIke Panhc 			return 0;
14457ac3b05SIke Panhc 		}
14557ac3b05SIke Panhc 	}
14657ac3b05SIke Panhc 	pr_err("timeout in read_ec_cmd\n");
14757ac3b05SIke Panhc 	return -1;
14857ac3b05SIke Panhc }
14957ac3b05SIke Panhc 
15057ac3b05SIke Panhc static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data)
15157ac3b05SIke Panhc {
15257ac3b05SIke Panhc 	int val;
15357ac3b05SIke Panhc 	unsigned long int end_jiffies;
15457ac3b05SIke Panhc 
15557ac3b05SIke Panhc 	if (method_vpcw(handle, 0, data))
15657ac3b05SIke Panhc 		return -1;
15757ac3b05SIke Panhc 	if (method_vpcw(handle, 1, cmd))
15857ac3b05SIke Panhc 		return -1;
15957ac3b05SIke Panhc 
16057ac3b05SIke Panhc 	for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
16157ac3b05SIke Panhc 	     time_before(jiffies, end_jiffies);) {
16257ac3b05SIke Panhc 		schedule();
16357ac3b05SIke Panhc 		if (method_vpcr(handle, 1, &val))
16457ac3b05SIke Panhc 			return -1;
16557ac3b05SIke Panhc 		if (val == 0)
16657ac3b05SIke Panhc 			return 0;
16757ac3b05SIke Panhc 	}
16857ac3b05SIke Panhc 	pr_err("timeout in write_ec_cmd\n");
16957ac3b05SIke Panhc 	return -1;
17057ac3b05SIke Panhc }
17157ac3b05SIke Panhc /* the above is ACPI helpers */
17257ac3b05SIke Panhc 
17357ac3b05SIke Panhc static ssize_t show_ideapad_cam(struct device *dev,
17457ac3b05SIke Panhc 				struct device_attribute *attr,
17557ac3b05SIke Panhc 				char *buf)
17657ac3b05SIke Panhc {
17757ac3b05SIke Panhc 	struct ideapad_private *priv = dev_get_drvdata(dev);
17857ac3b05SIke Panhc 	acpi_handle handle = priv->handle;
17957ac3b05SIke Panhc 	unsigned long result;
18057ac3b05SIke Panhc 
18157ac3b05SIke Panhc 	if (read_ec_data(handle, 0x1D, &result))
18257ac3b05SIke Panhc 		return sprintf(buf, "-1\n");
18357ac3b05SIke Panhc 	return sprintf(buf, "%lu\n", result);
18457ac3b05SIke Panhc }
18557ac3b05SIke Panhc 
18657ac3b05SIke Panhc static ssize_t store_ideapad_cam(struct device *dev,
18757ac3b05SIke Panhc 				 struct device_attribute *attr,
18857ac3b05SIke Panhc 				 const char *buf, size_t count)
18957ac3b05SIke Panhc {
19057ac3b05SIke Panhc 	struct ideapad_private *priv = dev_get_drvdata(dev);
19157ac3b05SIke Panhc 	acpi_handle handle = priv->handle;
19257ac3b05SIke Panhc 	int ret, state;
19357ac3b05SIke Panhc 
19457ac3b05SIke Panhc 	if (!count)
19557ac3b05SIke Panhc 		return 0;
19657ac3b05SIke Panhc 	if (sscanf(buf, "%i", &state) != 1)
19757ac3b05SIke Panhc 		return -EINVAL;
19857ac3b05SIke Panhc 	ret = write_ec_cmd(handle, 0x1E, state);
19957ac3b05SIke Panhc 	if (ret < 0)
20057ac3b05SIke Panhc 		return ret;
20157ac3b05SIke Panhc 	return count;
20257ac3b05SIke Panhc }
20357ac3b05SIke Panhc 
20457ac3b05SIke Panhc static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam);
20557ac3b05SIke Panhc 
20657ac3b05SIke Panhc static int ideapad_rfk_set(void *data, bool blocked)
20757ac3b05SIke Panhc {
20857ac3b05SIke Panhc 	int device = (unsigned long)data;
20957ac3b05SIke Panhc 
21057ac3b05SIke Panhc 	if (device == IDEAPAD_DEV_KILLSW)
21157ac3b05SIke Panhc 		return -EINVAL;
21257ac3b05SIke Panhc 
21357ac3b05SIke Panhc 	return write_ec_cmd(ideapad_priv->handle,
21457ac3b05SIke Panhc 			    ideapad_rfk_data[device].opcode,
21557ac3b05SIke Panhc 			    !blocked);
21657ac3b05SIke Panhc }
21757ac3b05SIke Panhc 
21857ac3b05SIke Panhc static struct rfkill_ops ideapad_rfk_ops = {
21957ac3b05SIke Panhc 	.set_block = ideapad_rfk_set,
22057ac3b05SIke Panhc };
22157ac3b05SIke Panhc 
22257ac3b05SIke Panhc static void ideapad_sync_rfk_state(struct acpi_device *adevice)
22357ac3b05SIke Panhc {
22457ac3b05SIke Panhc 	struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
22557ac3b05SIke Panhc 	acpi_handle handle = priv->handle;
22657ac3b05SIke Panhc 	unsigned long hw_blocked;
22757ac3b05SIke Panhc 	int i;
22857ac3b05SIke Panhc 
22957ac3b05SIke Panhc 	if (read_ec_data(handle, 0x23, &hw_blocked))
23057ac3b05SIke Panhc 		return;
23157ac3b05SIke Panhc 	hw_blocked = !hw_blocked;
23257ac3b05SIke Panhc 
23357ac3b05SIke Panhc 	for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++)
23457ac3b05SIke Panhc 		if (priv->rfk[i])
23557ac3b05SIke Panhc 			rfkill_set_hw_state(priv->rfk[i], hw_blocked);
23657ac3b05SIke Panhc }
23757ac3b05SIke Panhc 
23857ac3b05SIke Panhc static int ideapad_register_rfkill(struct acpi_device *adevice, int dev)
23957ac3b05SIke Panhc {
24057ac3b05SIke Panhc 	struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
24157ac3b05SIke Panhc 	int ret;
24257ac3b05SIke Panhc 	unsigned long sw_blocked;
24357ac3b05SIke Panhc 
244bfa97b7dSIke Panhc 	if (no_bt_rfkill &&
245bfa97b7dSIke Panhc 	    (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) {
246bfa97b7dSIke Panhc 		/* Force to enable bluetooth when no_bt_rfkill=1 */
247bfa97b7dSIke Panhc 		write_ec_cmd(ideapad_priv->handle,
248bfa97b7dSIke Panhc 			     ideapad_rfk_data[dev].opcode, 1);
249bfa97b7dSIke Panhc 		return 0;
250bfa97b7dSIke Panhc 	}
251bfa97b7dSIke Panhc 
25257ac3b05SIke Panhc 	priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, &adevice->dev,
25357ac3b05SIke Panhc 				      ideapad_rfk_data[dev].type, &ideapad_rfk_ops,
25457ac3b05SIke Panhc 				      (void *)(long)dev);
25557ac3b05SIke Panhc 	if (!priv->rfk[dev])
25657ac3b05SIke Panhc 		return -ENOMEM;
25757ac3b05SIke Panhc 
25857ac3b05SIke Panhc 	if (read_ec_data(ideapad_priv->handle, ideapad_rfk_data[dev].opcode-1,
25957ac3b05SIke Panhc 			 &sw_blocked)) {
26057ac3b05SIke Panhc 		rfkill_init_sw_state(priv->rfk[dev], 0);
26157ac3b05SIke Panhc 	} else {
26257ac3b05SIke Panhc 		sw_blocked = !sw_blocked;
26357ac3b05SIke Panhc 		rfkill_init_sw_state(priv->rfk[dev], sw_blocked);
26457ac3b05SIke Panhc 	}
26557ac3b05SIke Panhc 
26657ac3b05SIke Panhc 	ret = rfkill_register(priv->rfk[dev]);
26757ac3b05SIke Panhc 	if (ret) {
26857ac3b05SIke Panhc 		rfkill_destroy(priv->rfk[dev]);
26957ac3b05SIke Panhc 		return ret;
27057ac3b05SIke Panhc 	}
27157ac3b05SIke Panhc 	return 0;
27257ac3b05SIke Panhc }
27357ac3b05SIke Panhc 
27457ac3b05SIke Panhc static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev)
27557ac3b05SIke Panhc {
27657ac3b05SIke Panhc 	struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
27757ac3b05SIke Panhc 
27857ac3b05SIke Panhc 	if (!priv->rfk[dev])
27957ac3b05SIke Panhc 		return;
28057ac3b05SIke Panhc 
28157ac3b05SIke Panhc 	rfkill_unregister(priv->rfk[dev]);
28257ac3b05SIke Panhc 	rfkill_destroy(priv->rfk[dev]);
28357ac3b05SIke Panhc }
28457ac3b05SIke Panhc 
28598ee6919SIke Panhc /*
28698ee6919SIke Panhc  * Platform device
28798ee6919SIke Panhc  */
288c9f718d0SIke Panhc static struct attribute *ideapad_attributes[] = {
289c9f718d0SIke Panhc 	&dev_attr_camera_power.attr,
290c9f718d0SIke Panhc 	NULL
291c9f718d0SIke Panhc };
292c9f718d0SIke Panhc 
293c9f718d0SIke Panhc static struct attribute_group ideapad_attribute_group = {
294c9f718d0SIke Panhc 	.attrs = ideapad_attributes
295c9f718d0SIke Panhc };
296c9f718d0SIke Panhc 
29798ee6919SIke Panhc static int __devinit ideapad_platform_init(void)
29898ee6919SIke Panhc {
29998ee6919SIke Panhc 	int result;
30098ee6919SIke Panhc 
30198ee6919SIke Panhc 	ideapad_priv->platform_device = platform_device_alloc("ideapad", -1);
30298ee6919SIke Panhc 	if (!ideapad_priv->platform_device)
30398ee6919SIke Panhc 		return -ENOMEM;
30498ee6919SIke Panhc 	platform_set_drvdata(ideapad_priv->platform_device, ideapad_priv);
30598ee6919SIke Panhc 
30698ee6919SIke Panhc 	result = platform_device_add(ideapad_priv->platform_device);
30798ee6919SIke Panhc 	if (result)
30898ee6919SIke Panhc 		goto fail_platform_device;
30998ee6919SIke Panhc 
310c9f718d0SIke Panhc 	result = sysfs_create_group(&ideapad_priv->platform_device->dev.kobj,
311c9f718d0SIke Panhc 				    &ideapad_attribute_group);
312c9f718d0SIke Panhc 	if (result)
313c9f718d0SIke Panhc 		goto fail_sysfs;
31498ee6919SIke Panhc 	return 0;
31598ee6919SIke Panhc 
316c9f718d0SIke Panhc fail_sysfs:
317c9f718d0SIke Panhc 	platform_device_del(ideapad_priv->platform_device);
31898ee6919SIke Panhc fail_platform_device:
31998ee6919SIke Panhc 	platform_device_put(ideapad_priv->platform_device);
32098ee6919SIke Panhc 	return result;
32198ee6919SIke Panhc }
32298ee6919SIke Panhc 
32398ee6919SIke Panhc static void ideapad_platform_exit(void)
32498ee6919SIke Panhc {
325c9f718d0SIke Panhc 	sysfs_remove_group(&ideapad_priv->platform_device->dev.kobj,
326c9f718d0SIke Panhc 			   &ideapad_attribute_group);
32798ee6919SIke Panhc 	platform_device_unregister(ideapad_priv->platform_device);
32898ee6919SIke Panhc }
32998ee6919SIke Panhc /* the above is platform device */
33098ee6919SIke Panhc 
331f63409aeSIke Panhc /*
332f63409aeSIke Panhc  * input device
333f63409aeSIke Panhc  */
334f63409aeSIke Panhc static const struct key_entry ideapad_keymap[] = {
335f63409aeSIke Panhc 	{ KE_KEY, 0x06, { KEY_SWITCHVIDEOMODE } },
336f63409aeSIke Panhc 	{ KE_KEY, 0x0D, { KEY_WLAN } },
337f63409aeSIke Panhc 	{ KE_END, 0 },
338f63409aeSIke Panhc };
339f63409aeSIke Panhc 
340f63409aeSIke Panhc static int __devinit ideapad_input_init(void)
341f63409aeSIke Panhc {
342f63409aeSIke Panhc 	struct input_dev *inputdev;
343f63409aeSIke Panhc 	int error;
344f63409aeSIke Panhc 
345f63409aeSIke Panhc 	inputdev = input_allocate_device();
346f63409aeSIke Panhc 	if (!inputdev) {
347f63409aeSIke Panhc 		pr_info("Unable to allocate input device\n");
348f63409aeSIke Panhc 		return -ENOMEM;
349f63409aeSIke Panhc 	}
350f63409aeSIke Panhc 
351f63409aeSIke Panhc 	inputdev->name = "Ideapad extra buttons";
352f63409aeSIke Panhc 	inputdev->phys = "ideapad/input0";
353f63409aeSIke Panhc 	inputdev->id.bustype = BUS_HOST;
354f63409aeSIke Panhc 	inputdev->dev.parent = &ideapad_priv->platform_device->dev;
355f63409aeSIke Panhc 
356f63409aeSIke Panhc 	error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL);
357f63409aeSIke Panhc 	if (error) {
358f63409aeSIke Panhc 		pr_err("Unable to setup input device keymap\n");
359f63409aeSIke Panhc 		goto err_free_dev;
360f63409aeSIke Panhc 	}
361f63409aeSIke Panhc 
362f63409aeSIke Panhc 	error = input_register_device(inputdev);
363f63409aeSIke Panhc 	if (error) {
364f63409aeSIke Panhc 		pr_err("Unable to register input device\n");
365f63409aeSIke Panhc 		goto err_free_keymap;
366f63409aeSIke Panhc 	}
367f63409aeSIke Panhc 
368f63409aeSIke Panhc 	ideapad_priv->inputdev = inputdev;
369f63409aeSIke Panhc 	return 0;
370f63409aeSIke Panhc 
371f63409aeSIke Panhc err_free_keymap:
372f63409aeSIke Panhc 	sparse_keymap_free(inputdev);
373f63409aeSIke Panhc err_free_dev:
374f63409aeSIke Panhc 	input_free_device(inputdev);
375f63409aeSIke Panhc 	return error;
376f63409aeSIke Panhc }
377f63409aeSIke Panhc 
378f63409aeSIke Panhc static void __devexit ideapad_input_exit(void)
379f63409aeSIke Panhc {
380f63409aeSIke Panhc 	sparse_keymap_free(ideapad_priv->inputdev);
381f63409aeSIke Panhc 	input_unregister_device(ideapad_priv->inputdev);
382f63409aeSIke Panhc 	ideapad_priv->inputdev = NULL;
383f63409aeSIke Panhc }
384f63409aeSIke Panhc 
385f63409aeSIke Panhc static void ideapad_input_report(unsigned long scancode)
386f63409aeSIke Panhc {
387f63409aeSIke Panhc 	sparse_keymap_report_event(ideapad_priv->inputdev, scancode, 1, true);
388f63409aeSIke Panhc }
389f63409aeSIke Panhc /* the above is input device */
390f63409aeSIke Panhc 
39157ac3b05SIke Panhc static const struct acpi_device_id ideapad_device_ids[] = {
39257ac3b05SIke Panhc 	{ "VPC2004", 0},
39357ac3b05SIke Panhc 	{ "", 0},
39457ac3b05SIke Panhc };
39557ac3b05SIke Panhc MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
39657ac3b05SIke Panhc 
39757ac3b05SIke Panhc static int ideapad_acpi_add(struct acpi_device *adevice)
39857ac3b05SIke Panhc {
39998ee6919SIke Panhc 	int ret, i, cfg;
40057ac3b05SIke Panhc 	struct ideapad_private *priv;
40157ac3b05SIke Panhc 
40257ac3b05SIke Panhc 	if (read_method_int(adevice->handle, "_CFG", &cfg))
40357ac3b05SIke Panhc 		return -ENODEV;
40457ac3b05SIke Panhc 
40557ac3b05SIke Panhc 	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
40657ac3b05SIke Panhc 	if (!priv)
40757ac3b05SIke Panhc 		return -ENOMEM;
40898ee6919SIke Panhc 	ideapad_priv = priv;
409c9f718d0SIke Panhc 	priv->handle = adevice->handle;
410c9f718d0SIke Panhc 	dev_set_drvdata(&adevice->dev, priv);
41198ee6919SIke Panhc 
41298ee6919SIke Panhc 	ret = ideapad_platform_init();
41398ee6919SIke Panhc 	if (ret)
41498ee6919SIke Panhc 		goto platform_failed;
41557ac3b05SIke Panhc 
416f63409aeSIke Panhc 	ret = ideapad_input_init();
417f63409aeSIke Panhc 	if (ret)
418f63409aeSIke Panhc 		goto input_failed;
419f63409aeSIke Panhc 
420c9f718d0SIke Panhc 	for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++) {
421c9f718d0SIke Panhc 		if (test_bit(ideapad_rfk_data[i].cfgbit, (unsigned long *)&cfg))
42257ac3b05SIke Panhc 			ideapad_register_rfkill(adevice, i);
42357ac3b05SIke Panhc 	}
42457ac3b05SIke Panhc 	ideapad_sync_rfk_state(adevice);
425c9f718d0SIke Panhc 
42657ac3b05SIke Panhc 	return 0;
42798ee6919SIke Panhc 
428f63409aeSIke Panhc input_failed:
429f63409aeSIke Panhc 	ideapad_platform_exit();
43098ee6919SIke Panhc platform_failed:
43198ee6919SIke Panhc 	kfree(priv);
43298ee6919SIke Panhc 	return ret;
43357ac3b05SIke Panhc }
43457ac3b05SIke Panhc 
43557ac3b05SIke Panhc static int ideapad_acpi_remove(struct acpi_device *adevice, int type)
43657ac3b05SIke Panhc {
43757ac3b05SIke Panhc 	struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
43857ac3b05SIke Panhc 	int i;
43957ac3b05SIke Panhc 
440c9f718d0SIke Panhc 	for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++)
44157ac3b05SIke Panhc 		ideapad_unregister_rfkill(adevice, i);
442f63409aeSIke Panhc 	ideapad_input_exit();
44398ee6919SIke Panhc 	ideapad_platform_exit();
44457ac3b05SIke Panhc 	dev_set_drvdata(&adevice->dev, NULL);
44557ac3b05SIke Panhc 	kfree(priv);
446c9f718d0SIke Panhc 
44757ac3b05SIke Panhc 	return 0;
44857ac3b05SIke Panhc }
44957ac3b05SIke Panhc 
45057ac3b05SIke Panhc static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event)
45157ac3b05SIke Panhc {
45257ac3b05SIke Panhc 	acpi_handle handle = adevice->handle;
45357ac3b05SIke Panhc 	unsigned long vpc1, vpc2, vpc_bit;
45457ac3b05SIke Panhc 
45557ac3b05SIke Panhc 	if (read_ec_data(handle, 0x10, &vpc1))
45657ac3b05SIke Panhc 		return;
45757ac3b05SIke Panhc 	if (read_ec_data(handle, 0x1A, &vpc2))
45857ac3b05SIke Panhc 		return;
45957ac3b05SIke Panhc 
46057ac3b05SIke Panhc 	vpc1 = (vpc2 << 8) | vpc1;
46157ac3b05SIke Panhc 	for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) {
46257ac3b05SIke Panhc 		if (test_bit(vpc_bit, &vpc1)) {
46357ac3b05SIke Panhc 			if (vpc_bit == 9)
46457ac3b05SIke Panhc 				ideapad_sync_rfk_state(adevice);
465f63409aeSIke Panhc 			else
466f63409aeSIke Panhc 				ideapad_input_report(vpc_bit);
46757ac3b05SIke Panhc 		}
46857ac3b05SIke Panhc 	}
46957ac3b05SIke Panhc }
47057ac3b05SIke Panhc 
47157ac3b05SIke Panhc static struct acpi_driver ideapad_acpi_driver = {
47257ac3b05SIke Panhc 	.name = "ideapad_acpi",
47357ac3b05SIke Panhc 	.class = "IdeaPad",
47457ac3b05SIke Panhc 	.ids = ideapad_device_ids,
47557ac3b05SIke Panhc 	.ops.add = ideapad_acpi_add,
47657ac3b05SIke Panhc 	.ops.remove = ideapad_acpi_remove,
47757ac3b05SIke Panhc 	.ops.notify = ideapad_acpi_notify,
47857ac3b05SIke Panhc 	.owner = THIS_MODULE,
47957ac3b05SIke Panhc };
48057ac3b05SIke Panhc 
48157ac3b05SIke Panhc 
48257ac3b05SIke Panhc static int __init ideapad_acpi_module_init(void)
48357ac3b05SIke Panhc {
48457ac3b05SIke Panhc 	acpi_bus_register_driver(&ideapad_acpi_driver);
48557ac3b05SIke Panhc 
48657ac3b05SIke Panhc 	return 0;
48757ac3b05SIke Panhc }
48857ac3b05SIke Panhc 
48957ac3b05SIke Panhc 
49057ac3b05SIke Panhc static void __exit ideapad_acpi_module_exit(void)
49157ac3b05SIke Panhc {
49257ac3b05SIke Panhc 	acpi_bus_unregister_driver(&ideapad_acpi_driver);
49357ac3b05SIke Panhc 
49457ac3b05SIke Panhc }
49557ac3b05SIke Panhc 
49657ac3b05SIke Panhc MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
49757ac3b05SIke Panhc MODULE_DESCRIPTION("IdeaPad ACPI Extras");
49857ac3b05SIke Panhc MODULE_LICENSE("GPL");
49957ac3b05SIke Panhc 
50057ac3b05SIke Panhc module_init(ideapad_acpi_module_init);
50157ac3b05SIke Panhc module_exit(ideapad_acpi_module_exit);
502