]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/platform/x86/eeepc-wmi.c
Merge tag 'v2.6.38' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[mv-sheeva.git] / drivers / platform / x86 / eeepc-wmi.c
index 0d50fbbe24784e86e3eef1522134ac585d6b6a2c..4d38f98aa97657b0b13262327eccc757d6f93066 100644 (file)
@@ -2,6 +2,7 @@
  * Eee PC WMI hotkey driver
  *
  * Copyright(C) 2010 Intel Corporation.
+ * Copyright(C) 2010 Corentin Chary <corentin.chary@gmail.com>
  *
  * Portions based on wistron_btns.c:
  * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
 #include <linux/input/sparse-keymap.h>
 #include <linux/fb.h>
 #include <linux/backlight.h>
+#include <linux/leds.h>
+#include <linux/rfkill.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
 #include <linux/platform_device.h>
 #include <acpi/acpi_bus.h>
 #include <acpi/acpi_drivers.h>
@@ -44,6 +49,8 @@ MODULE_AUTHOR("Yong Wang <yong.y.wang@intel.com>");
 MODULE_DESCRIPTION("Eee PC WMI Hotkey Driver");
 MODULE_LICENSE("GPL");
 
+#define EEEPC_ACPI_HID         "ASUS010" /* old _HID used in eeepc-laptop */
+
 #define EEEPC_WMI_EVENT_GUID   "ABBC0F72-8EA1-11D1-00A0-C90629100000"
 #define EEEPC_WMI_MGMT_GUID    "97845ED0-4E6D-11DE-8A39-0800200C9A66"
 
@@ -60,6 +67,10 @@ MODULE_ALIAS("wmi:"EEEPC_WMI_MGMT_GUID);
 #define EEEPC_WMI_METHODID_CFVS        0x53564643
 
 #define EEEPC_WMI_DEVID_BACKLIGHT      0x00050012
+#define EEEPC_WMI_DEVID_TPDLED         0x00100011
+#define EEEPC_WMI_DEVID_WLAN           0x00010011
+#define EEEPC_WMI_DEVID_BLUETOOTH      0x00010013
+#define EEEPC_WMI_DEVID_WWAN3G         0x00010019
 
 static const struct key_entry eeepc_wmi_keymap[] = {
        /* Sleep already handled via generic ACPI code */
@@ -83,11 +94,37 @@ struct bios_args {
        u32     ctrl_param;
 };
 
+/*
+ * eeepc-wmi/    - debugfs root directory
+ *   dev_id      - current dev_id
+ *   ctrl_param  - current ctrl_param
+ *   devs        - call DEVS(dev_id, ctrl_param) and print result
+ *   dsts        - call DSTS(dev_id)  and print result
+ */
+struct eeepc_wmi_debug {
+       struct dentry *root;
+       u32 dev_id;
+       u32 ctrl_param;
+};
+
 struct eeepc_wmi {
        struct input_dev *inputdev;
        struct backlight_device *backlight_device;
+       struct platform_device *platform_device;
+
+       struct led_classdev tpd_led;
+       int tpd_led_wk;
+       struct workqueue_struct *led_workqueue;
+       struct work_struct tpd_led_work;
+
+       struct rfkill *wlan_rfkill;
+       struct rfkill *bluetooth_rfkill;
+       struct rfkill *wwan3g_rfkill;
+
+       struct eeepc_wmi_debug debug;
 };
 
+/* Only used in eeepc_wmi_init() and eeepc_wmi_exit() */
 static struct platform_device *platform_device;
 
 static int eeepc_wmi_input_init(struct eeepc_wmi *eeepc)
@@ -101,7 +138,7 @@ static int eeepc_wmi_input_init(struct eeepc_wmi *eeepc)
        eeepc->inputdev->name = "Eee PC WMI hotkeys";
        eeepc->inputdev->phys = EEEPC_WMI_FILE "/input0";
        eeepc->inputdev->id.bustype = BUS_HOST;
-       eeepc->inputdev->dev.parent = &platform_device->dev;
+       eeepc->inputdev->dev.parent = &eeepc->platform_device->dev;
 
        err = sparse_keymap_setup(eeepc->inputdev, eeepc_wmi_keymap, NULL);
        if (err)
@@ -130,7 +167,7 @@ static void eeepc_wmi_input_exit(struct eeepc_wmi *eeepc)
        eeepc->inputdev = NULL;
 }
 
-static acpi_status eeepc_wmi_get_devstate(u32 dev_id, u32 *ctrl_param)
+static acpi_status eeepc_wmi_get_devstate(u32 dev_id, u32 *retval)
 {
        struct acpi_buffer input = { (acpi_size)sizeof(u32), &dev_id };
        struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
@@ -150,8 +187,8 @@ static acpi_status eeepc_wmi_get_devstate(u32 dev_id, u32 *ctrl_param)
        else
                tmp = 0;
 
-       if (ctrl_param)
-               *ctrl_param = tmp;
+       if (retval)
+               *retval = tmp;
 
        kfree(obj);
 
@@ -159,7 +196,8 @@ static acpi_status eeepc_wmi_get_devstate(u32 dev_id, u32 *ctrl_param)
 
 }
 
-static acpi_status eeepc_wmi_set_devstate(u32 dev_id, u32 ctrl_param)
+static acpi_status eeepc_wmi_set_devstate(u32 dev_id, u32 ctrl_param,
+                                         u32 *retval)
 {
        struct bios_args args = {
                .dev_id = dev_id,
@@ -168,34 +206,281 @@ static acpi_status eeepc_wmi_set_devstate(u32 dev_id, u32 ctrl_param)
        struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
        acpi_status status;
 
-       status = wmi_evaluate_method(EEEPC_WMI_MGMT_GUID,
-                       1, EEEPC_WMI_METHODID_DEVS, &input, NULL);
+       if (!retval) {
+               status = wmi_evaluate_method(EEEPC_WMI_MGMT_GUID, 1,
+                                            EEEPC_WMI_METHODID_DEVS,
+                                            &input, NULL);
+       } else {
+               struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+               union acpi_object *obj;
+               u32 tmp;
+
+               status = wmi_evaluate_method(EEEPC_WMI_MGMT_GUID, 1,
+                                            EEEPC_WMI_METHODID_DEVS,
+                                            &input, &output);
+
+               if (ACPI_FAILURE(status))
+                       return status;
+
+               obj = (union acpi_object *)output.pointer;
+               if (obj && obj->type == ACPI_TYPE_INTEGER)
+                       tmp = (u32)obj->integer.value;
+               else
+                       tmp = 0;
+
+               *retval = tmp;
+
+               kfree(obj);
+       }
 
        return status;
 }
 
+/*
+ * LEDs
+ */
+/*
+ * These functions actually update the LED's, and are called from a
+ * workqueue. By doing this as separate work rather than when the LED
+ * subsystem asks, we avoid messing with the Eeepc ACPI stuff during a
+ * potentially bad time, such as a timer interrupt.
+ */
+static void tpd_led_update(struct work_struct *work)
+{
+       int ctrl_param;
+       struct eeepc_wmi *eeepc;
+
+       eeepc = container_of(work, struct eeepc_wmi, tpd_led_work);
+
+       ctrl_param = eeepc->tpd_led_wk;
+       eeepc_wmi_set_devstate(EEEPC_WMI_DEVID_TPDLED, ctrl_param, NULL);
+}
+
+static void tpd_led_set(struct led_classdev *led_cdev,
+                       enum led_brightness value)
+{
+       struct eeepc_wmi *eeepc;
+
+       eeepc = container_of(led_cdev, struct eeepc_wmi, tpd_led);
+
+       eeepc->tpd_led_wk = !!value;
+       queue_work(eeepc->led_workqueue, &eeepc->tpd_led_work);
+}
+
+static int read_tpd_state(struct eeepc_wmi *eeepc)
+{
+       u32 retval;
+       acpi_status status;
+
+       status = eeepc_wmi_get_devstate(EEEPC_WMI_DEVID_TPDLED, &retval);
+
+       if (ACPI_FAILURE(status))
+               return -1;
+       else if (!retval || retval == 0x00060000)
+               /*
+                * if touchpad led is present, DSTS will set some bits,
+                * usually 0x00020000.
+                * 0x00060000 means that the device is not supported
+                */
+               return -ENODEV;
+       else
+               /* Status is stored in the first bit */
+               return retval & 0x1;
+}
+
+static enum led_brightness tpd_led_get(struct led_classdev *led_cdev)
+{
+       struct eeepc_wmi *eeepc;
+
+       eeepc = container_of(led_cdev, struct eeepc_wmi, tpd_led);
+
+       return read_tpd_state(eeepc);
+}
+
+static int eeepc_wmi_led_init(struct eeepc_wmi *eeepc)
+{
+       int rv;
+
+       if (read_tpd_state(eeepc) < 0)
+               return 0;
+
+       eeepc->led_workqueue = create_singlethread_workqueue("led_workqueue");
+       if (!eeepc->led_workqueue)
+               return -ENOMEM;
+       INIT_WORK(&eeepc->tpd_led_work, tpd_led_update);
+
+       eeepc->tpd_led.name = "eeepc::touchpad";
+       eeepc->tpd_led.brightness_set = tpd_led_set;
+       eeepc->tpd_led.brightness_get = tpd_led_get;
+       eeepc->tpd_led.max_brightness = 1;
+
+       rv = led_classdev_register(&eeepc->platform_device->dev,
+                                  &eeepc->tpd_led);
+       if (rv) {
+               destroy_workqueue(eeepc->led_workqueue);
+               return rv;
+       }
+
+       return 0;
+}
+
+static void eeepc_wmi_led_exit(struct eeepc_wmi *eeepc)
+{
+       if (eeepc->tpd_led.dev)
+               led_classdev_unregister(&eeepc->tpd_led);
+       if (eeepc->led_workqueue)
+               destroy_workqueue(eeepc->led_workqueue);
+}
+
+/*
+ * Rfkill devices
+ */
+static int eeepc_rfkill_set(void *data, bool blocked)
+{
+       int dev_id = (unsigned long)data;
+       u32 ctrl_param = !blocked;
+
+       return eeepc_wmi_set_devstate(dev_id, ctrl_param, NULL);
+}
+
+static void eeepc_rfkill_query(struct rfkill *rfkill, void *data)
+{
+       int dev_id = (unsigned long)data;
+       u32 retval;
+       acpi_status status;
+
+       status = eeepc_wmi_get_devstate(dev_id, &retval);
+
+       if (ACPI_FAILURE(status))
+               return ;
+
+       rfkill_set_sw_state(rfkill, !(retval & 0x1));
+}
+
+static const struct rfkill_ops eeepc_rfkill_ops = {
+       .set_block = eeepc_rfkill_set,
+       .query = eeepc_rfkill_query,
+};
+
+static int eeepc_new_rfkill(struct eeepc_wmi *eeepc,
+                           struct rfkill **rfkill,
+                           const char *name,
+                           enum rfkill_type type, int dev_id)
+{
+       int result;
+       u32 retval;
+       acpi_status status;
+
+       status = eeepc_wmi_get_devstate(dev_id, &retval);
+
+       if (ACPI_FAILURE(status))
+               return -1;
+
+       /* If the device is present, DSTS will always set some bits
+        * 0x00070000 - 1110000000000000000 - device supported
+        * 0x00060000 - 1100000000000000000 - not supported
+        * 0x00020000 - 0100000000000000000 - device supported
+        * 0x00010000 - 0010000000000000000 - not supported / special mode ?
+        */
+       if (!retval || retval == 0x00060000)
+               return -ENODEV;
+
+       *rfkill = rfkill_alloc(name, &eeepc->platform_device->dev, type,
+                              &eeepc_rfkill_ops, (void *)(long)dev_id);
+
+       if (!*rfkill)
+               return -EINVAL;
+
+       rfkill_init_sw_state(*rfkill, !(retval & 0x1));
+       result = rfkill_register(*rfkill);
+       if (result) {
+               rfkill_destroy(*rfkill);
+               *rfkill = NULL;
+               return result;
+       }
+       return 0;
+}
+
+static void eeepc_wmi_rfkill_exit(struct eeepc_wmi *eeepc)
+{
+       if (eeepc->wlan_rfkill) {
+               rfkill_unregister(eeepc->wlan_rfkill);
+               rfkill_destroy(eeepc->wlan_rfkill);
+               eeepc->wlan_rfkill = NULL;
+       }
+       if (eeepc->bluetooth_rfkill) {
+               rfkill_unregister(eeepc->bluetooth_rfkill);
+               rfkill_destroy(eeepc->bluetooth_rfkill);
+               eeepc->bluetooth_rfkill = NULL;
+       }
+       if (eeepc->wwan3g_rfkill) {
+               rfkill_unregister(eeepc->wwan3g_rfkill);
+               rfkill_destroy(eeepc->wwan3g_rfkill);
+               eeepc->wwan3g_rfkill = NULL;
+       }
+}
+
+static int eeepc_wmi_rfkill_init(struct eeepc_wmi *eeepc)
+{
+       int result = 0;
+
+       result = eeepc_new_rfkill(eeepc, &eeepc->wlan_rfkill,
+                                 "eeepc-wlan", RFKILL_TYPE_WLAN,
+                                 EEEPC_WMI_DEVID_WLAN);
+
+       if (result && result != -ENODEV)
+               goto exit;
+
+       result = eeepc_new_rfkill(eeepc, &eeepc->bluetooth_rfkill,
+                                 "eeepc-bluetooth", RFKILL_TYPE_BLUETOOTH,
+                                 EEEPC_WMI_DEVID_BLUETOOTH);
+
+       if (result && result != -ENODEV)
+               goto exit;
+
+       result = eeepc_new_rfkill(eeepc, &eeepc->wwan3g_rfkill,
+                                 "eeepc-wwan3g", RFKILL_TYPE_WWAN,
+                                 EEEPC_WMI_DEVID_WWAN3G);
+
+       if (result && result != -ENODEV)
+               goto exit;
+
+exit:
+       if (result && result != -ENODEV)
+               eeepc_wmi_rfkill_exit(eeepc);
+
+       if (result == -ENODEV)
+               result = 0;
+
+       return result;
+}
+
+/*
+ * Backlight
+ */
 static int read_brightness(struct backlight_device *bd)
 {
-       static u32 ctrl_param;
+       u32 retval;
        acpi_status status;
 
-       status = eeepc_wmi_get_devstate(EEEPC_WMI_DEVID_BACKLIGHT, &ctrl_param);
+       status = eeepc_wmi_get_devstate(EEEPC_WMI_DEVID_BACKLIGHT, &retval);
 
        if (ACPI_FAILURE(status))
                return -1;
        else
-               return ctrl_param & 0xFF;
+               return retval & 0xFF;
 }
 
 static int update_bl_status(struct backlight_device *bd)
 {
 
-       static u32 ctrl_param;
+       u32 ctrl_param;
        acpi_status status;
 
        ctrl_param = bd->props.brightness;
 
-       status = eeepc_wmi_set_devstate(EEEPC_WMI_DEVID_BACKLIGHT, ctrl_param);
+       status = eeepc_wmi_set_devstate(EEEPC_WMI_DEVID_BACKLIGHT,
+                                       ctrl_param, NULL);
 
        if (ACPI_FAILURE(status))
                return -1;
@@ -234,7 +519,7 @@ static int eeepc_wmi_backlight_init(struct eeepc_wmi *eeepc)
        memset(&props, 0, sizeof(struct backlight_properties));
        props.max_brightness = 15;
        bd = backlight_device_register(EEEPC_WMI_FILE,
-                                      &platform_device->dev, eeepc,
+                                      &eeepc->platform_device->dev, eeepc,
                                       &eeepc_wmi_bl_ops, &props);
        if (IS_ERR(bd)) {
                pr_err("Could not register backlight device\n");
@@ -321,65 +606,240 @@ static ssize_t store_cpufv(struct device *dev, struct device_attribute *attr,
 
 static DEVICE_ATTR(cpufv, S_IRUGO | S_IWUSR, NULL, store_cpufv);
 
+static struct attribute *platform_attributes[] = {
+       &dev_attr_cpufv.attr,
+       NULL
+};
+
+static struct attribute_group platform_attribute_group = {
+       .attrs = platform_attributes
+};
+
 static void eeepc_wmi_sysfs_exit(struct platform_device *device)
 {
-       device_remove_file(&device->dev, &dev_attr_cpufv);
+       sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
 }
 
 static int eeepc_wmi_sysfs_init(struct platform_device *device)
 {
-       int retval = -ENOMEM;
+       return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
+}
 
-       retval = device_create_file(&device->dev, &dev_attr_cpufv);
-       if (retval)
-               goto error_sysfs;
+/*
+ * Platform device
+ */
+static int __init eeepc_wmi_platform_init(struct eeepc_wmi *eeepc)
+{
+       int err;
 
+       eeepc->platform_device = platform_device_alloc(EEEPC_WMI_FILE, -1);
+       if (!eeepc->platform_device)
+               return -ENOMEM;
+       platform_set_drvdata(eeepc->platform_device, eeepc);
+
+       err = platform_device_add(eeepc->platform_device);
+       if (err)
+               goto fail_platform_device;
+
+       err = eeepc_wmi_sysfs_init(eeepc->platform_device);
+       if (err)
+               goto fail_sysfs;
        return 0;
 
-error_sysfs:
-       eeepc_wmi_sysfs_exit(platform_device);
-       return retval;
+fail_sysfs:
+       platform_device_del(eeepc->platform_device);
+fail_platform_device:
+       platform_device_put(eeepc->platform_device);
+       return err;
 }
 
-static int __devinit eeepc_wmi_platform_probe(struct platform_device *device)
+static void eeepc_wmi_platform_exit(struct eeepc_wmi *eeepc)
 {
+       eeepc_wmi_sysfs_exit(eeepc->platform_device);
+       platform_device_unregister(eeepc->platform_device);
+}
+
+/*
+ * debugfs
+ */
+struct eeepc_wmi_debugfs_node {
        struct eeepc_wmi *eeepc;
-       int err;
+       char *name;
+       int (*show)(struct seq_file *m, void *data);
+};
+
+static int show_dsts(struct seq_file *m, void *data)
+{
+       struct eeepc_wmi *eeepc = m->private;
        acpi_status status;
+       u32 retval = -1;
 
-       eeepc = platform_get_drvdata(device);
+       status = eeepc_wmi_get_devstate(eeepc->debug.dev_id, &retval);
+
+       if (ACPI_FAILURE(status))
+               return -EIO;
+
+       seq_printf(m, "DSTS(%x) = %x\n", eeepc->debug.dev_id, retval);
+
+       return 0;
+}
+
+static int show_devs(struct seq_file *m, void *data)
+{
+       struct eeepc_wmi *eeepc = m->private;
+       acpi_status status;
+       u32 retval = -1;
+
+       status = eeepc_wmi_set_devstate(eeepc->debug.dev_id,
+                                       eeepc->debug.ctrl_param, &retval);
+       if (ACPI_FAILURE(status))
+               return -EIO;
+
+       seq_printf(m, "DEVS(%x, %x) = %x\n", eeepc->debug.dev_id,
+                  eeepc->debug.ctrl_param, retval);
+
+       return 0;
+}
+
+static struct eeepc_wmi_debugfs_node eeepc_wmi_debug_files[] = {
+       { NULL, "devs", show_devs },
+       { NULL, "dsts", show_dsts },
+};
+
+static int eeepc_wmi_debugfs_open(struct inode *inode, struct file *file)
+{
+       struct eeepc_wmi_debugfs_node *node = inode->i_private;
+
+       return single_open(file, node->show, node->eeepc);
+}
+
+static const struct file_operations eeepc_wmi_debugfs_io_ops = {
+       .owner = THIS_MODULE,
+       .open  = eeepc_wmi_debugfs_open,
+       .read = seq_read,
+       .llseek = seq_lseek,
+       .release = single_release,
+};
+
+static void eeepc_wmi_debugfs_exit(struct eeepc_wmi *eeepc)
+{
+       debugfs_remove_recursive(eeepc->debug.root);
+}
+
+static int eeepc_wmi_debugfs_init(struct eeepc_wmi *eeepc)
+{
+       struct dentry *dent;
+       int i;
+
+       eeepc->debug.root = debugfs_create_dir(EEEPC_WMI_FILE, NULL);
+       if (!eeepc->debug.root) {
+               pr_err("failed to create debugfs directory");
+               goto error_debugfs;
+       }
+
+       dent = debugfs_create_x32("dev_id", S_IRUGO|S_IWUSR,
+                                 eeepc->debug.root, &eeepc->debug.dev_id);
+       if (!dent)
+               goto error_debugfs;
+
+       dent = debugfs_create_x32("ctrl_param", S_IRUGO|S_IWUSR,
+                                 eeepc->debug.root, &eeepc->debug.ctrl_param);
+       if (!dent)
+               goto error_debugfs;
+
+       for (i = 0; i < ARRAY_SIZE(eeepc_wmi_debug_files); i++) {
+               struct eeepc_wmi_debugfs_node *node = &eeepc_wmi_debug_files[i];
+
+               node->eeepc = eeepc;
+               dent = debugfs_create_file(node->name, S_IFREG | S_IRUGO,
+                                          eeepc->debug.root, node,
+                                          &eeepc_wmi_debugfs_io_ops);
+               if (!dent) {
+                       pr_err("failed to create debug file: %s\n", node->name);
+                       goto error_debugfs;
+               }
+       }
+
+       return 0;
+
+error_debugfs:
+       eeepc_wmi_debugfs_exit(eeepc);
+       return -ENOMEM;
+}
+
+/*
+ * WMI Driver
+ */
+static struct platform_device * __init eeepc_wmi_add(void)
+{
+       struct eeepc_wmi *eeepc;
+       acpi_status status;
+       int err;
+
+       eeepc = kzalloc(sizeof(struct eeepc_wmi), GFP_KERNEL);
+       if (!eeepc)
+               return ERR_PTR(-ENOMEM);
+
+       /*
+        * Register the platform device first.  It is used as a parent for the
+        * sub-devices below.
+        */
+       err = eeepc_wmi_platform_init(eeepc);
+       if (err)
+               goto fail_platform;
 
        err = eeepc_wmi_input_init(eeepc);
        if (err)
-               goto error_input;
+               goto fail_input;
+
+       err = eeepc_wmi_led_init(eeepc);
+       if (err)
+               goto fail_leds;
+
+       err = eeepc_wmi_rfkill_init(eeepc);
+       if (err)
+               goto fail_rfkill;
 
        if (!acpi_video_backlight_support()) {
                err = eeepc_wmi_backlight_init(eeepc);
                if (err)
-                       goto error_backlight;
+                       goto fail_backlight;
        } else
                pr_info("Backlight controlled by ACPI video driver\n");
 
        status = wmi_install_notify_handler(EEEPC_WMI_EVENT_GUID,
-                                       eeepc_wmi_notify, eeepc);
+                                           eeepc_wmi_notify, eeepc);
        if (ACPI_FAILURE(status)) {
                pr_err("Unable to register notify handler - %d\n",
                        status);
                err = -ENODEV;
-               goto error_wmi;
+               goto fail_wmi_handler;
        }
 
-       return 0;
+       err = eeepc_wmi_debugfs_init(eeepc);
+       if (err)
+               goto fail_debugfs;
 
-error_wmi:
+       return eeepc->platform_device;
+
+fail_debugfs:
+       wmi_remove_notify_handler(EEEPC_WMI_EVENT_GUID);
+fail_wmi_handler:
        eeepc_wmi_backlight_exit(eeepc);
-error_backlight:
+fail_backlight:
+       eeepc_wmi_rfkill_exit(eeepc);
+fail_rfkill:
+       eeepc_wmi_led_exit(eeepc);
+fail_leds:
        eeepc_wmi_input_exit(eeepc);
-error_input:
-       return err;
+fail_input:
+       eeepc_wmi_platform_exit(eeepc);
+fail_platform:
+       kfree(eeepc);
+       return ERR_PTR(err);
 }
 
-static int __devexit eeepc_wmi_platform_remove(struct platform_device *device)
+static int eeepc_wmi_remove(struct platform_device *device)
 {
        struct eeepc_wmi *eeepc;
 
@@ -387,7 +847,12 @@ static int __devexit eeepc_wmi_platform_remove(struct platform_device *device)
        wmi_remove_notify_handler(EEEPC_WMI_EVENT_GUID);
        eeepc_wmi_backlight_exit(eeepc);
        eeepc_wmi_input_exit(eeepc);
+       eeepc_wmi_led_exit(eeepc);
+       eeepc_wmi_rfkill_exit(eeepc);
+       eeepc_wmi_debugfs_exit(eeepc);
+       eeepc_wmi_platform_exit(eeepc);
 
+       kfree(eeepc);
        return 0;
 }
 
@@ -396,13 +861,31 @@ static struct platform_driver platform_driver = {
                .name = EEEPC_WMI_FILE,
                .owner = THIS_MODULE,
        },
-       .probe = eeepc_wmi_platform_probe,
-       .remove = __devexit_p(eeepc_wmi_platform_remove),
 };
 
+static acpi_status __init eeepc_wmi_parse_device(acpi_handle handle, u32 level,
+                                                void *context, void **retval)
+{
+       pr_warning("Found legacy ATKD device (%s)", EEEPC_ACPI_HID);
+       *(bool *)context = true;
+       return AE_CTRL_TERMINATE;
+}
+
+static int __init eeepc_wmi_check_atkd(void)
+{
+       acpi_status status;
+       bool found = false;
+
+       status = acpi_get_devices(EEEPC_ACPI_HID, eeepc_wmi_parse_device,
+                                 &found, NULL);
+
+       if (ACPI_FAILURE(status) || !found)
+               return 0;
+       return -1;
+}
+
 static int __init eeepc_wmi_init(void)
 {
-       struct eeepc_wmi *eeepc;
        int err;
 
        if (!wmi_has_guid(EEEPC_WMI_EVENT_GUID) ||
@@ -411,58 +894,40 @@ static int __init eeepc_wmi_init(void)
                return -ENODEV;
        }
 
-       eeepc = kzalloc(sizeof(struct eeepc_wmi), GFP_KERNEL);
-       if (!eeepc)
-               return -ENOMEM;
-
-       platform_device = platform_device_alloc(EEEPC_WMI_FILE, -1);
-       if (!platform_device) {
-               pr_warning("Unable to allocate platform device\n");
-               err = -ENOMEM;
-               goto fail_platform;
+       if (eeepc_wmi_check_atkd()) {
+               pr_warning("WMI device present, but legacy ATKD device is also "
+                          "present and enabled.");
+               pr_warning("You probably booted with acpi_osi=\"Linux\" or "
+                          "acpi_osi=\"!Windows 2009\"");
+               pr_warning("Can't load eeepc-wmi, use default acpi_osi "
+                          "(preferred) or eeepc-laptop");
+               return -ENODEV;
        }
 
-       err = platform_device_add(platform_device);
-       if (err) {
-               pr_warning("Unable to add platform device\n");
-               goto put_dev;
+       platform_device = eeepc_wmi_add();
+       if (IS_ERR(platform_device)) {
+               err = PTR_ERR(platform_device);
+               goto fail_eeepc_wmi;
        }
 
-       platform_set_drvdata(platform_device, eeepc);
-
        err = platform_driver_register(&platform_driver);
        if (err) {
                pr_warning("Unable to register platform driver\n");
-               goto del_dev;
+               goto fail_platform_driver;
        }
 
-       err = eeepc_wmi_sysfs_init(platform_device);
-       if (err)
-               goto del_sysfs;
-
        return 0;
 
-del_sysfs:
-       eeepc_wmi_sysfs_exit(platform_device);
-del_dev:
-       platform_device_del(platform_device);
-put_dev:
-       platform_device_put(platform_device);
-fail_platform:
-       kfree(eeepc);
-
+fail_platform_driver:
+       eeepc_wmi_remove(platform_device);
+fail_eeepc_wmi:
        return err;
 }
 
 static void __exit eeepc_wmi_exit(void)
 {
-       struct eeepc_wmi *eeepc;
-
-       eeepc_wmi_sysfs_exit(platform_device);
-       eeepc = platform_get_drvdata(platform_device);
+       eeepc_wmi_remove(platform_device);
        platform_driver_unregister(&platform_driver);
-       platform_device_unregister(platform_device);
-       kfree(eeepc);
 }
 
 module_init(eeepc_wmi_init);