linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] ideapad-laptop: add new driver
@ 2010-08-13  4:08 Ike Panhc
  2010-08-13  6:37 ` Corentin Chary
  2010-08-13  9:14 ` David Woodhouse
  0 siblings, 2 replies; 14+ messages in thread
From: Ike Panhc @ 2010-08-13  4:08 UTC (permalink / raw)
  To: platform-driver-x86, linux-acpi
  Cc: Matthew Garrett, Len Brown, Randy Dunlap, Corentin Chary,
	Andrew Morton, Alan Cox, Thomas Renninger, linux-kernel

ideapad-laptop is a new driver which enable hotkeys on Lenovo Ideapad laptops

The driver will bind on ACPI HID:VPC2004. When hotkey pressed, the notify
function will be called and query EC to tell which key pressed.

Hotkeys enabled listed below:
 * LCD backlight switch - reports KEY_DISPLAY_OFF
 * One key restore - reports KEY_PROG1
 * Brightness Up/down - reports KEY_BRIGHTNESS_CYCLE
   Both up/down keys reports the same event. Need to find other way to detect
 * Touchpad switch - reports KEY_F13
 * Video output switch - reports KEY_SWITCHVIDEOMODE
 * Camera switch - reports KEY_CAMERA
 * Video resolution switch - reports KEY_VIDEO_NEXT
 * S/W rfkill key - reports KEY_WLAN

The developing git tree as reference:
  git://kernel.ubuntu.com/ikepanhc/ideapad-laptop.git

This patch made against current checkout of mainline kernel.

Signed-off-by: Ike Panhc <ike.pan@canonical.com>
---
 drivers/platform/x86/Kconfig          |   11 +
 drivers/platform/x86/Makefile         |    1 +
 drivers/platform/x86/ideapad-laptop.c |  398 +++++++++++++++++++++++++++++++++
 3 files changed, 410 insertions(+), 0 deletions(-)
 create mode 100644 drivers/platform/x86/ideapad-laptop.c

diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 79baa63..e9a203d 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -581,4 +581,15 @@ config INTEL_IPS
 	  functionality.  If in doubt, say Y here; it will only load on
 	  supported platforms.
 
+config IDEAPAD_LAPTOP
+	tristate "Ideapad Laptop Extras"
+	depends on ACPI
+	depends on INPUT
+	select INPUT_SPARSEKMAP
+	---help---
+	  This is the ACPI extra Linux driver for ideapad laptops which enable
+	  ACPI event based hotkey.
+
+	  If you have an Ideapad laptop, say Y or M here.
+
 endif # X86_PLATFORM_DEVICES
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 4744c77..47ca2b9 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -29,4 +29,5 @@ obj-$(CONFIG_INTEL_SCU_IPC)	+= intel_scu_ipc.o
 obj-$(CONFIG_RAR_REGISTER)	+= intel_rar_register.o
 obj-$(CONFIG_INTEL_IPS)		+= intel_ips.o
 obj-$(CONFIG_GPIO_INTEL_PMIC)	+= intel_pmic_gpio.o
+obj-$(CONFIG_IDEAPAD_LAPTOP)	+= ideapad-laptop.o
 
diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c
new file mode 100644
index 0000000..d666d24
--- /dev/null
+++ b/drivers/platform/x86/ideapad-laptop.c
@@ -0,0 +1,398 @@
+/*
+ *  ideapad-laptop.c: ACPI extra driver for ideapad series laptop
+ *
+ *  This driver is based on eeepc-laptop.c
+ *
+ *  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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <acpi/acpi_drivers.h>
+#include <acpi/acpi_bus.h>
+#include <linux/uaccess.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+
+#define IDEAPAD_LAPTOP_NAME	"Ideapad ACPI extra driver"
+#define IDEAPAD_LAPTOP_FILE	"ideapad"
+
+#define IDEAPAD_ACPI_CLASS		"hotkey"
+#define IDEAPAD_ACPI_DEVICE_NAME	"Hotkey"
+#define IDEAPAD_ACPI_HID		"VPC2004"
+
+#define IDEAPAD_EC_TIMEOUT	(25)
+
+MODULE_AUTHOR("Ike Panhc");
+MODULE_DESCRIPTION(IDEAPAD_LAPTOP_NAME);
+MODULE_LICENSE("GPL");
+
+/*
+ * This is the main structure, we can use it to store useful information
+ */
+struct ideapad_laptop {
+	acpi_handle handle;		/* the handle of the acpi device */
+	u32 cm_supported;		/* the control methods supported
+					   by this BIOS */
+	u16 event_count[128];		/* count for each event */
+
+	struct platform_device *platform_device;
+	struct input_dev *inputdev;
+
+	int cfg;
+};
+
+/*
+ * ACPI Helpers
+ */
+static int read_method_int(acpi_handle handle, const char *method, int *val)
+{
+	acpi_status status;
+	unsigned long long result;
+
+	status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
+	if (ACPI_FAILURE(status)) {
+		*val = -1;
+		return -1;
+	} else {
+		*val = result;
+		return 0;
+	}
+}
+
+static int method_vpcr(acpi_handle handle, int cmd, int *ret)
+{
+	acpi_status status;
+	unsigned long long result;
+	struct acpi_object_list params;
+	union acpi_object in_obj;
+
+	params.count = 1;
+	params.pointer = &in_obj;
+	in_obj.type = ACPI_TYPE_INTEGER;
+	in_obj.integer.value = cmd;
+
+	status = acpi_evaluate_integer(handle, "VPCR", &params, &result);
+
+	if (ACPI_FAILURE(status)) {
+		*ret = -1;
+		return -1;
+	} else {
+		*ret = result;
+		return 0;
+	}
+}
+
+static int method_vpcw(acpi_handle handle, int cmd, int data)
+{
+	struct acpi_object_list params;
+	union acpi_object in_obj[2];
+	acpi_status status;
+
+	params.count = 2;
+	params.pointer = in_obj;
+	in_obj[0].type = ACPI_TYPE_INTEGER;
+	in_obj[0].integer.value = cmd;
+	in_obj[1].type = ACPI_TYPE_INTEGER;
+	in_obj[1].integer.value = data;
+	status = acpi_evaluate_object(handle, "VPCW", &params, NULL);
+	if (status != AE_OK)
+		return -1;
+	return 0;
+}
+
+static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data)
+{
+	int val;
+	unsigned long int start_jiffies, now_jiffies;
+
+	if (method_vpcw(handle, 1, cmd))
+		return -1;
+
+	for (start_jiffies = jiffies;; ) {
+		if (method_vpcr(handle, 1, &val))
+			return -1;
+		if (val == 0) {
+			if (method_vpcr(handle, 0, &val))
+				return -1;
+			*data = val;
+			return 0;
+		}
+		now_jiffies = jiffies;
+		if ((now_jiffies-start_jiffies) > (HZ)/IDEAPAD_EC_TIMEOUT+1) {
+			pr_err("timeout in read_ec_cmd\n");
+			return -1;
+		}
+	}
+}
+
+/*
+ * Platform driver
+ */
+static struct platform_driver platform_driver = {
+	.driver = {
+		.name = IDEAPAD_LAPTOP_FILE,
+		.owner = THIS_MODULE,
+	}
+};
+
+static int ideapad_platform_init(struct ideapad_laptop *ideapad)
+{
+	int result;
+
+	ideapad->platform_device = platform_device_alloc(IDEAPAD_LAPTOP_FILE,
+							-1);
+	if (!ideapad->platform_device)
+		return -ENOMEM;
+	platform_set_drvdata(ideapad->platform_device, ideapad);
+
+	result = platform_device_add(ideapad->platform_device);
+	if (result)
+		goto fail_platform_device;
+
+	return 0;
+
+fail_platform_device:
+	platform_device_put(ideapad->platform_device);
+	return result;
+}
+
+static void ideapad_platform_exit(struct ideapad_laptop *ideapad)
+{
+	platform_device_unregister(ideapad->platform_device);
+}
+
+/*
+ * Input device
+ */
+static const struct key_entry ideapad_keymap[] = {
+	{ KE_KEY, 0x02, { KEY_DISPLAY_OFF } },
+	{ KE_KEY, 0x03, { KEY_PROG1 } },
+	{ KE_KEY, 0x04, { KEY_BRIGHTNESS_CYCLE } },
+	{ KE_KEY, 0x05, { KEY_F13 } },
+	{ KE_KEY, 0x06, { KEY_SWITCHVIDEOMODE } },
+	{ KE_KEY, 0x07, { KEY_CAMERA } },
+	{ KE_KEY, 0x0B, { KEY_VIDEO_NEXT } },
+	{ KE_KEY, 0x0D, { KEY_WLAN } },
+	{ KE_END, 0},
+};
+
+static int ideapad_input_init(struct ideapad_laptop *ideapad)
+{
+	struct input_dev *input;
+	int error;
+
+	input = input_allocate_device();
+	if (!input) {
+		pr_info("Unable to allocate input device\n");
+		return -ENOMEM;
+	}
+
+	input->name = "Lenovo Ideapad Hotkeys";
+	input->phys = IDEAPAD_LAPTOP_FILE "/input0";
+	input->id.bustype = BUS_HOST;
+	input->dev.parent = &ideapad->platform_device->dev;
+
+	error = sparse_keymap_setup(input, ideapad_keymap, NULL);
+	if (error) {
+		pr_err("Unable to setup input device keymap\n");
+		goto err_free_dev;
+	}
+
+	error = input_register_device(input);
+	if (error) {
+		pr_err("Unable to register input device\n");
+		goto err_free_keymap;
+	}
+
+	ideapad->inputdev = input;
+	return 0;
+
+err_free_keymap:
+	sparse_keymap_free(input);
+err_free_dev:
+	input_free_device(input);
+	return error;
+}
+
+static void ideapad_input_exit(struct ideapad_laptop *ideapad)
+{
+	if (ideapad->inputdev)
+		input_unregister_device(ideapad->inputdev);
+}
+
+/*
+ * ACPI driver
+ */
+static void ideapad_acpi_notify(struct acpi_device *device, u32 event)
+{
+	struct ideapad_laptop *ideapad = device->driver_data;
+	acpi_handle handle = ideapad->handle;
+	unsigned long vpc1, vpc2, vpc_bit;
+
+	if (read_ec_data(handle, 0x10, &vpc1))
+		return;
+	if (read_ec_data(handle, 0x1A, &vpc2))
+		return;
+
+	vpc1 = (vpc2 << 8) | vpc1;
+	for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) {
+		if (test_bit(vpc_bit, &vpc1)) {
+			if (vpc_bit == 9)
+				continue;
+			sparse_keymap_report_event(ideapad->inputdev, event, 1,
+						true);
+		}
+	}
+}
+
+static int ideapad_acpi_init(struct ideapad_laptop *ideapad,
+			   struct acpi_device *device)
+{
+	int result;
+	acpi_handle handle = ideapad->handle;
+
+	result = acpi_bus_get_status(device);
+	if (result)
+		return result;
+	if (!device->status.present) {
+		pr_err("Hotkey device not present, aborting\n");
+		return -ENODEV;
+	}
+
+	if (read_method_int(handle, "_STA", &result)) {
+		pr_err("Access _STA failed\n");
+		return -ENODEV;
+	}
+	if (result != 0x0F) {
+		pr_err("_STA return unknown value: 0x%X\n", result);
+		return -ENODEV;
+	}
+
+	if (read_method_int(handle, "_CFG", &result)) {
+		pr_err("Access _CFG failed\n");
+		return -ENODEV;
+	}
+	ideapad->cfg = result;
+
+	return 0;
+}
+
+static bool ideapad_device_present;
+
+static int __devinit ideapad_acpi_add(struct acpi_device *device)
+{
+	struct ideapad_laptop *ideapad;
+	int result;
+
+	ideapad = kzalloc(sizeof(struct ideapad_laptop), GFP_KERNEL);
+	if (!ideapad)
+		return -ENOMEM;
+	ideapad->handle = device->handle;
+	strcpy(acpi_device_name(device), IDEAPAD_ACPI_DEVICE_NAME);
+	strcpy(acpi_device_class(device), IDEAPAD_ACPI_CLASS);
+	device->driver_data = ideapad;
+
+	result = ideapad_acpi_init(ideapad, device);
+	if (result)
+		goto fail_platform;
+
+	result = ideapad_platform_init(ideapad);
+	if (result)
+		goto fail_platform;
+
+	result = ideapad_input_init(ideapad);
+	if (result)
+		goto fail_input;
+
+	ideapad_device_present = true;
+	return 0;
+
+fail_input:
+	ideapad_platform_exit(ideapad);
+fail_platform:
+	kfree(ideapad);
+
+	return result;
+}
+
+static int ideapad_acpi_remove(struct acpi_device *device, int type)
+{
+	struct ideapad_laptop *ideapad = acpi_driver_data(device);
+
+	ideapad_input_exit(ideapad);
+	ideapad_platform_exit(ideapad);
+
+	kfree(ideapad);
+	return 0;
+}
+
+
+static const struct acpi_device_id ideapad_device_ids[] = {
+	{IDEAPAD_ACPI_HID, 0},
+	{"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
+
+static struct acpi_driver ideapad_acpi_driver = {
+	.name = IDEAPAD_LAPTOP_NAME,
+	.class = IDEAPAD_ACPI_CLASS,
+	.owner = THIS_MODULE,
+	.ids = ideapad_device_ids,
+	.flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
+	.ops = {
+		.add = ideapad_acpi_add,
+		.remove = ideapad_acpi_remove,
+		.notify = ideapad_acpi_notify,
+	},
+};
+
+
+static int __init ideapad_laptop_init(void)
+{
+	int result;
+
+	result = platform_driver_register(&platform_driver);
+	if (result < 0)
+		return result;
+
+	result = acpi_bus_register_driver(&ideapad_acpi_driver);
+	if (result < 0)
+		goto fail_acpi_driver;
+
+	if (!ideapad_device_present) {
+		result = -ENODEV;
+		goto fail_no_device;
+	}
+
+	return 0;
+
+fail_no_device:
+	acpi_bus_unregister_driver(&ideapad_acpi_driver);
+fail_acpi_driver:
+	platform_driver_unregister(&platform_driver);
+	return result;
+}
+
+static void __exit ideapad_laptop_exit(void)
+{
+	acpi_bus_unregister_driver(&ideapad_acpi_driver);
+	platform_driver_unregister(&platform_driver);
+}
+
+module_init(ideapad_laptop_init);
+module_exit(ideapad_laptop_exit);
-- 
1.7.0.4


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH] ideapad-laptop: add new driver
  2010-08-13  4:08 [PATCH] ideapad-laptop: add new driver Ike Panhc
@ 2010-08-13  6:37 ` Corentin Chary
  2010-08-13  6:53   ` Ike Panhc
  2010-08-13  9:14 ` David Woodhouse
  1 sibling, 1 reply; 14+ messages in thread
From: Corentin Chary @ 2010-08-13  6:37 UTC (permalink / raw)
  To: Ike Panhc
  Cc: platform-driver-x86, linux-acpi, Matthew Garrett, Len Brown,
	Randy Dunlap, Andrew Morton, Alan Cox, Thomas Renninger,
	linux-kernel

On Fri, Aug 13, 2010 at 6:08 AM, Ike Panhc <ike.pan@canonical.com> wrote:
> ideapad-laptop is a new driver which enable hotkeys on Lenovo Ideapad laptops
>
> The driver will bind on ACPI HID:VPC2004. When hotkey pressed, the notify
> function will be called and query EC to tell which key pressed.
>
> Hotkeys enabled listed below:
>  * LCD backlight switch - reports KEY_DISPLAY_OFF
>  * One key restore - reports KEY_PROG1
>  * Brightness Up/down - reports KEY_BRIGHTNESS_CYCLE
>   Both up/down keys reports the same event. Need to find other way to detect
>  * Touchpad switch - reports KEY_F13
>  * Video output switch - reports KEY_SWITCHVIDEOMODE
>  * Camera switch - reports KEY_CAMERA
>  * Video resolution switch - reports KEY_VIDEO_NEXT
>  * S/W rfkill key - reports KEY_WLAN
>
> The developing git tree as reference:
>  git://kernel.ubuntu.com/ikepanhc/ideapad-laptop.git
>
> This patch made against current checkout of mainline kernel.

Cool, now we got two ideapad drivers :p.

David just sent an rfkill driver to this list. Maybe you could merge your work ?

Thanks,
-- 
Corentin Chary
http://xf.iksaif.net

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] ideapad-laptop: add new driver
  2010-08-13  6:37 ` Corentin Chary
@ 2010-08-13  6:53   ` Ike Panhc
  2010-08-13  7:01     ` Corentin Chary
  2010-08-13  9:22     ` David Woodhouse
  0 siblings, 2 replies; 14+ messages in thread
From: Ike Panhc @ 2010-08-13  6:53 UTC (permalink / raw)
  To: Corentin Chary
  Cc: platform-driver-x86, linux-acpi, Matthew Garrett, Len Brown,
	Randy Dunlap, Andrew Morton, Alan Cox, Thomas Renninger,
	linux-kernel

On 08/13/2010 02:37 PM, Corentin Chary wrote:
> On Fri, Aug 13, 2010 at 6:08 AM, Ike Panhc <ike.pan@canonical.com> wrote:
>> ideapad-laptop is a new driver which enable hotkeys on Lenovo Ideapad laptops
>>
>> The driver will bind on ACPI HID:VPC2004. When hotkey pressed, the notify
>> function will be called and query EC to tell which key pressed.
>>
>> Hotkeys enabled listed below:
>>  * LCD backlight switch - reports KEY_DISPLAY_OFF
>>  * One key restore - reports KEY_PROG1
>>  * Brightness Up/down - reports KEY_BRIGHTNESS_CYCLE
>>   Both up/down keys reports the same event. Need to find other way to detect
>>  * Touchpad switch - reports KEY_F13
>>  * Video output switch - reports KEY_SWITCHVIDEOMODE
>>  * Camera switch - reports KEY_CAMERA
>>  * Video resolution switch - reports KEY_VIDEO_NEXT
>>  * S/W rfkill key - reports KEY_WLAN
>>
>> The developing git tree as reference:
>>  git://kernel.ubuntu.com/ikepanhc/ideapad-laptop.git
>>
>> This patch made against current checkout of mainline kernel.
> 
> Cool, now we got two ideapad drivers :p.
> 
> David just sent an rfkill driver to this list. Maybe you could merge your work ?
> 
> Thanks,
Could you point me where is the rfkill driver? Sorry I do not find it.

I will see what I can do.

Thanks.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] ideapad-laptop: add new driver
  2010-08-13  6:53   ` Ike Panhc
@ 2010-08-13  7:01     ` Corentin Chary
  2010-08-13  7:15       ` Ike Panhc
  2010-08-13  9:22     ` David Woodhouse
  1 sibling, 1 reply; 14+ messages in thread
From: Corentin Chary @ 2010-08-13  7:01 UTC (permalink / raw)
  To: Ike Panhc
  Cc: platform-driver-x86, linux-acpi, Matthew Garrett, Len Brown,
	Randy Dunlap, Andrew Morton, Alan Cox, Thomas Renninger,
	linux-kernel, David Woodhouse

> Could you point me where is the rfkill driver? Sorry I do not find it.
>
> I will see what I can do.
>
> Thanks.
>

Here is the thread
http://thread.gmane.org/gmane.linux.drivers.platform.x86.devel/471

-- 
Corentin Chary
http://xf.iksaif.net

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] ideapad-laptop: add new driver
  2010-08-13  7:01     ` Corentin Chary
@ 2010-08-13  7:15       ` Ike Panhc
  0 siblings, 0 replies; 14+ messages in thread
From: Ike Panhc @ 2010-08-13  7:15 UTC (permalink / raw)
  To: Corentin Chary
  Cc: platform-driver-x86, linux-acpi, Matthew Garrett, Len Brown,
	Randy Dunlap, Andrew Morton, Alan Cox, Thomas Renninger,
	linux-kernel, David Woodhouse

On 08/13/2010 03:01 PM, Corentin Chary wrote:
>> Could you point me where is the rfkill driver? Sorry I do not find it.
>>
>> I will see what I can do.
>>
>> Thanks.
>>
> 
> Here is the thread
> http://thread.gmane.org/gmane.linux.drivers.platform.x86.devel/471
> 
Yes, I believe I can. will read patch from David and merge.

But I think i will not use \_SB_.SECN as major interface. I know there is
another command code of EC that can access HW switch status and shutdown
the interface.



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] ideapad-laptop: add new driver
  2010-08-13  4:08 [PATCH] ideapad-laptop: add new driver Ike Panhc
  2010-08-13  6:37 ` Corentin Chary
@ 2010-08-13  9:14 ` David Woodhouse
  2010-08-13  9:27   ` Ike Panhc
  1 sibling, 1 reply; 14+ messages in thread
From: David Woodhouse @ 2010-08-13  9:14 UTC (permalink / raw)
  To: Ike Panhc
  Cc: platform-driver-x86, linux-acpi, Matthew Garrett, Len Brown,
	Randy Dunlap, Corentin Chary, Andrew Morton, Alan Cox,
	Thomas Renninger, linux-kernel

On Fri, 2010-08-13 at 12:08 +0800, Ike Panhc wrote:
> 
> The driver will bind on ACPI HID:VPC2004. When hotkey pressed, the
> notify
> function will be called and query EC to tell which key pressed.
> 
> Hotkeys enabled listed below:
>  * LCD backlight switch - reports KEY_DISPLAY_OFF
>  * One key restore - reports KEY_PROG1
>  * Brightness Up/down - reports KEY_BRIGHTNESS_CYCLE
>    Both up/down keys reports the same event. Need to find other way to
> detect
>  * Touchpad switch - reports KEY_F13
>  * Video output switch - reports KEY_SWITCHVIDEOMODE
>  * Camera switch - reports KEY_CAMERA
>  * Video resolution switch - reports KEY_VIDEO_NEXT
>  * S/W rfkill key - reports KEY_WLAN 

Hm, interesting. What model (and BIOS version) are you testing with?

On my S10-3, (DMI BIOS version 2ACN23WW, date 03/12/2010), I get these
keys through the normal keyboard controller -- and we can tell the
brightness up/down apart, too.

http://git.kernel.org/?p=linux/hotplug/udev.git;a=commitdiff;h=9cd075e3

Do you see those keycodes too... and do they *stop* arriving through the
keyboard controller when you load your driver?

-- 
David Woodhouse                            Open Source Technology Centre
David.Woodhouse@intel.com                              Intel Corporation


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] ideapad-laptop: add new driver
  2010-08-13  6:53   ` Ike Panhc
  2010-08-13  7:01     ` Corentin Chary
@ 2010-08-13  9:22     ` David Woodhouse
  1 sibling, 0 replies; 14+ messages in thread
From: David Woodhouse @ 2010-08-13  9:22 UTC (permalink / raw)
  To: Ike Panhc
  Cc: Corentin Chary, platform-driver-x86, linux-acpi, Matthew Garrett,
	Len Brown, Randy Dunlap, Andrew Morton, Alan Cox,
	Thomas Renninger, linux-kernel

On Fri, 2010-08-13 at 14:53 +0800, Ike Panhc wrote:
> Could you point me where is the rfkill driver? Sorry I do not find it.

	git://git.infradead.org/users/dwmw2/ideapad-2.6.git
	http://git.infradead.org/users/dwmw2/ideapad-2.6.git

After fixing Corentin's review feedback and conferring with Matthew, I
asked Linus to pull it last night -- without the rfkill support, people
were having to reinstall Windows and use it to unblock the rfkill and
get wireless working.

http://bugs.meego.com/show_bug.cgi?id=4086
https://bugs.launchpad.net/ubuntu/+source/linux/+bug/577114
http://forums.lenovo.com/t5/Linux-Discussion/Solution-to-rfkill-wireless-problems-w-IdeaPad-S12/td-p/224122

-- 
David Woodhouse                            Open Source Technology Centre
David.Woodhouse@intel.com                              Intel Corporation


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] ideapad-laptop: add new driver
  2010-08-13  9:14 ` David Woodhouse
@ 2010-08-13  9:27   ` Ike Panhc
  2010-08-13  9:34     ` David Woodhouse
  0 siblings, 1 reply; 14+ messages in thread
From: Ike Panhc @ 2010-08-13  9:27 UTC (permalink / raw)
  To: David Woodhouse
  Cc: platform-driver-x86, linux-acpi, Matthew Garrett, Len Brown,
	Randy Dunlap, Corentin Chary, Andrew Morton, Alan Cox,
	Thomas Renninger, linux-kernel

On 08/13/2010 05:14 PM, David Woodhouse wrote:
> 
> Hm, interesting. What model (and BIOS version) are you testing with?
I have two machine, B550 and S10-3, the BIOS date/version of S10-3 is the
same with you.

> 
> On my S10-3, (DMI BIOS version 2ACN23WW, date 03/12/2010), I get these
> keys through the normal keyboard controller -- and we can tell the
> brightness up/down apart, too.
> 
> http://git.kernel.org/?p=linux/hotplug/udev.git;a=commitdiff;h=9cd075e3
> 
> Do you see those keycodes too... and do they *stop* arriving through the
> keyboard controller when you load your driver?
> 
Yes, IIRC EC will send i8042 keycode when you booting with QS button, not
with power button. atkbd.c reports unknown key after booting with QS button.


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] ideapad-laptop: add new driver
  2010-08-13  9:27   ` Ike Panhc
@ 2010-08-13  9:34     ` David Woodhouse
  2010-08-14  2:56       ` Ike Panhc
                         ` (2 more replies)
  0 siblings, 3 replies; 14+ messages in thread
From: David Woodhouse @ 2010-08-13  9:34 UTC (permalink / raw)
  To: Ike Panhc
  Cc: platform-driver-x86, linux-acpi, Matthew Garrett, Len Brown,
	Randy Dunlap, Corentin Chary, Andrew Morton, Alan Cox,
	Thomas Renninger, linux-kernel

On Fri, 2010-08-13 at 17:27 +0800, Ike Panhc wrote:
> Yes, IIRC EC will send i8042 keycode when you booting with QS button, not
> with power button. atkbd.c reports unknown key after booting with QS button. 

That's interesting. I don't *ever* boot with the QS button; I get the
keycodes when I boot normally. I tried the QS button precisely once, but
only *after* I blew away the original contents of the hard drive with my
MeeGo installation, so it didn't boot.

Do you know how to restore it? Or do you have a copy of the 'lenovo-ec'
module that it apparently contains?

I did keep the Windows restore partition, just in case I needed to go
back to it to get wireless working. But when I tried to recover, it told
me it didn't like my partition table -- so I fixed that and next time I
booted into the OS recovery, it just dumped me at a command prompt and
didn't even manage to start the graphical recovery tool.

-- 
David Woodhouse                            Open Source Technology Centre
David.Woodhouse@intel.com                              Intel Corporation


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] ideapad-laptop: add new driver
  2010-08-13  9:34     ` David Woodhouse
@ 2010-08-14  2:56       ` Ike Panhc
  2010-08-14  9:49         ` David Woodhouse
  2010-08-15  2:33         ` Len Brown
  2010-08-15  8:14       ` Florian Echtler
  2010-08-15  8:20       ` Florian Echtler
  2 siblings, 2 replies; 14+ messages in thread
From: Ike Panhc @ 2010-08-14  2:56 UTC (permalink / raw)
  To: David Woodhouse
  Cc: platform-driver-x86, linux-acpi, Matthew Garrett, Len Brown,
	Randy Dunlap, Corentin Chary, Andrew Morton, Alan Cox,
	Thomas Renninger, linux-kernel

On 08/13/2010 05:34 PM, David Woodhouse wrote:
> On Fri, 2010-08-13 at 17:27 +0800, Ike Panhc wrote:
>> Yes, IIRC EC will send i8042 keycode when you booting with QS button, not
>> with power button. atkbd.c reports unknown key after booting with QS button. 
> 
> That's interesting. I don't *ever* boot with the QS button; I get the
> keycodes when I boot normally. I tried the QS button precisely once, but
> only *after* I blew away the original contents of the hard drive with my
> MeeGo installation, so it didn't boot.
> 
> Do you know how to restore it? Or do you have a copy of the 'lenovo-ec'
> module that it apparently contains?
> 
> I did keep the Windows restore partition, just in case I needed to go
> back to it to get wireless working. But when I tried to recover, it told
> me it didn't like my partition table -- so I fixed that and next time I
> booted into the OS recovery, it just dumped me at a command prompt and
> didn't even manage to start the graphical recovery tool.
> 
No, I received the ideapads without any OS preinstalled, and get the design
spec with NDA. I asked and got the approve to send the driver to mainline
kernel according to the spec.

Since your driver is pulled. I am happy to have another patch made against
your driver and post again.


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] ideapad-laptop: add new driver
  2010-08-14  2:56       ` Ike Panhc
@ 2010-08-14  9:49         ` David Woodhouse
  2010-08-15  2:33         ` Len Brown
  1 sibling, 0 replies; 14+ messages in thread
From: David Woodhouse @ 2010-08-14  9:49 UTC (permalink / raw)
  To: Ike Panhc
  Cc: platform-driver-x86, linux-acpi, Matthew Garrett, Len Brown,
	Randy Dunlap, Corentin Chary, Andrew Morton, Alan Cox,
	Thomas Renninger, linux-kernel

On Sat, 2010-08-14 at 10:56 +0800, Ike Panhc wrote:
> No, I received the ideapads without any OS preinstalled, and get the
> design spec with NDA. I asked and got the approve to send the driver
> to mainline kernel according to the spec. 

Cool. I'd like to have a copy of that spec. Please could you tell me who
in Lenovo (presumably) I should contact?

-- 
David Woodhouse                            Open Source Technology Centre
David.Woodhouse@intel.com                              Intel Corporation


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] ideapad-laptop: add new driver
  2010-08-14  2:56       ` Ike Panhc
  2010-08-14  9:49         ` David Woodhouse
@ 2010-08-15  2:33         ` Len Brown
  1 sibling, 0 replies; 14+ messages in thread
From: Len Brown @ 2010-08-15  2:33 UTC (permalink / raw)
  To: Ike Panhc
  Cc: David Woodhouse, platform-driver-x86, linux-acpi,
	Matthew Garrett, Randy Dunlap, Corentin Chary, Andrew Morton,
	Alan Cox, Thomas Renninger, Linux Kernel Mailing List

If you merge the drivers, I'd sure prefer the name "ideapad-laptop"
over "ideapad-acpi".

While the maintainerless "toshiba-acpi" driver was a pioneer,
and thinkpad-acpi feared installed-base confusion when considering
a name change, we tend to reserve the "acpi" in drivers for things
that supply ACPI, rather than simply use ACPI.

Also, you may find that the platform driver ends up doing
more for the laptop than just using what is behind its
platform-specific ACPI extension.

thanks,
Len Brown, Intel Open Source Technology Center


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] ideapad-laptop: add new driver
  2010-08-13  9:34     ` David Woodhouse
  2010-08-14  2:56       ` Ike Panhc
@ 2010-08-15  8:14       ` Florian Echtler
  2010-08-15  8:20       ` Florian Echtler
  2 siblings, 0 replies; 14+ messages in thread
From: Florian Echtler @ 2010-08-15  8:14 UTC (permalink / raw)
  To: David Woodhouse
  Cc: Ike Panhc, platform-driver-x86, linux-acpi, Matthew Garrett,
	Len Brown, Randy Dunlap, Corentin Chary, Andrew Morton, Alan Cox,
	Thomas Renninger, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 441 bytes --]

On Fri, 2010-08-13 at 10:34 +0100, David Woodhouse wrote:
> Do you know how to restore it? Or do you have a copy of the 'lenovo-ec'
> module that it apparently contains?
Hello David,

I do have that module: I coaxed it out of Lenovo with the help of
gpl-violations.org as no source was available on my S10-3t. Since it is
flagged as Dual BSD/GPL license, there should be no harm in posting it
here. I hope it helps you in some way.

Florian

[-- Attachment #2: lenovo_ec.c --]
[-- Type: text/x-csrc, Size: 27251 bytes --]

/*
 * lenovo_s11_ec.c Lenovo_S11 ACPI EC Extras
 */

#include <linux/init.h>
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/fcntl.h> /* O_ACCMODE */
#include <asm/system.h> /* cli(), *_flags */
#include <asm/uaccess.h> /* copy_from/to_user */
#include <asm/io.h> /* inb, outb */
#include <linux/sched.h> /* set_cpus_allowed */
#include <linux/acpi.h>
#include <linux/seq_file.h>
#include <acpi/acpi_drivers.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)
    typedef unsigned long long evaluate_size_t;
#else
    typedef unsigned long evaluate_size_t;
#endif

static DECLARE_MUTEX(gecn_sem);
static DECLARE_MUTEX(secn_sem);

static  struct proc_dir_entry *lenovo_ec_fs_dir;
static char *model = NULL;
struct acpi_lenovo_cs2_device {
        acpi_handle handle;
        char DECN;              
        char GECN;            
        char SECN;            
};

struct acpi_lenovo_cs2_device lenovo_cs2_device;
static acpi_handle vpc0_handle;
MODULE_LICENSE("Dual BSD/GPL");
module_param(model, charp, S_IRUGO);
/*
 *  Proc handle for reading the status of ec device.
 */

static int acpi_s11_ec_read_camera(struct seq_file *seq, void *offset)
{
	char tmp;
	if(ec_read(0xb8,&tmp)){
		printk("ec_read fail\n");
		return -EFAULT;
	}
	seq_printf(seq, "%d\n",!(tmp & 0x8) == 0);
	return 0;
}

static int acpi_s11_ec_read_wifi(struct seq_file *seq, void *offset)
{
	char tmp;
	if(ec_read(0xbb,&tmp)){
		printk("ec_read fail\n");
		return -EFAULT;
	}
	seq_printf(seq, "%d\n",!(tmp & 0x1) == 0);
	return 0;
}

static int acpi_s11_ec_read_dvme(struct seq_file *seq, void *offset)
{
	char tmp;
	if(ec_read(0xb8,&tmp)){
		printk("ec_read fail\n");
		return -EFAULT;
	}
	seq_printf(seq, "%d\n",!(tmp & 0x80) == 0);
	return 0;
}

static int acpi_s11_ec_read_tp(struct seq_file *seq, void *offset)
{
	char tmp;
	if(ec_write(0x00,0x1b)){
		printk("ec_write fail\n");
		return -EFAULT;
	}
	mdelay(10);
	if(ec_read(0x01,&tmp)){
		printk("ec_read_fail\n");
		return -EFAULT;
	}
	seq_printf(seq, "%d\n",tmp);
	return 0;
}

static int acpi_s11_ec_read_antenna(struct seq_file *seq, void *offset)
{
	char tmp;
	if(ec_read(0xbb,&tmp)){
		printk("ec_read_fail\n");
		return -EFAULT;
	}
	seq_printf(seq, "%d\n",!(tmp & 0x10) == 0);
	return 0;
}

static int acpi_s11_ec_read_w3g(struct seq_file *seq, void *offset)
{
        char tmp;
        if(ec_read(0xbb,&tmp)){
                printk("ec_read_fail\n");
                return -EFAULT;
        }
        seq_printf(seq, "%d\n",((tmp >> 6) & 0x1) == 1);
        return 0;
}


static int acpi_s11_ec_read_brightness(struct seq_file *seq, void *offset)
{
	char tmp;
	int i;
	if(ec_read(0xb9,&tmp)){
		printk("ec_read fail\n");
		return -EFAULT;
	}
	seq_printf(seq, "levels: ");
	for(i=0;i<=100;i=i+10)
		seq_printf(seq," %d",i);
	seq_printf(seq, "\ncurrent: %d\n",tmp*10);
	return 0;
}

static int  acpi_s11_ec_write_camera(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
	char tmp;
	if(buffer[0]!='0' && buffer[0]!='1'){ 
		return count;
		}
	
	if(ec_read(0xb8,&tmp)){
		printk("ec_read fail\n");
		return -EFAULT;
	}
	ec_write(0xb8, buffer[0]== '1' ? (0x08 | tmp) : (~0x08 & tmp));
	return count;
}

static int  acpi_s11_ec_write_wifi(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
	char tmp;
	if(buffer[0]!='0' && buffer[0]!='1'){ 
		return count;
		}
	if(ec_read(0xbb,&tmp)){
		printk("ec_read fail\n");
		return -EFAULT;
	}
	ec_write(0xbb, buffer[0]== '1' ? (0x01 | tmp) : (~0x01 & tmp));
	return count;
}

static int  acpi_s11_ec_write_dvme(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
	char tmp;
	if(buffer[0] != '0' && buffer[0] != '1'){ 
		return count;
		}
	
	if(ec_read(0xb8,&tmp)){
		printk("ec_read fail\n");
		return -EFAULT;
	}
	ec_write(0xb8, buffer[0]== '1' ? (0x80 | tmp) : (~0x80 & tmp));
	return count;
}


static int  acpi_s11_ec_write_brightness(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
	unsigned int level = 0;
	char str[5] = { 0 };
	if(copy_from_user(str,buffer,count))
		return -EFAULT;
	str[count] = 0;
	level = simple_strtoul(str, NULL, 0);
	if(level > 100)
		return -EFAULT;
	if(ec_write(0xb9,(level / 10))){
		printk("ec_write fail\n");
		return -EFAULT;
	}
	return count;
}

static int  acpi_s11_ec_write_w3g(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
        char tmp;
        if(buffer[0]!='0' && buffer[0]!='1'){
                return count;
                }

        if(ec_read(0xbb,&tmp)){
                printk("ec_read fail\n");
                return -EFAULT;
        }
        ec_write(0xbb, buffer[0]== '1' ? (0x80 | tmp) : (~0x80 & tmp));
        return count;
}



static int acpi_s11_ec_camera_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_s11_ec_read_camera, PDE(inode)->data);
}

static int acpi_s11_ec_wifi_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_s11_ec_read_wifi, PDE(inode)->data);
}

static int acpi_s11_ec_tp_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_s11_ec_read_tp, PDE(inode)->data);
}

static int acpi_s11_ec_antenna_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_s11_ec_read_antenna, PDE(inode)->data);
}

static int acpi_s11_ec_w3g_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_s11_ec_read_w3g, PDE(inode)->data);
}


static int acpi_s11_ec_dvme_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_s11_ec_read_dvme, PDE(inode)->data);
}

static int acpi_s11_ec_brightness_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_s11_ec_read_brightness, PDE(inode)->data);
}

static struct file_operations acpi_s11_ec_camera_ops = {
	.open = acpi_s11_ec_camera_open_fs,
	.read = seq_read,
	.write = acpi_s11_ec_write_camera,
	.llseek = seq_lseek,
	.release = single_release,
	.owner = THIS_MODULE,
};

static struct file_operations acpi_s11_ec_wifi_ops = {
	.open = acpi_s11_ec_wifi_open_fs,
	.read = seq_read,
	.write = acpi_s11_ec_write_wifi,
	.llseek = seq_lseek,
	.release = single_release,
	.owner = THIS_MODULE,
};

static struct file_operations acpi_s11_ec_tp_ops = {
	.open = acpi_s11_ec_tp_open_fs,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
	.owner = THIS_MODULE,
};

static struct file_operations acpi_s11_ec_antenna_ops = {
	.open = acpi_s11_ec_antenna_open_fs,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
	.owner = THIS_MODULE,
};

static struct file_operations acpi_s11_ec_w3g_ops = {
        .open = acpi_s11_ec_w3g_open_fs,
        .read = seq_read,
   	.write = acpi_s11_ec_write_w3g,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

static struct file_operations acpi_s11_ec_dvme_ops = {
	.open = acpi_s11_ec_dvme_open_fs,
	.read = seq_read,
	.write = acpi_s11_ec_write_dvme,
	.llseek = seq_lseek,
	.release = single_release,
	.owner = THIS_MODULE,
};

static struct file_operations acpi_s11_ec_brightness_ops = {
	.open = acpi_s11_ec_brightness_open_fs,
	.read = seq_read,
	.write = acpi_s11_ec_write_brightness,
	.llseek = seq_lseek,
	.release = single_release,
	.owner = THIS_MODULE,
};


static int acpi_s20_ec_read_antenna(struct seq_file *seq, void *offset)
{
	char tmp;
	if(ec_read(0x52,&tmp)){
		printk("ec_read_fail\n");
		return -EFAULT;
	}
	seq_printf(seq, "%d\n",!(tmp & 0x01) == 0);
	return 0;
}

static int acpi_s20_ec_read_w3g(struct seq_file *seq, void *offset)
{
        evaluate_size_t tmp;
        acpi_evaluate_integer(vpc0_handle, "_CFG", NULL,
                                             &tmp);
        seq_printf(seq, "%d\n",((tmp >> 17) & 0x1) == 1);
        return 0;
}

static int acpi_s20_ec_read_wifi(struct seq_file *seq, void *offset)
{
	char tmp;
	if(ec_read(0x71,&tmp)){
		printk("ec_read fail\n");
		return -EFAULT;
	}
	seq_printf(seq, "%d\n",!(tmp & 0x1) == 0);
	return 0;
}

static int acpi_s20_ec_antenna_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_s20_ec_read_antenna, PDE(inode)->data);
}

static int acpi_s20_ec_w3g_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_s20_ec_read_w3g, PDE(inode)->data);
}

static int acpi_s20_ec_wifi_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_s20_ec_read_wifi, PDE(inode)->data);
}

static struct file_operations acpi_s20_ec_antenna_ops = {
	.open = acpi_s20_ec_antenna_open_fs,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
	.owner = THIS_MODULE,
};

static struct file_operations acpi_s20_ec_w3g_ops = {
        .open = acpi_s20_ec_w3g_open_fs,
        .read = seq_read,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

static struct file_operations acpi_s20_ec_wifi_ops = {
        .open = acpi_s20_ec_wifi_open_fs,
        .read = seq_read,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

static int acpi_cs2_ec_read_w3g(struct seq_file *seq, void *offset)
{
        evaluate_size_t tmp;
        union acpi_object arg0 = { ACPI_TYPE_INTEGER };
        struct acpi_object_list args = { 1, &arg0 };
        arg0.integer.value = 4;
        if (lenovo_cs2_device.GECN != 1){
                seq_printf(seq, "No device\n");
                return 0;
        }
        else{   
                if (down_interruptible(&gecn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_integer(lenovo_cs2_device.handle, "GECN", &args, &tmp);
                up(&gecn_sem);
                seq_printf(seq, "%d\n",(int)tmp);
                return 0;
        }
}


static int acpi_cs2_ec_read_antenna(struct seq_file *seq, void *offset)
{
        evaluate_size_t tmp;
        union acpi_object arg0 = { ACPI_TYPE_INTEGER };
        struct acpi_object_list args = { 1, &arg0 };
        arg0.integer.value = 5;
        if (lenovo_cs2_device.GECN != 1){
                seq_printf(seq, "No device\n");
                return 0;
        }
        else{
                if (down_interruptible(&gecn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_integer(lenovo_cs2_device.handle, "GECN", &args, &tmp);
                up(&gecn_sem);
                seq_printf(seq, "%d\n",(int)tmp);
                return 0;
        }

}

static int acpi_cs2_ec_read_wifi(struct seq_file *seq, void *offset)
{
        evaluate_size_t tmp;
        union acpi_object arg0 = { ACPI_TYPE_INTEGER };
        struct acpi_object_list args = { 1, &arg0 };
        arg0.integer.value = 2;
        if (lenovo_cs2_device.GECN != 1){
                seq_printf(seq, "No device\n");
                return 0;
        }
        else{
                if (down_interruptible(&gecn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_integer(lenovo_cs2_device.handle, "GECN", &args, &tmp);
                up(&gecn_sem);
                seq_printf(seq, "%d\n",(int)tmp);
                return 0;
        }

}

static int acpi_cs2_ec_read_camera(struct seq_file *seq, void *offset)
{
        evaluate_size_t tmp;
        union acpi_object arg0 = { ACPI_TYPE_INTEGER };
        struct acpi_object_list args = { 1, &arg0 };
        arg0.integer.value = 1;
        if (lenovo_cs2_device.GECN != 1){
                seq_printf(seq, "No device\n");
                return 0;
        }
        else{
                if (down_interruptible(&gecn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_integer(lenovo_cs2_device.handle, "GECN", &args, &tmp);
                up(&gecn_sem);
                seq_printf(seq, "%d\n",(int)tmp);
                return 0;
        }

}

static int acpi_cs2_ec_read_bluetooth(struct seq_file *seq, void *offset)
{
        evaluate_size_t tmp;
        union acpi_object arg0 = { ACPI_TYPE_INTEGER };
        struct acpi_object_list args = { 1, &arg0 };
        arg0.integer.value = 3;
        if (lenovo_cs2_device.GECN != 1){
                seq_printf(seq, "No device\n");
                return 0;
        }
        else{
                if (down_interruptible(&gecn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_integer(lenovo_cs2_device.handle, "GECN", &args, &tmp);
                up(&gecn_sem);
                seq_printf(seq, "%d\n",(int)tmp);
                return 0;
        }

}

static int  acpi_cs2_ec_write_camera(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
        union acpi_object in_arg[2];
        struct acpi_object_list args = { 2, in_arg };
        if(buffer[0]!='0' && buffer[0]!='1'){
                return count;
                }
        in_arg[0].type = ACPI_TYPE_INTEGER;
        in_arg[0].integer.value = 1;
        in_arg[1].type = ACPI_TYPE_INTEGER;
        in_arg[1].integer.value = (buffer[0] == '1');
        if (lenovo_cs2_device.SECN == 1){
                if (down_interruptible(&secn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_object(lenovo_cs2_device.handle, "SECN", &args, NULL);
                up(&secn_sem);
        }
        return count;
}

static int  acpi_cs2_ec_write_wifi(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
        union acpi_object in_arg[2];
        struct acpi_object_list args = { 2, in_arg };
        if(buffer[0]!='0' && buffer[0]!='1'){
                return count;
                }
        in_arg[0].type = ACPI_TYPE_INTEGER;
        in_arg[0].integer.value = 2;
        in_arg[1].type = ACPI_TYPE_INTEGER;
        in_arg[1].integer.value = (buffer[0] == '1');
        if (lenovo_cs2_device.SECN == 1){
                if (down_interruptible(&secn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_object(lenovo_cs2_device.handle, "SECN", &args, NULL);
                up(&secn_sem);
        }
        return count;
}

static int  acpi_cs2_ec_write_w3g(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
        union acpi_object in_arg[2];
        struct acpi_object_list args = { 2, in_arg };
        if(buffer[0]!='0' && buffer[0]!='1'){
                return count;
                }
        in_arg[0].type = ACPI_TYPE_INTEGER;
        in_arg[0].integer.value = 4;
        in_arg[1].type = ACPI_TYPE_INTEGER;
        in_arg[1].integer.value = (buffer[0] == '1');
        if (lenovo_cs2_device.SECN == 1){
                if (down_interruptible(&secn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_object(lenovo_cs2_device.handle, "SECN", &args, NULL);
                up(&secn_sem);
        }
        return count;
}

static int  acpi_cs2_ec_write_bluetooth(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
        union acpi_object in_arg[2];
        struct acpi_object_list args = { 2, in_arg };
        if(buffer[0]!='0' && buffer[0]!='1'){
                return count;
                }
        in_arg[0].type = ACPI_TYPE_INTEGER;
        in_arg[0].integer.value = 3;
        in_arg[1].type = ACPI_TYPE_INTEGER;
        in_arg[1].integer.value = (buffer[0] == '1');
        if (lenovo_cs2_device.SECN == 1){
                if (down_interruptible(&secn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_object(lenovo_cs2_device.handle, "SECN", &args, NULL);
                up(&secn_sem);
        }
        return count;
}

static int acpi_cs2_ec_antenna_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_cs2_ec_read_antenna, PDE(inode)->data);
}

static int acpi_cs2_ec_w3g_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_cs2_ec_read_w3g, PDE(inode)->data);
}

static int acpi_cs2_ec_wifi_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_cs2_ec_read_wifi, PDE(inode)->data);
}

static int acpi_cs2_ec_camera_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_cs2_ec_read_camera, PDE(inode)->data);
}

static int acpi_cs2_ec_bluetooth_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_cs2_ec_read_bluetooth, PDE(inode)->data);
}

static struct file_operations acpi_cs2_ec_antenna_ops = {
        .open = acpi_cs2_ec_antenna_open_fs,
        .read = seq_read,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

static struct file_operations acpi_cs2_ec_w3g_ops = {
        .open = acpi_cs2_ec_w3g_open_fs,
        .read = seq_read,
	.write = acpi_cs2_ec_write_w3g,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

static struct file_operations acpi_cs2_ec_wifi_ops = {
        .open = acpi_cs2_ec_wifi_open_fs,
        .read = seq_read,
	.write = acpi_cs2_ec_write_wifi,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

static struct file_operations acpi_cs2_ec_camera_ops = {
        .open = acpi_cs2_ec_camera_open_fs,
        .read = seq_read,
	.write = acpi_cs2_ec_write_camera,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

static struct file_operations acpi_cs2_ec_bluetooth_ops = {
        .open = acpi_cs2_ec_bluetooth_open_fs,
        .read = seq_read,
        .write = acpi_cs2_ec_write_bluetooth,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

static int acpi_m3b_ec_read_antenna(struct seq_file *seq, void *offset)
{
        char tmp;
        if(ec_read(0x52,&tmp)){
                printk("ec_read_fail\n");
                return -EFAULT;
        }
        seq_printf(seq, "%d\n",!(tmp & 0x01) == 0);
        return 0;
}

static int acpi_m3b_ec_antenna_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_m3b_ec_read_antenna, PDE(inode)->data);
}

static struct file_operations acpi_m3b_ec_antenna_ops = {
        .open = acpi_m3b_ec_antenna_open_fs,
        .read = seq_read,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

/*
 * This function is used to regiter the ec device's proc file and operations
 */
static int lenovo_ec_proc_add(char *name,struct file_operations *acpi_ec_ops,struct proc_dir_entry *dir)
{
	struct proc_dir_entry * entry = NULL;
	entry = create_proc_entry(name, S_IRUGO,
				  dir);
	if (!entry)
		return -ENODEV;
	else {
		entry->proc_fops = acpi_ec_ops;
		entry->data = NULL;
		entry->owner = THIS_MODULE;
	}
	return 0;
}

/*
 * The lenovo S11 has six ec devices : camera, wifi, brightness controller, touch pad, dvm enalbe, antenna.
 */

static int lenovo_s11_ec_init(void)
{
	int result;
	printk("Lenovo s11 ec driver init\n");
	result = lenovo_ec_proc_add("camera",&acpi_s11_ec_camera_ops,lenovo_ec_fs_dir);
	if(result == -ENODEV)
		return result;
	result = lenovo_ec_proc_add("wifi", &acpi_s11_ec_wifi_ops,lenovo_ec_fs_dir);
	if(result == -ENODEV)
		return result;
	result = lenovo_ec_proc_add("tp", &acpi_s11_ec_tp_ops,lenovo_ec_fs_dir);
	if(result == -ENODEV)
		return result;
	result = lenovo_ec_proc_add("dvme", &acpi_s11_ec_dvme_ops,lenovo_ec_fs_dir);
	if(result == -ENODEV)
		return result;
	result = lenovo_ec_proc_add("brightness", &acpi_s11_ec_brightness_ops,lenovo_ec_fs_dir);
	if(result == -ENODEV)
		return result;
	result = lenovo_ec_proc_add("antenna", &acpi_s11_ec_antenna_ops,lenovo_ec_fs_dir);
	if(result == -ENODEV)
		return result;
        result = lenovo_ec_proc_add("w3g", &acpi_s11_ec_w3g_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;

	return 0;
}

static void lenovo_s11_ec_exit(void)
{
	printk("Lenovo_s11 ec driver remove\n");
	remove_proc_entry("camera", lenovo_ec_fs_dir);
	remove_proc_entry("wifi", lenovo_ec_fs_dir);
	remove_proc_entry("tp", lenovo_ec_fs_dir);
	remove_proc_entry("dvme", lenovo_ec_fs_dir);
	remove_proc_entry("brightness", lenovo_ec_fs_dir);
	remove_proc_entry("antenna", lenovo_ec_fs_dir);
	remove_proc_entry("w3g", lenovo_ec_fs_dir);
	return;
}

static acpi_status
find_vpc0(acpi_handle handle, u32 lvl, void *context, void **rv)
{

        char prefix[80] = {'\0'};
	int *vpc;
        struct acpi_buffer buffer = {sizeof(prefix), prefix };
	vpc = context;
        acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer);

        if (strcmp(prefix,"VPC0") == 0){
                vpc0_handle = handle;
		*vpc = 1;
                }
        return AE_OK;
}

static int lenovo_s20_vpn_init(void)
{
	int vpc0 = 0;
        acpi_handle h_dummy1;
	acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
			ACPI_UINT32_MAX, find_vpc0, &vpc0, NULL);
	if (vpc0 == 1){
        	if (ACPI_SUCCESS(acpi_get_handle(vpc0_handle, "_CFG", &h_dummy1)))
			return 0;
		}
	return -1;
}


static int lenovo_s20_ec_init(void)
{
	int result;
	printk("Lenovo s20 ec driver init\n");
	result =  lenovo_s20_vpn_init();
        if(result == -1)
                return -ENODEV;
	result = lenovo_ec_proc_add("antenna",&acpi_s20_ec_antenna_ops,lenovo_ec_fs_dir);
	if(result == -ENODEV)
		return result;
        result = lenovo_ec_proc_add("w3g",&acpi_s20_ec_w3g_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("wifi",&acpi_s20_ec_wifi_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
	return 0;
}

static void lenovo_s20_ec_exit(void)
{
	printk("Lenovo_s20 ec driver remove\n");
	remove_proc_entry("antenna", lenovo_ec_fs_dir);
	remove_proc_entry("w3g", lenovo_ec_fs_dir);
	remove_proc_entry("wifi", lenovo_ec_fs_dir);
}

static acpi_status
find_sb(acpi_handle handle, u32 lvl, void *context, void **rv)
{

        char prefix[80] = {'\0'};
        int *sb;
        struct acpi_buffer buffer = {sizeof(prefix), prefix };
        sb = context;
        acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer);
        if (strcmp(prefix,"_SB_") == 0){
                lenovo_cs2_device.handle = handle;
                *sb = 1;
                }
        return AE_OK;
}

static int lenovo_cs2_rootpath_init(void)
{
        int sb = 0;
        acpi_handle h_dummy1;
        acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
                        ACPI_UINT32_MAX, find_sb, &sb, NULL);
        if (sb == 1){
                if (ACPI_SUCCESS(acpi_get_handle(lenovo_cs2_device.handle, "DECN", &h_dummy1)))
                        lenovo_cs2_device.DECN = 1;
                else
                        lenovo_cs2_device.DECN = 0;
                if (ACPI_SUCCESS(acpi_get_handle(lenovo_cs2_device.handle, "SECN", &h_dummy1)))
                        lenovo_cs2_device.SECN = 1;
                else
                        lenovo_cs2_device.SECN = 0;
                if (ACPI_SUCCESS(acpi_get_handle(lenovo_cs2_device.handle, "GECN", &h_dummy1)))
                        lenovo_cs2_device.GECN = 1;
                else
                        lenovo_cs2_device.GECN = 0;
                return 0;
        }
        else{
                lenovo_cs2_device.GECN = 0;
                lenovo_cs2_device.SECN = 0;
                lenovo_cs2_device.DECN = 0;
                return -1;
        }
}

static int lenovo_cs2_ec_init(void)
{
        int result;
        printk("Lenovo cs2 ec driver init\n");
        result =  lenovo_cs2_rootpath_init();
        if(result == -1)
                return -ENODEV;
        result = lenovo_ec_proc_add("antenna",&acpi_cs2_ec_antenna_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("w3g",&acpi_cs2_ec_w3g_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("wifi",&acpi_cs2_ec_wifi_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("camera",&acpi_cs2_ec_camera_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("bluetooth",&acpi_cs2_ec_bluetooth_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        return 0;
}

static void lenovo_cs2_ec_exit(void)
{
        printk("Lenovo_cs2 ec driver remove\n");
        remove_proc_entry("antenna", lenovo_ec_fs_dir);
        remove_proc_entry("w3g", lenovo_ec_fs_dir);
        remove_proc_entry("wifi", lenovo_ec_fs_dir);
        remove_proc_entry("camera", lenovo_ec_fs_dir);
}

static int lenovo_m3b_ec_init(void)
{
        /* Use the acpi method interface same as Lenovo CS2*/
        int result;
        printk("Lenovo mariana-3b ec driver init\n");
        result =  lenovo_cs2_rootpath_init();
        if(result == -1)
                return -ENODEV;
        result = lenovo_ec_proc_add("antenna",&acpi_cs2_ec_antenna_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("w3g",&acpi_cs2_ec_w3g_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("wifi",&acpi_cs2_ec_wifi_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("camera",&acpi_cs2_ec_camera_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("bluetooth",&acpi_cs2_ec_bluetooth_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        return 0;
}

static void lenovo_m3b_ec_exit(void)
{
        printk("Lenovo mariana-3b ec driver remove\n");
        remove_proc_entry("antenna", lenovo_ec_fs_dir);
}

static int lenovo_ec_init(void)
{
	lenovo_ec_fs_dir = proc_mkdir("lenovo",acpi_root_dir);
        if (model != NULL){
	    if (strncmp(model, "S11", 3) == 0)
                lenovo_s11_ec_init();
	    else if (strncmp(model, "S20", 3) == 0)
	        lenovo_s20_ec_init();
	    else if (strncmp(model, "CS2", 3) == 0)
                lenovo_cs2_ec_init();
	    else if (strncmp(model, "M3B", 3) == 0)
                lenovo_m3b_ec_init();
        }
	return 0;
}

static void lenovo_ec_exit(void)
{
        if (model != NULL){
	    if (strncmp(model, "S11", 3) == 0)
		lenovo_s11_ec_exit();
	    else if (strncmp(model, "S20", 3) == 0)
		lenovo_s20_ec_exit();
	    else if (strncmp(model, "CS2", 3) == 0)
	        lenovo_cs2_ec_exit();
	    else if (strncmp(model, "M3B", 3) == 0)
	        lenovo_m3b_ec_exit();
        }
	remove_proc_entry("lenovo", acpi_root_dir);
}

module_init(lenovo_ec_init);
module_exit(lenovo_ec_exit);


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] ideapad-laptop: add new driver
  2010-08-13  9:34     ` David Woodhouse
  2010-08-14  2:56       ` Ike Panhc
  2010-08-15  8:14       ` Florian Echtler
@ 2010-08-15  8:20       ` Florian Echtler
  2 siblings, 0 replies; 14+ messages in thread
From: Florian Echtler @ 2010-08-15  8:20 UTC (permalink / raw)
  To: David Woodhouse
  Cc: Ike Panhc, platform-driver-x86, linux-acpi, Matthew Garrett,
	Len Brown, Randy Dunlap, Corentin Chary, Andrew Morton, Alan Cox,
	Thomas Renninger, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 442 bytes --]

On Fri, 2010-08-13 at 10:34 +0100, David Woodhouse wrote:
> Do you know how to restore it? Or do you have a copy of the 'lenovo-ec'
> module that it apparently contains?
Hello David,

I do have that module: I coaxed it out of Lenovo with the help of
gpl-violations.org as no source was available on my S10-3t. Since it is
flagged as Dual BSD/GPL license, there should be no harm in posting it
here. I hope it helps you in some way.

Florian


[-- Attachment #2: lenovo_ec.c --]
[-- Type: text/x-csrc, Size: 27251 bytes --]

/*
 * lenovo_s11_ec.c Lenovo_S11 ACPI EC Extras
 */

#include <linux/init.h>
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/fcntl.h> /* O_ACCMODE */
#include <asm/system.h> /* cli(), *_flags */
#include <asm/uaccess.h> /* copy_from/to_user */
#include <asm/io.h> /* inb, outb */
#include <linux/sched.h> /* set_cpus_allowed */
#include <linux/acpi.h>
#include <linux/seq_file.h>
#include <acpi/acpi_drivers.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)
    typedef unsigned long long evaluate_size_t;
#else
    typedef unsigned long evaluate_size_t;
#endif

static DECLARE_MUTEX(gecn_sem);
static DECLARE_MUTEX(secn_sem);

static  struct proc_dir_entry *lenovo_ec_fs_dir;
static char *model = NULL;
struct acpi_lenovo_cs2_device {
        acpi_handle handle;
        char DECN;              
        char GECN;            
        char SECN;            
};

struct acpi_lenovo_cs2_device lenovo_cs2_device;
static acpi_handle vpc0_handle;
MODULE_LICENSE("Dual BSD/GPL");
module_param(model, charp, S_IRUGO);
/*
 *  Proc handle for reading the status of ec device.
 */

static int acpi_s11_ec_read_camera(struct seq_file *seq, void *offset)
{
	char tmp;
	if(ec_read(0xb8,&tmp)){
		printk("ec_read fail\n");
		return -EFAULT;
	}
	seq_printf(seq, "%d\n",!(tmp & 0x8) == 0);
	return 0;
}

static int acpi_s11_ec_read_wifi(struct seq_file *seq, void *offset)
{
	char tmp;
	if(ec_read(0xbb,&tmp)){
		printk("ec_read fail\n");
		return -EFAULT;
	}
	seq_printf(seq, "%d\n",!(tmp & 0x1) == 0);
	return 0;
}

static int acpi_s11_ec_read_dvme(struct seq_file *seq, void *offset)
{
	char tmp;
	if(ec_read(0xb8,&tmp)){
		printk("ec_read fail\n");
		return -EFAULT;
	}
	seq_printf(seq, "%d\n",!(tmp & 0x80) == 0);
	return 0;
}

static int acpi_s11_ec_read_tp(struct seq_file *seq, void *offset)
{
	char tmp;
	if(ec_write(0x00,0x1b)){
		printk("ec_write fail\n");
		return -EFAULT;
	}
	mdelay(10);
	if(ec_read(0x01,&tmp)){
		printk("ec_read_fail\n");
		return -EFAULT;
	}
	seq_printf(seq, "%d\n",tmp);
	return 0;
}

static int acpi_s11_ec_read_antenna(struct seq_file *seq, void *offset)
{
	char tmp;
	if(ec_read(0xbb,&tmp)){
		printk("ec_read_fail\n");
		return -EFAULT;
	}
	seq_printf(seq, "%d\n",!(tmp & 0x10) == 0);
	return 0;
}

static int acpi_s11_ec_read_w3g(struct seq_file *seq, void *offset)
{
        char tmp;
        if(ec_read(0xbb,&tmp)){
                printk("ec_read_fail\n");
                return -EFAULT;
        }
        seq_printf(seq, "%d\n",((tmp >> 6) & 0x1) == 1);
        return 0;
}


static int acpi_s11_ec_read_brightness(struct seq_file *seq, void *offset)
{
	char tmp;
	int i;
	if(ec_read(0xb9,&tmp)){
		printk("ec_read fail\n");
		return -EFAULT;
	}
	seq_printf(seq, "levels: ");
	for(i=0;i<=100;i=i+10)
		seq_printf(seq," %d",i);
	seq_printf(seq, "\ncurrent: %d\n",tmp*10);
	return 0;
}

static int  acpi_s11_ec_write_camera(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
	char tmp;
	if(buffer[0]!='0' && buffer[0]!='1'){ 
		return count;
		}
	
	if(ec_read(0xb8,&tmp)){
		printk("ec_read fail\n");
		return -EFAULT;
	}
	ec_write(0xb8, buffer[0]== '1' ? (0x08 | tmp) : (~0x08 & tmp));
	return count;
}

static int  acpi_s11_ec_write_wifi(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
	char tmp;
	if(buffer[0]!='0' && buffer[0]!='1'){ 
		return count;
		}
	if(ec_read(0xbb,&tmp)){
		printk("ec_read fail\n");
		return -EFAULT;
	}
	ec_write(0xbb, buffer[0]== '1' ? (0x01 | tmp) : (~0x01 & tmp));
	return count;
}

static int  acpi_s11_ec_write_dvme(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
	char tmp;
	if(buffer[0] != '0' && buffer[0] != '1'){ 
		return count;
		}
	
	if(ec_read(0xb8,&tmp)){
		printk("ec_read fail\n");
		return -EFAULT;
	}
	ec_write(0xb8, buffer[0]== '1' ? (0x80 | tmp) : (~0x80 & tmp));
	return count;
}


static int  acpi_s11_ec_write_brightness(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
	unsigned int level = 0;
	char str[5] = { 0 };
	if(copy_from_user(str,buffer,count))
		return -EFAULT;
	str[count] = 0;
	level = simple_strtoul(str, NULL, 0);
	if(level > 100)
		return -EFAULT;
	if(ec_write(0xb9,(level / 10))){
		printk("ec_write fail\n");
		return -EFAULT;
	}
	return count;
}

static int  acpi_s11_ec_write_w3g(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
        char tmp;
        if(buffer[0]!='0' && buffer[0]!='1'){
                return count;
                }

        if(ec_read(0xbb,&tmp)){
                printk("ec_read fail\n");
                return -EFAULT;
        }
        ec_write(0xbb, buffer[0]== '1' ? (0x80 | tmp) : (~0x80 & tmp));
        return count;
}



static int acpi_s11_ec_camera_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_s11_ec_read_camera, PDE(inode)->data);
}

static int acpi_s11_ec_wifi_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_s11_ec_read_wifi, PDE(inode)->data);
}

static int acpi_s11_ec_tp_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_s11_ec_read_tp, PDE(inode)->data);
}

static int acpi_s11_ec_antenna_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_s11_ec_read_antenna, PDE(inode)->data);
}

static int acpi_s11_ec_w3g_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_s11_ec_read_w3g, PDE(inode)->data);
}


static int acpi_s11_ec_dvme_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_s11_ec_read_dvme, PDE(inode)->data);
}

static int acpi_s11_ec_brightness_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_s11_ec_read_brightness, PDE(inode)->data);
}

static struct file_operations acpi_s11_ec_camera_ops = {
	.open = acpi_s11_ec_camera_open_fs,
	.read = seq_read,
	.write = acpi_s11_ec_write_camera,
	.llseek = seq_lseek,
	.release = single_release,
	.owner = THIS_MODULE,
};

static struct file_operations acpi_s11_ec_wifi_ops = {
	.open = acpi_s11_ec_wifi_open_fs,
	.read = seq_read,
	.write = acpi_s11_ec_write_wifi,
	.llseek = seq_lseek,
	.release = single_release,
	.owner = THIS_MODULE,
};

static struct file_operations acpi_s11_ec_tp_ops = {
	.open = acpi_s11_ec_tp_open_fs,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
	.owner = THIS_MODULE,
};

static struct file_operations acpi_s11_ec_antenna_ops = {
	.open = acpi_s11_ec_antenna_open_fs,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
	.owner = THIS_MODULE,
};

static struct file_operations acpi_s11_ec_w3g_ops = {
        .open = acpi_s11_ec_w3g_open_fs,
        .read = seq_read,
   	.write = acpi_s11_ec_write_w3g,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

static struct file_operations acpi_s11_ec_dvme_ops = {
	.open = acpi_s11_ec_dvme_open_fs,
	.read = seq_read,
	.write = acpi_s11_ec_write_dvme,
	.llseek = seq_lseek,
	.release = single_release,
	.owner = THIS_MODULE,
};

static struct file_operations acpi_s11_ec_brightness_ops = {
	.open = acpi_s11_ec_brightness_open_fs,
	.read = seq_read,
	.write = acpi_s11_ec_write_brightness,
	.llseek = seq_lseek,
	.release = single_release,
	.owner = THIS_MODULE,
};


static int acpi_s20_ec_read_antenna(struct seq_file *seq, void *offset)
{
	char tmp;
	if(ec_read(0x52,&tmp)){
		printk("ec_read_fail\n");
		return -EFAULT;
	}
	seq_printf(seq, "%d\n",!(tmp & 0x01) == 0);
	return 0;
}

static int acpi_s20_ec_read_w3g(struct seq_file *seq, void *offset)
{
        evaluate_size_t tmp;
        acpi_evaluate_integer(vpc0_handle, "_CFG", NULL,
                                             &tmp);
        seq_printf(seq, "%d\n",((tmp >> 17) & 0x1) == 1);
        return 0;
}

static int acpi_s20_ec_read_wifi(struct seq_file *seq, void *offset)
{
	char tmp;
	if(ec_read(0x71,&tmp)){
		printk("ec_read fail\n");
		return -EFAULT;
	}
	seq_printf(seq, "%d\n",!(tmp & 0x1) == 0);
	return 0;
}

static int acpi_s20_ec_antenna_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_s20_ec_read_antenna, PDE(inode)->data);
}

static int acpi_s20_ec_w3g_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_s20_ec_read_w3g, PDE(inode)->data);
}

static int acpi_s20_ec_wifi_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_s20_ec_read_wifi, PDE(inode)->data);
}

static struct file_operations acpi_s20_ec_antenna_ops = {
	.open = acpi_s20_ec_antenna_open_fs,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
	.owner = THIS_MODULE,
};

static struct file_operations acpi_s20_ec_w3g_ops = {
        .open = acpi_s20_ec_w3g_open_fs,
        .read = seq_read,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

static struct file_operations acpi_s20_ec_wifi_ops = {
        .open = acpi_s20_ec_wifi_open_fs,
        .read = seq_read,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

static int acpi_cs2_ec_read_w3g(struct seq_file *seq, void *offset)
{
        evaluate_size_t tmp;
        union acpi_object arg0 = { ACPI_TYPE_INTEGER };
        struct acpi_object_list args = { 1, &arg0 };
        arg0.integer.value = 4;
        if (lenovo_cs2_device.GECN != 1){
                seq_printf(seq, "No device\n");
                return 0;
        }
        else{   
                if (down_interruptible(&gecn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_integer(lenovo_cs2_device.handle, "GECN", &args, &tmp);
                up(&gecn_sem);
                seq_printf(seq, "%d\n",(int)tmp);
                return 0;
        }
}


static int acpi_cs2_ec_read_antenna(struct seq_file *seq, void *offset)
{
        evaluate_size_t tmp;
        union acpi_object arg0 = { ACPI_TYPE_INTEGER };
        struct acpi_object_list args = { 1, &arg0 };
        arg0.integer.value = 5;
        if (lenovo_cs2_device.GECN != 1){
                seq_printf(seq, "No device\n");
                return 0;
        }
        else{
                if (down_interruptible(&gecn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_integer(lenovo_cs2_device.handle, "GECN", &args, &tmp);
                up(&gecn_sem);
                seq_printf(seq, "%d\n",(int)tmp);
                return 0;
        }

}

static int acpi_cs2_ec_read_wifi(struct seq_file *seq, void *offset)
{
        evaluate_size_t tmp;
        union acpi_object arg0 = { ACPI_TYPE_INTEGER };
        struct acpi_object_list args = { 1, &arg0 };
        arg0.integer.value = 2;
        if (lenovo_cs2_device.GECN != 1){
                seq_printf(seq, "No device\n");
                return 0;
        }
        else{
                if (down_interruptible(&gecn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_integer(lenovo_cs2_device.handle, "GECN", &args, &tmp);
                up(&gecn_sem);
                seq_printf(seq, "%d\n",(int)tmp);
                return 0;
        }

}

static int acpi_cs2_ec_read_camera(struct seq_file *seq, void *offset)
{
        evaluate_size_t tmp;
        union acpi_object arg0 = { ACPI_TYPE_INTEGER };
        struct acpi_object_list args = { 1, &arg0 };
        arg0.integer.value = 1;
        if (lenovo_cs2_device.GECN != 1){
                seq_printf(seq, "No device\n");
                return 0;
        }
        else{
                if (down_interruptible(&gecn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_integer(lenovo_cs2_device.handle, "GECN", &args, &tmp);
                up(&gecn_sem);
                seq_printf(seq, "%d\n",(int)tmp);
                return 0;
        }

}

static int acpi_cs2_ec_read_bluetooth(struct seq_file *seq, void *offset)
{
        evaluate_size_t tmp;
        union acpi_object arg0 = { ACPI_TYPE_INTEGER };
        struct acpi_object_list args = { 1, &arg0 };
        arg0.integer.value = 3;
        if (lenovo_cs2_device.GECN != 1){
                seq_printf(seq, "No device\n");
                return 0;
        }
        else{
                if (down_interruptible(&gecn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_integer(lenovo_cs2_device.handle, "GECN", &args, &tmp);
                up(&gecn_sem);
                seq_printf(seq, "%d\n",(int)tmp);
                return 0;
        }

}

static int  acpi_cs2_ec_write_camera(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
        union acpi_object in_arg[2];
        struct acpi_object_list args = { 2, in_arg };
        if(buffer[0]!='0' && buffer[0]!='1'){
                return count;
                }
        in_arg[0].type = ACPI_TYPE_INTEGER;
        in_arg[0].integer.value = 1;
        in_arg[1].type = ACPI_TYPE_INTEGER;
        in_arg[1].integer.value = (buffer[0] == '1');
        if (lenovo_cs2_device.SECN == 1){
                if (down_interruptible(&secn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_object(lenovo_cs2_device.handle, "SECN", &args, NULL);
                up(&secn_sem);
        }
        return count;
}

static int  acpi_cs2_ec_write_wifi(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
        union acpi_object in_arg[2];
        struct acpi_object_list args = { 2, in_arg };
        if(buffer[0]!='0' && buffer[0]!='1'){
                return count;
                }
        in_arg[0].type = ACPI_TYPE_INTEGER;
        in_arg[0].integer.value = 2;
        in_arg[1].type = ACPI_TYPE_INTEGER;
        in_arg[1].integer.value = (buffer[0] == '1');
        if (lenovo_cs2_device.SECN == 1){
                if (down_interruptible(&secn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_object(lenovo_cs2_device.handle, "SECN", &args, NULL);
                up(&secn_sem);
        }
        return count;
}

static int  acpi_cs2_ec_write_w3g(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
        union acpi_object in_arg[2];
        struct acpi_object_list args = { 2, in_arg };
        if(buffer[0]!='0' && buffer[0]!='1'){
                return count;
                }
        in_arg[0].type = ACPI_TYPE_INTEGER;
        in_arg[0].integer.value = 4;
        in_arg[1].type = ACPI_TYPE_INTEGER;
        in_arg[1].integer.value = (buffer[0] == '1');
        if (lenovo_cs2_device.SECN == 1){
                if (down_interruptible(&secn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_object(lenovo_cs2_device.handle, "SECN", &args, NULL);
                up(&secn_sem);
        }
        return count;
}

static int  acpi_cs2_ec_write_bluetooth(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
        union acpi_object in_arg[2];
        struct acpi_object_list args = { 2, in_arg };
        if(buffer[0]!='0' && buffer[0]!='1'){
                return count;
                }
        in_arg[0].type = ACPI_TYPE_INTEGER;
        in_arg[0].integer.value = 3;
        in_arg[1].type = ACPI_TYPE_INTEGER;
        in_arg[1].integer.value = (buffer[0] == '1');
        if (lenovo_cs2_device.SECN == 1){
                if (down_interruptible(&secn_sem)){
                        return -ERESTARTSYS;
                }
                acpi_evaluate_object(lenovo_cs2_device.handle, "SECN", &args, NULL);
                up(&secn_sem);
        }
        return count;
}

static int acpi_cs2_ec_antenna_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_cs2_ec_read_antenna, PDE(inode)->data);
}

static int acpi_cs2_ec_w3g_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_cs2_ec_read_w3g, PDE(inode)->data);
}

static int acpi_cs2_ec_wifi_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_cs2_ec_read_wifi, PDE(inode)->data);
}

static int acpi_cs2_ec_camera_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_cs2_ec_read_camera, PDE(inode)->data);
}

static int acpi_cs2_ec_bluetooth_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_cs2_ec_read_bluetooth, PDE(inode)->data);
}

static struct file_operations acpi_cs2_ec_antenna_ops = {
        .open = acpi_cs2_ec_antenna_open_fs,
        .read = seq_read,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

static struct file_operations acpi_cs2_ec_w3g_ops = {
        .open = acpi_cs2_ec_w3g_open_fs,
        .read = seq_read,
	.write = acpi_cs2_ec_write_w3g,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

static struct file_operations acpi_cs2_ec_wifi_ops = {
        .open = acpi_cs2_ec_wifi_open_fs,
        .read = seq_read,
	.write = acpi_cs2_ec_write_wifi,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

static struct file_operations acpi_cs2_ec_camera_ops = {
        .open = acpi_cs2_ec_camera_open_fs,
        .read = seq_read,
	.write = acpi_cs2_ec_write_camera,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

static struct file_operations acpi_cs2_ec_bluetooth_ops = {
        .open = acpi_cs2_ec_bluetooth_open_fs,
        .read = seq_read,
        .write = acpi_cs2_ec_write_bluetooth,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

static int acpi_m3b_ec_read_antenna(struct seq_file *seq, void *offset)
{
        char tmp;
        if(ec_read(0x52,&tmp)){
                printk("ec_read_fail\n");
                return -EFAULT;
        }
        seq_printf(seq, "%d\n",!(tmp & 0x01) == 0);
        return 0;
}

static int acpi_m3b_ec_antenna_open_fs(struct inode *inode, struct file *file)
{
        return single_open(file, acpi_m3b_ec_read_antenna, PDE(inode)->data);
}

static struct file_operations acpi_m3b_ec_antenna_ops = {
        .open = acpi_m3b_ec_antenna_open_fs,
        .read = seq_read,
        .llseek = seq_lseek,
        .release = single_release,
        .owner = THIS_MODULE,
};

/*
 * This function is used to regiter the ec device's proc file and operations
 */
static int lenovo_ec_proc_add(char *name,struct file_operations *acpi_ec_ops,struct proc_dir_entry *dir)
{
	struct proc_dir_entry * entry = NULL;
	entry = create_proc_entry(name, S_IRUGO,
				  dir);
	if (!entry)
		return -ENODEV;
	else {
		entry->proc_fops = acpi_ec_ops;
		entry->data = NULL;
		entry->owner = THIS_MODULE;
	}
	return 0;
}

/*
 * The lenovo S11 has six ec devices : camera, wifi, brightness controller, touch pad, dvm enalbe, antenna.
 */

static int lenovo_s11_ec_init(void)
{
	int result;
	printk("Lenovo s11 ec driver init\n");
	result = lenovo_ec_proc_add("camera",&acpi_s11_ec_camera_ops,lenovo_ec_fs_dir);
	if(result == -ENODEV)
		return result;
	result = lenovo_ec_proc_add("wifi", &acpi_s11_ec_wifi_ops,lenovo_ec_fs_dir);
	if(result == -ENODEV)
		return result;
	result = lenovo_ec_proc_add("tp", &acpi_s11_ec_tp_ops,lenovo_ec_fs_dir);
	if(result == -ENODEV)
		return result;
	result = lenovo_ec_proc_add("dvme", &acpi_s11_ec_dvme_ops,lenovo_ec_fs_dir);
	if(result == -ENODEV)
		return result;
	result = lenovo_ec_proc_add("brightness", &acpi_s11_ec_brightness_ops,lenovo_ec_fs_dir);
	if(result == -ENODEV)
		return result;
	result = lenovo_ec_proc_add("antenna", &acpi_s11_ec_antenna_ops,lenovo_ec_fs_dir);
	if(result == -ENODEV)
		return result;
        result = lenovo_ec_proc_add("w3g", &acpi_s11_ec_w3g_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;

	return 0;
}

static void lenovo_s11_ec_exit(void)
{
	printk("Lenovo_s11 ec driver remove\n");
	remove_proc_entry("camera", lenovo_ec_fs_dir);
	remove_proc_entry("wifi", lenovo_ec_fs_dir);
	remove_proc_entry("tp", lenovo_ec_fs_dir);
	remove_proc_entry("dvme", lenovo_ec_fs_dir);
	remove_proc_entry("brightness", lenovo_ec_fs_dir);
	remove_proc_entry("antenna", lenovo_ec_fs_dir);
	remove_proc_entry("w3g", lenovo_ec_fs_dir);
	return;
}

static acpi_status
find_vpc0(acpi_handle handle, u32 lvl, void *context, void **rv)
{

        char prefix[80] = {'\0'};
	int *vpc;
        struct acpi_buffer buffer = {sizeof(prefix), prefix };
	vpc = context;
        acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer);

        if (strcmp(prefix,"VPC0") == 0){
                vpc0_handle = handle;
		*vpc = 1;
                }
        return AE_OK;
}

static int lenovo_s20_vpn_init(void)
{
	int vpc0 = 0;
        acpi_handle h_dummy1;
	acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
			ACPI_UINT32_MAX, find_vpc0, &vpc0, NULL);
	if (vpc0 == 1){
        	if (ACPI_SUCCESS(acpi_get_handle(vpc0_handle, "_CFG", &h_dummy1)))
			return 0;
		}
	return -1;
}


static int lenovo_s20_ec_init(void)
{
	int result;
	printk("Lenovo s20 ec driver init\n");
	result =  lenovo_s20_vpn_init();
        if(result == -1)
                return -ENODEV;
	result = lenovo_ec_proc_add("antenna",&acpi_s20_ec_antenna_ops,lenovo_ec_fs_dir);
	if(result == -ENODEV)
		return result;
        result = lenovo_ec_proc_add("w3g",&acpi_s20_ec_w3g_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("wifi",&acpi_s20_ec_wifi_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
	return 0;
}

static void lenovo_s20_ec_exit(void)
{
	printk("Lenovo_s20 ec driver remove\n");
	remove_proc_entry("antenna", lenovo_ec_fs_dir);
	remove_proc_entry("w3g", lenovo_ec_fs_dir);
	remove_proc_entry("wifi", lenovo_ec_fs_dir);
}

static acpi_status
find_sb(acpi_handle handle, u32 lvl, void *context, void **rv)
{

        char prefix[80] = {'\0'};
        int *sb;
        struct acpi_buffer buffer = {sizeof(prefix), prefix };
        sb = context;
        acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer);
        if (strcmp(prefix,"_SB_") == 0){
                lenovo_cs2_device.handle = handle;
                *sb = 1;
                }
        return AE_OK;
}

static int lenovo_cs2_rootpath_init(void)
{
        int sb = 0;
        acpi_handle h_dummy1;
        acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
                        ACPI_UINT32_MAX, find_sb, &sb, NULL);
        if (sb == 1){
                if (ACPI_SUCCESS(acpi_get_handle(lenovo_cs2_device.handle, "DECN", &h_dummy1)))
                        lenovo_cs2_device.DECN = 1;
                else
                        lenovo_cs2_device.DECN = 0;
                if (ACPI_SUCCESS(acpi_get_handle(lenovo_cs2_device.handle, "SECN", &h_dummy1)))
                        lenovo_cs2_device.SECN = 1;
                else
                        lenovo_cs2_device.SECN = 0;
                if (ACPI_SUCCESS(acpi_get_handle(lenovo_cs2_device.handle, "GECN", &h_dummy1)))
                        lenovo_cs2_device.GECN = 1;
                else
                        lenovo_cs2_device.GECN = 0;
                return 0;
        }
        else{
                lenovo_cs2_device.GECN = 0;
                lenovo_cs2_device.SECN = 0;
                lenovo_cs2_device.DECN = 0;
                return -1;
        }
}

static int lenovo_cs2_ec_init(void)
{
        int result;
        printk("Lenovo cs2 ec driver init\n");
        result =  lenovo_cs2_rootpath_init();
        if(result == -1)
                return -ENODEV;
        result = lenovo_ec_proc_add("antenna",&acpi_cs2_ec_antenna_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("w3g",&acpi_cs2_ec_w3g_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("wifi",&acpi_cs2_ec_wifi_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("camera",&acpi_cs2_ec_camera_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("bluetooth",&acpi_cs2_ec_bluetooth_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        return 0;
}

static void lenovo_cs2_ec_exit(void)
{
        printk("Lenovo_cs2 ec driver remove\n");
        remove_proc_entry("antenna", lenovo_ec_fs_dir);
        remove_proc_entry("w3g", lenovo_ec_fs_dir);
        remove_proc_entry("wifi", lenovo_ec_fs_dir);
        remove_proc_entry("camera", lenovo_ec_fs_dir);
}

static int lenovo_m3b_ec_init(void)
{
        /* Use the acpi method interface same as Lenovo CS2*/
        int result;
        printk("Lenovo mariana-3b ec driver init\n");
        result =  lenovo_cs2_rootpath_init();
        if(result == -1)
                return -ENODEV;
        result = lenovo_ec_proc_add("antenna",&acpi_cs2_ec_antenna_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("w3g",&acpi_cs2_ec_w3g_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("wifi",&acpi_cs2_ec_wifi_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("camera",&acpi_cs2_ec_camera_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        result = lenovo_ec_proc_add("bluetooth",&acpi_cs2_ec_bluetooth_ops,lenovo_ec_fs_dir);
        if(result == -ENODEV)
                return result;
        return 0;
}

static void lenovo_m3b_ec_exit(void)
{
        printk("Lenovo mariana-3b ec driver remove\n");
        remove_proc_entry("antenna", lenovo_ec_fs_dir);
}

static int lenovo_ec_init(void)
{
	lenovo_ec_fs_dir = proc_mkdir("lenovo",acpi_root_dir);
        if (model != NULL){
	    if (strncmp(model, "S11", 3) == 0)
                lenovo_s11_ec_init();
	    else if (strncmp(model, "S20", 3) == 0)
	        lenovo_s20_ec_init();
	    else if (strncmp(model, "CS2", 3) == 0)
                lenovo_cs2_ec_init();
	    else if (strncmp(model, "M3B", 3) == 0)
                lenovo_m3b_ec_init();
        }
	return 0;
}

static void lenovo_ec_exit(void)
{
        if (model != NULL){
	    if (strncmp(model, "S11", 3) == 0)
		lenovo_s11_ec_exit();
	    else if (strncmp(model, "S20", 3) == 0)
		lenovo_s20_ec_exit();
	    else if (strncmp(model, "CS2", 3) == 0)
	        lenovo_cs2_ec_exit();
	    else if (strncmp(model, "M3B", 3) == 0)
	        lenovo_m3b_ec_exit();
        }
	remove_proc_entry("lenovo", acpi_root_dir);
}

module_init(lenovo_ec_init);
module_exit(lenovo_ec_exit);


^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2010-08-15 17:13 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-08-13  4:08 [PATCH] ideapad-laptop: add new driver Ike Panhc
2010-08-13  6:37 ` Corentin Chary
2010-08-13  6:53   ` Ike Panhc
2010-08-13  7:01     ` Corentin Chary
2010-08-13  7:15       ` Ike Panhc
2010-08-13  9:22     ` David Woodhouse
2010-08-13  9:14 ` David Woodhouse
2010-08-13  9:27   ` Ike Panhc
2010-08-13  9:34     ` David Woodhouse
2010-08-14  2:56       ` Ike Panhc
2010-08-14  9:49         ` David Woodhouse
2010-08-15  2:33         ` Len Brown
2010-08-15  8:14       ` Florian Echtler
2010-08-15  8:20       ` Florian Echtler

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).