| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  *  ideapad_acpi.c - Lenovo IdeaPad ACPI Extras | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *  Copyright © 2010 Intel Corporation | 
					
						
							|  |  |  |  *  Copyright © 2010 David Woodhouse <dwmw2@infradead.org> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *  This program is free software; you can redistribute it and/or modify | 
					
						
							|  |  |  |  *  it under the terms of the GNU General Public License as published by | 
					
						
							|  |  |  |  *  the Free Software Foundation; either version 2 of the License, or | 
					
						
							|  |  |  |  *  (at your option) any later version. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *  This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  |  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  |  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  |  *  GNU General Public License for more details. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *  You should have received a copy of the GNU General Public License | 
					
						
							|  |  |  |  *  along with this program; if not, write to the Free Software | 
					
						
							|  |  |  |  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | 
					
						
							|  |  |  |  *  02110-1301, USA. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/kernel.h>
 | 
					
						
							|  |  |  | #include <linux/module.h>
 | 
					
						
							|  |  |  | #include <linux/init.h>
 | 
					
						
							|  |  |  | #include <linux/types.h>
 | 
					
						
							|  |  |  | #include <acpi/acpi_bus.h>
 | 
					
						
							|  |  |  | #include <acpi/acpi_drivers.h>
 | 
					
						
							|  |  |  | #include <linux/rfkill.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define IDEAPAD_DEV_CAMERA	0
 | 
					
						
							|  |  |  | #define IDEAPAD_DEV_WLAN	1
 | 
					
						
							|  |  |  | #define IDEAPAD_DEV_BLUETOOTH	2
 | 
					
						
							|  |  |  | #define IDEAPAD_DEV_3G		3
 | 
					
						
							|  |  |  | #define IDEAPAD_DEV_KILLSW	4
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | struct ideapad_private { | 
					
						
							|  |  |  | 	struct rfkill *rfk[5]; | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | }; | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | static struct { | 
					
						
							|  |  |  | 	char *name; | 
					
						
							|  |  |  | 	int type; | 
					
						
							|  |  |  | } ideapad_rfk_data[] = { | 
					
						
							|  |  |  | 	/* camera has no rfkill */ | 
					
						
							|  |  |  | 	{ "ideapad_wlan",	RFKILL_TYPE_WLAN }, | 
					
						
							|  |  |  | 	{ "ideapad_bluetooth",	RFKILL_TYPE_BLUETOOTH }, | 
					
						
							|  |  |  | 	{ "ideapad_3g",		RFKILL_TYPE_WWAN }, | 
					
						
							|  |  |  | 	{ "ideapad_killsw",	RFKILL_TYPE_WLAN } | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int ideapad_dev_exists(int device) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	acpi_status status; | 
					
						
							|  |  |  | 	union acpi_object in_param; | 
					
						
							|  |  |  | 	struct acpi_object_list input = { 1, &in_param }; | 
					
						
							|  |  |  | 	struct acpi_buffer output; | 
					
						
							|  |  |  | 	union acpi_object out_obj; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	output.length = sizeof(out_obj); | 
					
						
							|  |  |  | 	output.pointer = &out_obj; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	in_param.type = ACPI_TYPE_INTEGER; | 
					
						
							|  |  |  | 	in_param.integer.value = device + 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	status = acpi_evaluate_object(NULL, "\\_SB_.DECN", &input, &output); | 
					
						
							|  |  |  | 	if (ACPI_FAILURE(status)) { | 
					
						
							|  |  |  | 		printk(KERN_WARNING "IdeaPAD \\_SB_.DECN method failed %d. Is this an IdeaPAD?\n", status); | 
					
						
							|  |  |  | 		return -ENODEV; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (out_obj.type != ACPI_TYPE_INTEGER) { | 
					
						
							|  |  |  | 		printk(KERN_WARNING "IdeaPAD \\_SB_.DECN method returned unexpected type\n"); | 
					
						
							|  |  |  | 		return -ENODEV; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return out_obj.integer.value; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int ideapad_dev_get_state(int device) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	acpi_status status; | 
					
						
							|  |  |  | 	union acpi_object in_param; | 
					
						
							|  |  |  | 	struct acpi_object_list input = { 1, &in_param }; | 
					
						
							|  |  |  | 	struct acpi_buffer output; | 
					
						
							|  |  |  | 	union acpi_object out_obj; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	output.length = sizeof(out_obj); | 
					
						
							|  |  |  | 	output.pointer = &out_obj; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	in_param.type = ACPI_TYPE_INTEGER; | 
					
						
							|  |  |  | 	in_param.integer.value = device + 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	status = acpi_evaluate_object(NULL, "\\_SB_.GECN", &input, &output); | 
					
						
							|  |  |  | 	if (ACPI_FAILURE(status)) { | 
					
						
							|  |  |  | 		printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method failed %d\n", status); | 
					
						
							|  |  |  | 		return -ENODEV; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (out_obj.type != ACPI_TYPE_INTEGER) { | 
					
						
							|  |  |  | 		printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method returned unexpected type\n"); | 
					
						
							|  |  |  | 		return -ENODEV; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return out_obj.integer.value; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int ideapad_dev_set_state(int device, int state) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	acpi_status status; | 
					
						
							|  |  |  | 	union acpi_object in_params[2]; | 
					
						
							|  |  |  | 	struct acpi_object_list input = { 2, in_params }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	in_params[0].type = ACPI_TYPE_INTEGER; | 
					
						
							|  |  |  | 	in_params[0].integer.value = device + 1; | 
					
						
							|  |  |  | 	in_params[1].type = ACPI_TYPE_INTEGER; | 
					
						
							|  |  |  | 	in_params[1].integer.value = state; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	status = acpi_evaluate_object(NULL, "\\_SB_.SECN", &input, NULL); | 
					
						
							|  |  |  | 	if (ACPI_FAILURE(status)) { | 
					
						
							|  |  |  | 		printk(KERN_WARNING "IdeaPAD \\_SB_.SECN method failed %d\n", status); | 
					
						
							|  |  |  | 		return -ENODEV; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | static ssize_t show_ideapad_cam(struct device *dev, | 
					
						
							|  |  |  | 				struct device_attribute *attr, | 
					
						
							|  |  |  | 				char *buf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int state = ideapad_dev_get_state(IDEAPAD_DEV_CAMERA); | 
					
						
							|  |  |  | 	if (state < 0) | 
					
						
							|  |  |  | 		return state; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return sprintf(buf, "%d\n", state); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static ssize_t store_ideapad_cam(struct device *dev, | 
					
						
							|  |  |  | 				 struct device_attribute *attr, | 
					
						
							|  |  |  | 				 const char *buf, size_t count) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret, state; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!count) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 	if (sscanf(buf, "%i", &state) != 1) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:51 +01:00
										 |  |  | 	ret = ideapad_dev_set_state(IDEAPAD_DEV_CAMERA, !!state); | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 	if (ret < 0) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 	return count; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int ideapad_rfk_set(void *data, bool blocked) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int device = (unsigned long)data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (device == IDEAPAD_DEV_KILLSW) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 	return ideapad_dev_set_state(device, !blocked); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct rfkill_ops ideapad_rfk_ops = { | 
					
						
							|  |  |  | 	.set_block = ideapad_rfk_set, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | static void ideapad_sync_rfk_state(struct acpi_device *adevice) | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 	struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 	int hw_blocked = !ideapad_dev_get_state(IDEAPAD_DEV_KILLSW); | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 	rfkill_set_hw_state(priv->rfk[IDEAPAD_DEV_KILLSW], hw_blocked); | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 	for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++) | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 		if (priv->rfk[i]) | 
					
						
							|  |  |  | 			rfkill_set_hw_state(priv->rfk[i], hw_blocked); | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 	if (hw_blocked) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++) | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 		if (priv->rfk[i]) | 
					
						
							|  |  |  | 			rfkill_set_sw_state(priv->rfk[i], !ideapad_dev_get_state(i)); | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | static int ideapad_register_rfkill(struct acpi_device *adevice, int dev) | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 	struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 	priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev-1].name, &adevice->dev, | 
					
						
							|  |  |  | 				      ideapad_rfk_data[dev-1].type, &ideapad_rfk_ops, | 
					
						
							|  |  |  | 				      (void *)(long)dev); | 
					
						
							|  |  |  | 	if (!priv->rfk[dev]) | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 	ret = rfkill_register(priv->rfk[dev]); | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 	if (ret) { | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 		rfkill_destroy(priv->rfk[dev]); | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 		return ret; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev) | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 	struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!priv->rfk[dev]) | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 	rfkill_unregister(priv->rfk[dev]); | 
					
						
							|  |  |  | 	rfkill_destroy(priv->rfk[dev]); | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const struct acpi_device_id ideapad_device_ids[] = { | 
					
						
							|  |  |  | 	{ "VPC2004", 0}, | 
					
						
							|  |  |  | 	{ "", 0}, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | static int ideapad_acpi_add(struct acpi_device *adevice) | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | { | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 	int devs_present[5]; | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 	struct ideapad_private *priv; | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	for (i = IDEAPAD_DEV_CAMERA; i < IDEAPAD_DEV_KILLSW; i++) { | 
					
						
							|  |  |  | 		devs_present[i] = ideapad_dev_exists(i); | 
					
						
							|  |  |  | 		if (devs_present[i] < 0) | 
					
						
							|  |  |  | 			return devs_present[i]; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* The hardware switch is always present */ | 
					
						
							|  |  |  | 	devs_present[IDEAPAD_DEV_KILLSW] = 1; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 	priv = kzalloc(sizeof(*priv), GFP_KERNEL); | 
					
						
							|  |  |  | 	if (!priv) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 	if (devs_present[IDEAPAD_DEV_CAMERA]) { | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 		int ret = device_create_file(&adevice->dev, &dev_attr_camera_power); | 
					
						
							|  |  |  | 		if (ret) { | 
					
						
							|  |  |  | 			kfree(priv); | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 			return ret; | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 	dev_set_drvdata(&adevice->dev, priv); | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 	for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) { | 
					
						
							|  |  |  | 		if (!devs_present[i]) | 
					
						
							|  |  |  | 			continue; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 		ideapad_register_rfkill(adevice, i); | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 	ideapad_sync_rfk_state(adevice); | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | static int ideapad_acpi_remove(struct acpi_device *adevice, int type) | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 	struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 	int i; | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	device_remove_file(&adevice->dev, &dev_attr_camera_power); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) | 
					
						
							|  |  |  | 		ideapad_unregister_rfkill(adevice, i); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dev_set_drvdata(&adevice->dev, NULL); | 
					
						
							|  |  |  | 	kfree(priv); | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event) | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2010-08-11 17:59:35 +01:00
										 |  |  | 	ideapad_sync_rfk_state(adevice); | 
					
						
							| 
									
										
										
										
											2010-08-10 23:44:05 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct acpi_driver ideapad_acpi_driver = { | 
					
						
							|  |  |  | 	.name = "ideapad_acpi", | 
					
						
							|  |  |  | 	.class = "IdeaPad", | 
					
						
							|  |  |  | 	.ids = ideapad_device_ids, | 
					
						
							|  |  |  | 	.ops.add = ideapad_acpi_add, | 
					
						
							|  |  |  | 	.ops.remove = ideapad_acpi_remove, | 
					
						
							|  |  |  | 	.ops.notify = ideapad_acpi_notify, | 
					
						
							|  |  |  | 	.owner = THIS_MODULE, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int __init ideapad_acpi_module_init(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	acpi_bus_register_driver(&ideapad_acpi_driver); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void __exit ideapad_acpi_module_exit(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	acpi_bus_unregister_driver(&ideapad_acpi_driver); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); | 
					
						
							|  |  |  | MODULE_DESCRIPTION("IdeaPad ACPI Extras"); | 
					
						
							|  |  |  | MODULE_LICENSE("GPL"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module_init(ideapad_acpi_module_init); | 
					
						
							|  |  |  | module_exit(ideapad_acpi_module_exit); |