(drop after 2.6.38) acer-wmi: Add support for Aspire 1830 wlan hotkey
[linux-flexiantxendom0-natty.git] / drivers / platform / x86 / acer-wmi.c
index 60fbef2..7451b66 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  Acer WMI Laptop Extras
  *
- *  Copyright (C) 2007-2008    Carlos Corbacho <carlos@strangeworlds.co.uk>
+ *  Copyright (C) 2007-2009    Carlos Corbacho <carlos@strangeworlds.co.uk>
  *
  *  Based on acer_acpi:
  *    Copyright (C) 2005-2007  E.M. Smith
 #include <linux/rfkill.h>
 #include <linux/workqueue.h>
 #include <linux/debugfs.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/dmi.h>
 
 #include <acpi/acpi_drivers.h>
 
@@ -47,17 +51,7 @@ MODULE_LICENSE("GPL");
 #define ACER_ERR KERN_ERR ACER_LOGPREFIX
 #define ACER_NOTICE KERN_NOTICE ACER_LOGPREFIX
 #define ACER_INFO KERN_INFO ACER_LOGPREFIX
-
-/*
- * The following defines quirks to get some specific functions to work
- * which are known to not be supported over ACPI-WMI (such as the mail LED
- * on WMID based Acer's)
- */
-struct acer_quirks {
-       const char *vendor;
-       const char *model;
-       u16 quirks;
-};
+#define ACER_WARNING KERN_WARNING ACER_LOGPREFIX
 
 /*
  * Magic Number
@@ -90,14 +84,85 @@ struct acer_quirks {
  */
 #define AMW0_GUID1             "67C3371D-95A3-4C37-BB61-DD47B491DAAB"
 #define AMW0_GUID2             "431F16ED-0C2B-444C-B267-27DEB140CF9C"
-#define WMID_GUID1             "6AF4F258-B401-42fd-BE91-3D4AC2D7C0D3"
+#define WMID_GUID1             "6AF4F258-B401-42FD-BE91-3D4AC2D7C0D3"
 #define WMID_GUID2             "95764E09-FB56-4e83-B31A-37761F60994A"
+#define WMID_GUID3             "61EF69EA-865C-4BC3-A502-A0DEBA0CB531"
+
+/*
+ * Acer ACPI event GUIDs
+ */
+#define ACERWMID_EVENT_GUID "676AA15E-6A47-4D9F-A2CC-1E6D18D14026"
 
 MODULE_ALIAS("wmi:67C3371D-95A3-4C37-BB61-DD47B491DAAB");
 MODULE_ALIAS("wmi:6AF4F258-B401-42fd-BE91-3D4AC2D7C0D3");
+MODULE_ALIAS("wmi:676AA15E-6A47-4D9F-A2CC-1E6D18D14026");
+
+enum acer_wmi_event_ids {
+       WMID_HOTKEY_EVENT = 0x1,
+};
+
+static const struct key_entry acer_wmi_keymap[] = {
+       {KE_KEY, 0x01, {KEY_WLAN} },     /* WiFi */
+       {KE_KEY, 0x04, {KEY_WLAN} },     /* WiFi */
+       {KE_KEY, 0x12, {KEY_BLUETOOTH} },       /* BT */
+       {KE_KEY, 0x21, {KEY_PROG1} },    /* Backup */
+       {KE_KEY, 0x22, {KEY_PROG2} },    /* Arcade */
+       {KE_KEY, 0x23, {KEY_PROG3} },    /* P_Key */
+       {KE_KEY, 0x24, {KEY_PROG4} },    /* Social networking_Key */
+       {KE_KEY, 0x64, {KEY_SWITCHVIDEOMODE} }, /* Display Switch */
+       {KE_KEY, 0x82, {KEY_F22} },      /* Touch Pad On/Off */
+       {KE_END, 0}
+};
+
+static struct input_dev *acer_wmi_input_dev;
+
+struct event_return_value {
+       u8 function;
+       u8 key_num;
+       u16 device_state;
+       u32 reserved;
+} __attribute__((packed));
 
-/* Temporary workaround until the WMI sysfs interface goes in */
-MODULE_ALIAS("dmi:*:*Acer*:*:");
+/*
+ * GUID3 Get Device Status device flags
+ */
+#define ACER_WMID3_GDS_WIRELESS                (1<<0)  /* WiFi */
+#define ACER_WMID3_GDS_THREEG          (1<<6)  /* 3G */
+#define ACER_WMID3_GDS_BLUETOOTH       (1<<11) /* BT */
+
+struct lm_input_params {
+       u8 function_num;        /* Function Number */
+       u16 commun_devices;     /* Communication type devices default status */
+       u16 devices;            /* Other type devices default status */
+       u8 lm_status;           /* Launch Manager Status */
+       u16 reserved;
+} __attribute__((packed));
+
+struct lm_return_value {
+       u8 error_code;          /* Error Code */
+       u8 ec_return_value;     /* EC Return Value */
+       u16 reserved;
+} __attribute__((packed));
+
+struct wmid3_gds_input_param { /* Get Device Status input parameter */
+       u8 function_num;        /* Function Number */
+       u8 hotkey_number;       /* Hotkey Number */
+       u16 devices;            /* Get Device */
+} __attribute__((packed));
+
+struct wmid3_gds_return_value {        /* Get Device Status return value*/
+       u8 error_code;          /* Error Code */
+       u8 ec_return_value;     /* EC Return Value */
+       u16 devices;            /* Current Device Status */
+       u32 reserved;
+} __attribute__((packed));
+
+struct hotkey_function_type_aa {
+       u8 type;
+       u8 length;
+       u16 handle;
+       u16 commun_func_bitmap;
+} __attribute__((packed));
 
 /*
  * Interface capability flags
@@ -129,15 +194,19 @@ static int mailled = -1;
 static int brightness = -1;
 static int threeg = -1;
 static int force_series;
+static bool ec_raw_mode;
+static bool has_type_aa;
 
 module_param(mailled, int, 0444);
 module_param(brightness, int, 0444);
 module_param(threeg, int, 0444);
 module_param(force_series, int, 0444);
+module_param(ec_raw_mode, bool, 0444);
 MODULE_PARM_DESC(mailled, "Set initial state of Mail LED");
 MODULE_PARM_DESC(brightness, "Set initial LCD backlight brightness");
 MODULE_PARM_DESC(threeg, "Set initial state of 3G hardware");
 MODULE_PARM_DESC(force_series, "Force a different laptop series");
+MODULE_PARM_DESC(ec_raw_mode, "Enable EC raw mode");
 
 struct acer_data {
        int mailled;
@@ -153,6 +222,8 @@ struct acer_debug {
 
 static struct rfkill *wireless_rfkill;
 static struct rfkill *bluetooth_rfkill;
+static struct rfkill *threeg_rfkill;
+static bool rfkill_inited;
 
 /* Each low-level interface must define at least some of the following */
 struct wmi_interface {
@@ -202,7 +273,7 @@ static void set_quirks(void)
 static int dmi_matched(const struct dmi_system_id *dmi)
 {
        quirks = dmi->driver_data;
-       return 0;
+       return 1;
 }
 
 static struct quirk_entry quirk_unknown = {
@@ -557,6 +628,7 @@ static acpi_status AMW0_find_mailled(void)
        obj->buffer.length == sizeof(struct wmab_ret)) {
                ret = *((struct wmab_ret *) obj->buffer.pointer);
        } else {
+               kfree(out.pointer);
                return AE_ERROR;
        }
 
@@ -572,7 +644,7 @@ static acpi_status AMW0_set_capabilities(void)
 {
        struct wmab_args args;
        struct wmab_ret ret;
-       acpi_status status = AE_OK;
+       acpi_status status;
        struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
        union acpi_object *obj;
 
@@ -595,12 +667,13 @@ static acpi_status AMW0_set_capabilities(void)
        if (ACPI_FAILURE(status))
                return status;
 
-       obj = (union acpi_object *) out.pointer;
+       obj = out.pointer;
        if (obj && obj->type == ACPI_TYPE_BUFFER &&
        obj->buffer.length == sizeof(struct wmab_ret)) {
                ret = *((struct wmab_ret *) obj->buffer.pointer);
        } else {
-               return AE_ERROR;
+               status = AE_ERROR;
+               goto out;
        }
 
        if (ret.eax & 0x1)
@@ -609,23 +682,26 @@ static acpi_status AMW0_set_capabilities(void)
        args.ebx = 2 << 8;
        args.ebx |= ACER_AMW0_BLUETOOTH_MASK;
 
+       /*
+        * It's ok to use existing buffer for next wmab_execute call.
+        * But we need to kfree(out.pointer) if next wmab_execute fail.
+        */
        status = wmab_execute(&args, &out);
        if (ACPI_FAILURE(status))
-               return status;
+               goto out;
 
        obj = (union acpi_object *) out.pointer;
        if (obj && obj->type == ACPI_TYPE_BUFFER
        && obj->buffer.length == sizeof(struct wmab_ret)) {
                ret = *((struct wmab_ret *) obj->buffer.pointer);
        } else {
-               return AE_ERROR;
+               status = AE_ERROR;
+               goto out;
        }
 
        if (ret.eax & 0x1)
                interface->capability |= ACER_CAP_BLUETOOTH;
 
-       kfree(out.pointer);
-
        /*
         * This appears to be safe to enable, since all Wistron based laptops
         * appear to use the same EC register for brightness, even if they
@@ -634,7 +710,10 @@ static acpi_status AMW0_set_capabilities(void)
        if (quirks->brightness >= 0)
                interface->capability |= ACER_CAP_BRIGHTNESS;
 
-       return AE_OK;
+       status = AE_OK;
+out:
+       kfree(out.pointer);
+       return status;
 }
 
 static struct wmi_interface AMW0_interface = {
@@ -746,7 +825,9 @@ static acpi_status WMID_set_u32(u32 value, u32 cap, struct wmi_interface *iface)
                        return AE_BAD_PARAMETER;
                if (quirks->mailled == 1) {
                        param = value ? 0x92 : 0x93;
+                       i8042_lock_chip();
                        i8042_command(&param, 0x1059);
+                       i8042_unlock_chip();
                        return 0;
                }
                break;
@@ -756,6 +837,28 @@ static acpi_status WMID_set_u32(u32 value, u32 cap, struct wmi_interface *iface)
        return WMI_execute_u32(method_id, (u32)value, NULL);
 }
 
+static void type_aa_dmi_decode(const struct dmi_header *header, void *dummy)
+{
+       struct hotkey_function_type_aa *type_aa;
+
+       /* We are looking for OEM-specific Type AAh */
+       if (header->type != 0xAA)
+               return;
+
+       has_type_aa = true;
+       type_aa = (struct hotkey_function_type_aa *) header;
+
+       printk(ACER_INFO "Function bitmap for Communication Button: 0x%x\n",
+               type_aa->commun_func_bitmap);
+
+       if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_WIRELESS)
+               interface->capability |= ACER_CAP_WIRELESS;
+       if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_THREEG)
+               interface->capability |= ACER_CAP_THREEG;
+       if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_BLUETOOTH)
+               interface->capability |= ACER_CAP_BLUETOOTH;
+}
+
 static acpi_status WMID_set_capabilities(void)
 {
        struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
@@ -772,22 +875,25 @@ static acpi_status WMID_set_capabilities(void)
                obj->buffer.length == sizeof(u32)) {
                devices = *((u32 *) obj->buffer.pointer);
        } else {
+               kfree(out.pointer);
                return AE_ERROR;
        }
 
-       /* Not sure on the meaning of the relevant bits yet to detect these */
-       interface->capability |= ACER_CAP_WIRELESS;
-       interface->capability |= ACER_CAP_THREEG;
+       dmi_walk(type_aa_dmi_decode, NULL);
+       if (!has_type_aa) {
+               interface->capability |= ACER_CAP_WIRELESS;
+               interface->capability |= ACER_CAP_THREEG;
+               if (devices & 0x10)
+                       interface->capability |= ACER_CAP_BLUETOOTH;
+       }
 
        /* WMID always provides brightness methods */
        interface->capability |= ACER_CAP_BRIGHTNESS;
 
-       if (devices & 0x10)
-               interface->capability |= ACER_CAP_BLUETOOTH;
-
        if (!(devices & 0x20))
                max_brightness = 0x9;
 
+       kfree(out.pointer);
        return status;
 }
 
@@ -862,7 +968,8 @@ static void __init acer_commandline_init(void)
         * capability isn't available on the given interface
         */
        set_u32(mailled, ACER_CAP_MAILLED);
-       set_u32(threeg, ACER_CAP_THREEG);
+       if (!has_type_aa)
+               set_u32(threeg, ACER_CAP_THREEG);
        set_u32(brightness, ACER_CAP_BRIGHTNESS);
 }
 
@@ -916,16 +1023,20 @@ static int update_bl_status(struct backlight_device *bd)
        return 0;
 }
 
-static struct backlight_ops acer_bl_ops = {
+static const struct backlight_ops acer_bl_ops = {
        .get_brightness = read_brightness,
        .update_status = update_bl_status,
 };
 
 static int __devinit acer_backlight_init(struct device *dev)
 {
+       struct backlight_properties props;
        struct backlight_device *bd;
 
-       bd = backlight_device_register("acer-wmi", dev, NULL, &acer_bl_ops);
+       memset(&props, 0, sizeof(struct backlight_properties));
+       props.max_brightness = max_brightness;
+       bd = backlight_device_register("acer-wmi", dev, NULL, &acer_bl_ops,
+                                      &props);
        if (IS_ERR(bd)) {
                printk(ACER_ERR "Could not register Acer backlight device\n");
                acer_backlight_device = NULL;
@@ -935,8 +1046,7 @@ static int __devinit acer_backlight_init(struct device *dev)
        acer_backlight_device = bd;
 
        bd->props.power = FB_BLANK_UNBLANK;
-       bd->props.brightness = max_brightness;
-       bd->props.max_brightness = max_brightness;
+       bd->props.brightness = read_brightness(bd);
        backlight_update_status(bd);
        return 0;
 }
@@ -946,6 +1056,79 @@ static void acer_backlight_exit(void)
        backlight_device_unregister(acer_backlight_device);
 }
 
+static acpi_status wmid3_get_device_status(u32 *value, u16 device)
+{
+       struct wmid3_gds_return_value return_value;
+       acpi_status status;
+       union acpi_object *obj;
+       struct wmid3_gds_input_param params = {
+               .function_num = 0x1,
+               .hotkey_number = 0x01,
+               .devices = device,
+       };
+       struct acpi_buffer input = {
+               sizeof(struct wmid3_gds_input_param),
+               &params
+       };
+       struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+
+       status = wmi_evaluate_method(WMID_GUID3, 0, 0x2, &input, &output);
+       if (ACPI_FAILURE(status))
+               return status;
+
+       obj = output.pointer;
+
+       if (!obj)
+               return AE_ERROR;
+       else if (obj->type != ACPI_TYPE_BUFFER) {
+               kfree(obj);
+               return AE_ERROR;
+       }
+       if (obj->buffer.length != 8) {
+               printk(ACER_WARNING "Unknown buffer length %d\n",
+                       obj->buffer.length);
+               kfree(obj);
+               return AE_ERROR;
+       }
+
+       return_value = *((struct wmid3_gds_return_value *)obj->buffer.pointer);
+       kfree(obj);
+
+       if (return_value.error_code || return_value.ec_return_value)
+               printk(ACER_WARNING "Get Device Status failed: "
+                       "0x%x - 0x%x\n", return_value.error_code,
+                       return_value.ec_return_value);
+       else
+               *value = !!(return_value.devices & device);
+
+       return status;
+}
+
+static acpi_status get_device_status(u32 *value, u32 cap)
+{
+       if (wmi_has_guid(WMID_GUID3)) {
+               u16 device;
+
+               switch (cap) {
+               case ACER_CAP_WIRELESS:
+                       device = ACER_WMID3_GDS_WIRELESS;
+                       break;
+               case ACER_CAP_BLUETOOTH:
+                       device = ACER_WMID3_GDS_BLUETOOTH;
+                       break;
+               case ACER_CAP_THREEG:
+                       device = ACER_WMID3_GDS_THREEG;
+                       break;
+               default:
+                       return AE_ERROR;
+               }
+               return wmid3_get_device_status(value, device);
+
+       } else {
+               return get_u32(value, cap);
+       }
+}
+
 /*
  * Rfkill devices
  */
@@ -958,61 +1141,68 @@ static void acer_rfkill_update(struct work_struct *ignored)
 
        status = get_u32(&state, ACER_CAP_WIRELESS);
        if (ACPI_SUCCESS(status))
-               rfkill_force_state(wireless_rfkill, state ?
-                       RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED);
+               rfkill_set_sw_state(wireless_rfkill, !state);
 
        if (has_cap(ACER_CAP_BLUETOOTH)) {
                status = get_u32(&state, ACER_CAP_BLUETOOTH);
                if (ACPI_SUCCESS(status))
-                       rfkill_force_state(bluetooth_rfkill, state ?
-                               RFKILL_STATE_UNBLOCKED :
-                               RFKILL_STATE_SOFT_BLOCKED);
+                       rfkill_set_sw_state(bluetooth_rfkill, !state);
+       }
+
+       if (has_cap(ACER_CAP_THREEG) && wmi_has_guid(WMID_GUID3)) {
+               status = wmid3_get_device_status(&state,
+                               ACER_WMID3_GDS_THREEG);
+               if (ACPI_SUCCESS(status))
+                       rfkill_set_sw_state(threeg_rfkill, !state);
        }
 
        schedule_delayed_work(&acer_rfkill_work, round_jiffies_relative(HZ));
 }
 
-static int acer_rfkill_set(void *data, enum rfkill_state state)
+static int acer_rfkill_set(void *data, bool blocked)
 {
        acpi_status status;
-       u32 *cap = data;
-       status = set_u32((u32) (state == RFKILL_STATE_UNBLOCKED), *cap);
-       if (ACPI_FAILURE(status))
-               return -ENODEV;
+       u32 cap = (unsigned long)data;
+
+       if (rfkill_inited) {
+               status = set_u32(!blocked, cap);
+               if (ACPI_FAILURE(status))
+                       return -ENODEV;
+       }
+
        return 0;
 }
 
-static struct rfkill * acer_rfkill_register(struct device *dev,
-enum rfkill_type type, char *name, u32 cap)
+static const struct rfkill_ops acer_rfkill_ops = {
+       .set_block = acer_rfkill_set,
+};
+
+static struct rfkill *acer_rfkill_register(struct device *dev,
+                                          enum rfkill_type type,
+                                          char *name, u32 cap)
 {
        int err;
-       u32 state;
-       u32 *data;
        struct rfkill *rfkill_dev;
+       u32 state;
+       acpi_status status;
 
-       rfkill_dev = rfkill_allocate(dev, type);
+       rfkill_dev = rfkill_alloc(name, dev, type,
+                                 &acer_rfkill_ops,
+                                 (void *)(unsigned long)cap);
        if (!rfkill_dev)
                return ERR_PTR(-ENOMEM);
-       rfkill_dev->name = name;
-       get_u32(&state, cap);
-       rfkill_dev->state = state ? RFKILL_STATE_UNBLOCKED :
-               RFKILL_STATE_SOFT_BLOCKED;
-       data = kzalloc(sizeof(u32), GFP_KERNEL);
-       if (!data) {
-               rfkill_free(rfkill_dev);
-               return ERR_PTR(-ENOMEM);
-       }
-       *data = cap;
-       rfkill_dev->data = data;
-       rfkill_dev->toggle_radio = acer_rfkill_set;
-       rfkill_dev->user_claim_unsupported = 1;
+
+       status = get_device_status(&state, cap);
 
        err = rfkill_register(rfkill_dev);
        if (err) {
-               kfree(rfkill_dev->data);
-               rfkill_free(rfkill_dev);
+               rfkill_destroy(rfkill_dev);
                return ERR_PTR(err);
        }
+
+       if (ACPI_SUCCESS(status))
+               rfkill_set_sw_state(rfkill_dev, !state);
+
        return rfkill_dev;
 }
 
@@ -1028,25 +1218,50 @@ static int acer_rfkill_init(struct device *dev)
                        RFKILL_TYPE_BLUETOOTH, "acer-bluetooth",
                        ACER_CAP_BLUETOOTH);
                if (IS_ERR(bluetooth_rfkill)) {
-                       kfree(wireless_rfkill->data);
                        rfkill_unregister(wireless_rfkill);
+                       rfkill_destroy(wireless_rfkill);
                        return PTR_ERR(bluetooth_rfkill);
                }
        }
 
-       schedule_delayed_work(&acer_rfkill_work, round_jiffies_relative(HZ));
+       if (has_cap(ACER_CAP_THREEG)) {
+               threeg_rfkill = acer_rfkill_register(dev,
+                       RFKILL_TYPE_WWAN, "acer-threeg",
+                       ACER_CAP_THREEG);
+               if (IS_ERR(threeg_rfkill)) {
+                       rfkill_unregister(wireless_rfkill);
+                       rfkill_destroy(wireless_rfkill);
+                       rfkill_unregister(bluetooth_rfkill);
+                       rfkill_destroy(bluetooth_rfkill);
+                       return PTR_ERR(threeg_rfkill);
+               }
+       }
+
+       rfkill_inited = true;
+
+       if (ec_raw_mode || !wmi_has_guid(ACERWMID_EVENT_GUID))
+               schedule_delayed_work(&acer_rfkill_work,
+                       round_jiffies_relative(HZ));
 
        return 0;
 }
 
 static void acer_rfkill_exit(void)
 {
-       cancel_delayed_work_sync(&acer_rfkill_work);
-       kfree(wireless_rfkill->data);
+       if (ec_raw_mode || !wmi_has_guid(ACERWMID_EVENT_GUID))
+               cancel_delayed_work_sync(&acer_rfkill_work);
+
        rfkill_unregister(wireless_rfkill);
+       rfkill_destroy(wireless_rfkill);
+
        if (has_cap(ACER_CAP_BLUETOOTH)) {
-               kfree(bluetooth_rfkill->data);
                rfkill_unregister(bluetooth_rfkill);
+               rfkill_destroy(bluetooth_rfkill);
+       }
+
+       if (has_cap(ACER_CAP_THREEG)) {
+               rfkill_unregister(threeg_rfkill);
+               rfkill_destroy(threeg_rfkill);
        }
        return;
 }
@@ -1058,7 +1273,12 @@ static ssize_t show_bool_threeg(struct device *dev,
        struct device_attribute *attr, char *buf)
 {
        u32 result; \
-       acpi_status status = get_u32(&result, ACER_CAP_THREEG);
+       acpi_status status;
+       if (wmi_has_guid(WMID_GUID3))
+               status = wmid3_get_device_status(&result,
+                               ACER_WMID3_GDS_THREEG);
+       else
+               status = get_u32(&result, ACER_CAP_THREEG);
        if (ACPI_SUCCESS(status))
                return sprintf(buf, "%u\n", result);
        return sprintf(buf, "Read error\n");
@@ -1073,7 +1293,7 @@ static ssize_t set_bool_threeg(struct device *dev,
                        return -EINVAL;
        return count;
 }
-static DEVICE_ATTR(threeg, S_IWUGO | S_IRUGO | S_IWUSR, show_bool_threeg,
+static DEVICE_ATTR(threeg, S_IRUGO | S_IWUSR, show_bool_threeg,
        set_bool_threeg);
 
 static ssize_t show_interface(struct device *dev, struct device_attribute *attr,
@@ -1091,8 +1311,203 @@ static ssize_t show_interface(struct device *dev, struct device_attribute *attr,
        }
 }
 
-static DEVICE_ATTR(interface, S_IWUGO | S_IRUGO | S_IWUSR,
-       show_interface, NULL);
+static DEVICE_ATTR(interface, S_IRUGO, show_interface, NULL);
+
+static void acer_wmi_notify(u32 value, void *context)
+{
+       struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+       union acpi_object *obj;
+       struct event_return_value return_value;
+       acpi_status status;
+       u16 device_state;
+       const struct key_entry *key;
+
+       status = wmi_get_event_data(value, &response);
+       if (status != AE_OK) {
+               printk(ACER_WARNING "bad event status 0x%x\n", status);
+               return;
+       }
+
+       obj = (union acpi_object *)response.pointer;
+
+       if (!obj)
+               return;
+       if (obj->type != ACPI_TYPE_BUFFER) {
+               printk(ACER_WARNING "Unknown response received %d\n",
+                       obj->type);
+               kfree(obj);
+               return;
+       }
+       if (obj->buffer.length != 8) {
+               printk(ACER_WARNING "Unknown buffer length %d\n",
+                       obj->buffer.length);
+               kfree(obj);
+               return;
+       }
+
+       return_value = *((struct event_return_value *)obj->buffer.pointer);
+       kfree(obj);
+
+       switch (return_value.function) {
+       case WMID_HOTKEY_EVENT:
+               device_state = return_value.device_state;
+               pr_debug("device state: 0x%x\n", device_state);
+
+               key = sparse_keymap_entry_from_scancode(acer_wmi_input_dev,
+                                                       return_value.key_num);
+               if (!key) {
+                       printk(ACER_WARNING "Unknown key number - 0x%x\n",
+                               return_value.key_num);
+               } else {
+                       switch (key->keycode) {
+                       case KEY_WLAN:
+                       case KEY_BLUETOOTH:
+                               if (has_cap(ACER_CAP_WIRELESS))
+                                       rfkill_set_sw_state(wireless_rfkill,
+                                               !(device_state & ACER_WMID3_GDS_WIRELESS));
+                               if (has_cap(ACER_CAP_THREEG))
+                                       rfkill_set_sw_state(threeg_rfkill,
+                                               !(device_state & ACER_WMID3_GDS_THREEG));
+                               if (has_cap(ACER_CAP_BLUETOOTH))
+                                       rfkill_set_sw_state(bluetooth_rfkill,
+                                               !(device_state & ACER_WMID3_GDS_BLUETOOTH));
+                               break;
+                       }
+                       sparse_keymap_report_entry(acer_wmi_input_dev, key,
+                                                  1, true);
+               }
+               break;
+       default:
+               printk(ACER_WARNING "Unknown function number - %d - %d\n",
+                       return_value.function, return_value.key_num);
+               break;
+       }
+}
+
+static acpi_status
+wmid3_set_lm_mode(struct lm_input_params *params,
+                 struct lm_return_value *return_value)
+{
+       acpi_status status;
+       union acpi_object *obj;
+
+       struct acpi_buffer input = { sizeof(struct lm_input_params), params };
+       struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+
+       status = wmi_evaluate_method(WMID_GUID3, 0, 0x1, &input, &output);
+       if (ACPI_FAILURE(status))
+               return status;
+
+       obj = output.pointer;
+
+       if (!obj)
+               return AE_ERROR;
+       else if (obj->type != ACPI_TYPE_BUFFER) {
+               kfree(obj);
+               return AE_ERROR;
+       }
+       if (obj->buffer.length != 4) {
+               printk(ACER_WARNING "Unknown buffer length %d\n",
+                      obj->buffer.length);
+               kfree(obj);
+               return AE_ERROR;
+       }
+
+       *return_value = *((struct lm_return_value *)obj->buffer.pointer);
+       kfree(obj);
+
+       return status;
+}
+
+static int acer_wmi_enable_ec_raw(void)
+{
+       struct lm_return_value return_value;
+       acpi_status status;
+       struct lm_input_params params = {
+               .function_num = 0x1,
+               .commun_devices = 0xFFFF,
+               .devices = 0xFFFF,
+               .lm_status = 0x00,            /* Launch Manager Deactive */
+       };
+
+       status = wmid3_set_lm_mode(&params, &return_value);
+
+       if (return_value.error_code || return_value.ec_return_value)
+               printk(ACER_WARNING "Enabling EC raw mode failed: "
+                      "0x%x - 0x%x\n", return_value.error_code,
+                      return_value.ec_return_value);
+       else
+               printk(ACER_INFO "Enabled EC raw mode");
+
+       return status;
+}
+
+static int acer_wmi_enable_lm(void)
+{
+       struct lm_return_value return_value;
+       acpi_status status;
+       struct lm_input_params params = {
+               .function_num = 0x1,
+               .commun_devices = 0xFFFF,
+               .devices = 0xFFFF,
+               .lm_status = 0x01,            /* Launch Manager Active */
+       };
+
+       status = wmid3_set_lm_mode(&params, &return_value);
+
+       if (return_value.error_code || return_value.ec_return_value)
+               printk(ACER_WARNING "Enabling Launch Manager failed: "
+                      "0x%x - 0x%x\n", return_value.error_code,
+                      return_value.ec_return_value);
+
+       return status;
+}
+
+static int __init acer_wmi_input_setup(void)
+{
+       acpi_status status;
+       int err;
+
+       acer_wmi_input_dev = input_allocate_device();
+       if (!acer_wmi_input_dev)
+               return -ENOMEM;
+
+       acer_wmi_input_dev->name = "Acer WMI hotkeys";
+       acer_wmi_input_dev->phys = "wmi/input0";
+       acer_wmi_input_dev->id.bustype = BUS_HOST;
+
+       err = sparse_keymap_setup(acer_wmi_input_dev, acer_wmi_keymap, NULL);
+       if (err)
+               goto err_free_dev;
+
+       status = wmi_install_notify_handler(ACERWMID_EVENT_GUID,
+                                               acer_wmi_notify, NULL);
+       if (ACPI_FAILURE(status)) {
+               err = -EIO;
+               goto err_free_keymap;
+       }
+
+       err = input_register_device(acer_wmi_input_dev);
+       if (err)
+               goto err_uninstall_notifier;
+
+       return 0;
+
+err_uninstall_notifier:
+       wmi_remove_notify_handler(ACERWMID_EVENT_GUID);
+err_free_keymap:
+       sparse_keymap_free(acer_wmi_input_dev);
+err_free_dev:
+       input_free_device(acer_wmi_input_dev);
+       return err;
+}
+
+static void acer_wmi_input_destroy(void)
+{
+       wmi_remove_notify_handler(ACERWMID_EVENT_GUID);
+       sparse_keymap_free(acer_wmi_input_dev);
+       input_unregister_device(acer_wmi_input_dev);
+}
 
 /*
  * debugfs functions
@@ -1102,6 +1517,7 @@ static u32 get_wmid_devices(void)
        struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
        union acpi_object *obj;
        acpi_status status;
+       u32 devices = 0;
 
        status = wmi_query_block(WMID_GUID2, 1, &out);
        if (ACPI_FAILURE(status))
@@ -1110,10 +1526,11 @@ static u32 get_wmid_devices(void)
        obj = (union acpi_object *) out.pointer;
        if (obj && obj->type == ACPI_TYPE_BUFFER &&
                obj->buffer.length == sizeof(u32)) {
-               return *((u32 *) obj->buffer.pointer);
-       } else {
-               return 0;
+               devices = *((u32 *) obj->buffer.pointer);
        }
+
+       kfree(out.pointer);
+       return devices;
 }
 
 /*
@@ -1136,11 +1553,17 @@ static int __devinit acer_platform_probe(struct platform_device *device)
        }
 
        err = acer_rfkill_init(&device->dev);
+       if (err)
+               goto error_rfkill;
 
        return err;
 
+error_rfkill:
+       if (has_cap(ACER_CAP_BRIGHTNESS))
+               acer_backlight_exit();
 error_brightness:
-       acer_led_exit();
+       if (has_cap(ACER_CAP_MAILLED))
+               acer_led_exit();
 error_mailled:
        return err;
 }
@@ -1315,7 +1738,7 @@ static int __init acer_wmi_init(void)
                AMW0_find_mailled();
 
        if (!interface) {
-               printk(ACER_ERR "No or unsupported WMI interface, unable to "
+               printk(ACER_INFO "No or unsupported WMI interface, unable to "
                                "load\n");
                return -ENODEV;
        }
@@ -1328,22 +1751,51 @@ static int __init acer_wmi_init(void)
                       "generic video driver\n");
        }
 
-       if (platform_driver_register(&acer_platform_driver)) {
+       if (wmi_has_guid(WMID_GUID3)) {
+               if (ec_raw_mode) {
+                       if (ACPI_FAILURE(acer_wmi_enable_ec_raw())) {
+                               printk(ACER_ERR "Cannot enable EC raw mode\n");
+                               return -ENODEV;
+                       }
+               } else if (ACPI_FAILURE(acer_wmi_enable_lm())) {
+                       printk(ACER_ERR "Cannot enable Launch Manager mode\n");
+                       return -ENODEV;
+               }
+       } else if (ec_raw_mode) {
+               printk(ACER_INFO "No WMID EC raw mode enable method\n");
+       }
+
+       if (wmi_has_guid(ACERWMID_EVENT_GUID)) {
+               err = acer_wmi_input_setup();
+               if (err)
+                       return err;
+       }
+
+       err = platform_driver_register(&acer_platform_driver);
+       if (err) {
                printk(ACER_ERR "Unable to register platform driver.\n");
                goto error_platform_register;
        }
+
        acer_platform_device = platform_device_alloc("acer-wmi", -1);
-       platform_device_add(acer_platform_device);
+       if (!acer_platform_device) {
+               err = -ENOMEM;
+               goto error_device_alloc;
+       }
+
+       err = platform_device_add(acer_platform_device);
+       if (err)
+               goto error_device_add;
 
        err = create_sysfs();
        if (err)
-               return err;
+               goto error_create_sys;
 
        if (wmi_has_guid(WMID_GUID2)) {
                interface->debug.wmid_devices = get_wmid_devices();
                err = create_debugfs();
                if (err)
-                       return err;
+                       goto error_create_debugfs;
        }
 
        /* Override any initial settings with values from the commandline */
@@ -1351,15 +1803,29 @@ static int __init acer_wmi_init(void)
 
        return 0;
 
+error_create_debugfs:
+       remove_sysfs(acer_platform_device);
+error_create_sys:
+       platform_device_del(acer_platform_device);
+error_device_add:
+       platform_device_put(acer_platform_device);
+error_device_alloc:
+       platform_driver_unregister(&acer_platform_driver);
 error_platform_register:
-       return -ENODEV;
+       if (wmi_has_guid(ACERWMID_EVENT_GUID))
+               acer_wmi_input_destroy();
+
+       return err;
 }
 
 static void __exit acer_wmi_exit(void)
 {
+       if (wmi_has_guid(ACERWMID_EVENT_GUID))
+               acer_wmi_input_destroy();
+
        remove_sysfs(acer_platform_device);
        remove_debugfs();
-       platform_device_del(acer_platform_device);
+       platform_device_unregister(acer_platform_device);
        platform_driver_unregister(&acer_platform_driver);
 
        printk(ACER_INFO "Acer Laptop WMI Extras unloaded\n");