Merge tag 'platform-drivers-x86-v5.3-1' of git://git.infradead.org/linux-platform...
[linux-2.6-microblaze.git] / drivers / platform / x86 / asus-wmi.c
index 9b18a18..18f3a8b 100644 (file)
@@ -57,6 +57,7 @@ MODULE_LICENSE("GPL");
 #define NOTIFY_KBD_BRTUP               0xc4
 #define NOTIFY_KBD_BRTDWN              0xc5
 #define NOTIFY_KBD_BRTTOGGLE           0xc7
+#define NOTIFY_KBD_FBM                 0x99
 
 #define ASUS_WMI_FNLOCK_BIOS_DISABLED  BIT(0)
 
@@ -67,9 +68,27 @@ MODULE_LICENSE("GPL");
 #define ASUS_FAN_CTRL_MANUAL           1
 #define ASUS_FAN_CTRL_AUTO             2
 
+#define ASUS_FAN_MODE_NORMAL           0
+#define ASUS_FAN_MODE_OVERBOOST                1
+#define ASUS_FAN_MODE_OVERBOOST_MASK   0x01
+#define ASUS_FAN_MODE_SILENT           2
+#define ASUS_FAN_MODE_SILENT_MASK      0x02
+#define ASUS_FAN_MODES_MASK            0x03
+
 #define USB_INTEL_XUSB2PR              0xD0
 #define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_XHCI  0x9c31
 
+#define ASUS_ACPI_UID_ASUSWMI          "ASUSWMI"
+#define ASUS_ACPI_UID_ATK              "ATK"
+
+#define WMI_EVENT_QUEUE_SIZE           0x10
+#define WMI_EVENT_QUEUE_END            0x1
+#define WMI_EVENT_MASK                 0xFFFF
+/* The WMI hotkey event value is always the same. */
+#define WMI_EVENT_VALUE_ATK            0xFF
+
+#define WMI_EVENT_MASK                 0xFFFF
+
 static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL };
 
 static bool ashs_present(void)
@@ -85,6 +104,7 @@ static bool ashs_present(void)
 struct bios_args {
        u32 arg0;
        u32 arg1;
+       u32 arg2; /* At least TUF Gaming series uses 3 dword input buffer. */
 } __packed;
 
 /*
@@ -132,6 +152,7 @@ struct asus_wmi {
        int dsts_id;
        int spec;
        int sfun;
+       bool wmi_event_queue;
 
        struct input_dev *inputdev;
        struct backlight_device *backlight_device;
@@ -161,6 +182,10 @@ struct asus_wmi {
        int asus_hwmon_num_fans;
        int asus_hwmon_pwm;
 
+       bool fan_mode_available;
+       u8 fan_mode_mask;
+       u8 fan_mode;
+
        struct hotplug_slot hotplug_slot;
        struct mutex hotplug_lock;
        struct mutex wmi_lock;
@@ -174,6 +199,8 @@ struct asus_wmi {
        struct asus_wmi_driver *driver;
 };
 
+/* Input **********************************************************************/
+
 static int asus_wmi_input_init(struct asus_wmi *asus)
 {
        int err;
@@ -211,11 +238,15 @@ static void asus_wmi_input_exit(struct asus_wmi *asus)
        asus->inputdev = NULL;
 }
 
-int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval)
+/* WMI ************************************************************************/
+
+static int asus_wmi_evaluate_method3(u32 method_id,
+               u32 arg0, u32 arg1, u32 arg2, u32 *retval)
 {
        struct bios_args args = {
                .arg0 = arg0,
                .arg1 = arg1,
+               .arg2 = arg2,
        };
        struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
        struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
@@ -227,7 +258,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval)
                                     &input, &output);
 
        if (ACPI_FAILURE(status))
-               goto exit;
+               return -EIO;
 
        obj = (union acpi_object *)output.pointer;
        if (obj && obj->type == ACPI_TYPE_INTEGER)
@@ -238,15 +269,16 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval)
 
        kfree(obj);
 
-exit:
-       if (ACPI_FAILURE(status))
-               return -EIO;
-
        if (tmp == ASUS_WMI_UNSUPPORTED_METHOD)
                return -ENODEV;
 
        return 0;
 }
+
+int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval)
+{
+       return asus_wmi_evaluate_method3(method_id, arg0, arg1, 0, retval);
+}
 EXPORT_SYMBOL_GPL(asus_wmi_evaluate_method);
 
 static int asus_wmi_evaluate_method_agfn(const struct acpi_buffer args)
@@ -320,9 +352,8 @@ static int asus_wmi_get_devstate_simple(struct asus_wmi *asus, u32 dev_id)
                                          ASUS_WMI_DSTS_STATUS_BIT);
 }
 
-/*
- * LEDs
- */
+/* 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
@@ -427,6 +458,10 @@ static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
 static void kbd_led_set(struct led_classdev *led_cdev,
                        enum led_brightness value)
 {
+       /* Prevent disabling keyboard backlight on module unregister */
+       if (led_cdev->flags & LED_UNREGISTERING)
+               return;
+
        do_kbd_led_set(led_cdev, value);
 }
 
@@ -582,8 +617,7 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
                        goto error;
        }
 
-       led_val = kbd_led_read(asus, NULL, NULL);
-       if (led_val >= 0) {
+       if (!kbd_led_read(asus, &led_val, NULL)) {
                asus->kbd_led_wk = led_val;
                asus->kbd_led.name = "asus::kbd_backlight";
                asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
@@ -633,6 +667,7 @@ error:
        return rv;
 }
 
+/* RF *************************************************************************/
 
 /*
  * PCI hotplug (for wlan rfkill)
@@ -1055,6 +1090,8 @@ exit:
        return result;
 }
 
+/* Quirks *********************************************************************/
+
 static void asus_wmi_set_xusb2pr(struct asus_wmi *asus)
 {
        struct pci_dev *xhci_pdev;
@@ -1087,9 +1124,8 @@ static void asus_wmi_set_als(void)
        asus_wmi_set_devstate(ASUS_WMI_DEVID_ALS_ENABLE, 1, NULL);
 }
 
-/*
- * Hwmon device
- */
+/* Hwmon device ***************************************************************/
+
 static int asus_hwmon_agfn_fan_speed_read(struct asus_wmi *asus, int fan,
                                          int *speed)
 {
@@ -1353,8 +1389,7 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj,
                                          struct attribute *attr, int idx)
 {
        struct device *dev = container_of(kobj, struct device, kobj);
-       struct platform_device *pdev = to_platform_device(dev->parent);
-       struct asus_wmi *asus = platform_get_drvdata(pdev);
+       struct asus_wmi *asus = dev_get_drvdata(dev->parent);
        int dev_id = -1;
        int fan_attr = -1;
        u32 value = ASUS_WMI_UNSUPPORTED_METHOD;
@@ -1395,8 +1430,11 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj,
                else
                        ok = fan_attr <= asus->asus_hwmon_num_fans;
        } else if (dev_id == ASUS_WMI_DEVID_THERMAL_CTRL) {
-               /* If value is zero, something is clearly wrong */
-               if (!value)
+               /*
+                * If the temperature value in deci-Kelvin is near the absolute
+                * zero temperature, something is clearly wrong
+                */
+               if (value == 0 || value == 1)
                        ok = false;
        } else if (fan_attr <= asus->asus_hwmon_num_fans && fan_attr != -1) {
                ok = true;
@@ -1415,11 +1453,12 @@ __ATTRIBUTE_GROUPS(hwmon_attribute);
 
 static int asus_wmi_hwmon_init(struct asus_wmi *asus)
 {
+       struct device *dev = &asus->platform_device->dev;
        struct device *hwmon;
 
-       hwmon = hwmon_device_register_with_groups(&asus->platform_device->dev,
-                                                 "asus", asus,
-                                                 hwmon_attribute_groups);
+       hwmon = devm_hwmon_device_register_with_groups(dev, "asus", asus,
+                       hwmon_attribute_groups);
+
        if (IS_ERR(hwmon)) {
                pr_err("Could not register asus hwmon device\n");
                return PTR_ERR(hwmon);
@@ -1427,9 +1466,137 @@ static int asus_wmi_hwmon_init(struct asus_wmi *asus)
        return 0;
 }
 
-/*
- * Backlight
- */
+static int asus_wmi_fan_init(struct asus_wmi *asus)
+{
+       int status;
+
+       asus->asus_hwmon_pwm = -1;
+       asus->asus_hwmon_num_fans = -1;
+       asus->asus_hwmon_fan_manual_mode = false;
+
+       status = asus_hwmon_get_fan_number(asus, &asus->asus_hwmon_num_fans);
+       if (status) {
+               asus->asus_hwmon_num_fans = 0;
+               pr_warn("Could not determine number of fans: %d\n", status);
+               return -ENXIO;
+       }
+
+       pr_info("Number of fans: %d\n", asus->asus_hwmon_num_fans);
+       return 0;
+}
+
+/* Fan mode *******************************************************************/
+
+static int fan_mode_check_present(struct asus_wmi *asus)
+{
+       u32 result;
+       int err;
+
+       asus->fan_mode_available = false;
+
+       err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_MODE, &result);
+       if (err) {
+               if (err == -ENODEV)
+                       return 0;
+               else
+                       return err;
+       }
+
+       if ((result & ASUS_WMI_DSTS_PRESENCE_BIT) &&
+                       (result & ASUS_FAN_MODES_MASK)) {
+               asus->fan_mode_available = true;
+               asus->fan_mode_mask = result & ASUS_FAN_MODES_MASK;
+       }
+
+       return 0;
+}
+
+static int fan_mode_write(struct asus_wmi *asus)
+{
+       int err;
+       u8 value;
+       u32 retval;
+
+       value = asus->fan_mode;
+
+       pr_info("Set fan mode: %u\n", value);
+       err = asus_wmi_set_devstate(ASUS_WMI_DEVID_FAN_MODE, value, &retval);
+
+       if (err) {
+               pr_warn("Failed to set fan mode: %d\n", err);
+               return err;
+       }
+
+       if (retval != 1) {
+               pr_warn("Failed to set fan mode (retval): 0x%x\n", retval);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static int fan_mode_switch_next(struct asus_wmi *asus)
+{
+       if (asus->fan_mode == ASUS_FAN_MODE_NORMAL) {
+               if (asus->fan_mode_mask & ASUS_FAN_MODE_OVERBOOST_MASK)
+                       asus->fan_mode = ASUS_FAN_MODE_OVERBOOST;
+               else if (asus->fan_mode_mask & ASUS_FAN_MODE_SILENT_MASK)
+                       asus->fan_mode = ASUS_FAN_MODE_SILENT;
+       } else if (asus->fan_mode == ASUS_FAN_MODE_OVERBOOST) {
+               if (asus->fan_mode_mask & ASUS_FAN_MODE_SILENT_MASK)
+                       asus->fan_mode = ASUS_FAN_MODE_SILENT;
+               else
+                       asus->fan_mode = ASUS_FAN_MODE_NORMAL;
+       } else {
+               asus->fan_mode = ASUS_FAN_MODE_NORMAL;
+       }
+
+       return fan_mode_write(asus);
+}
+
+static ssize_t fan_mode_show(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       struct asus_wmi *asus = dev_get_drvdata(dev);
+
+       return scnprintf(buf, PAGE_SIZE, "%d\n", asus->fan_mode);
+}
+
+static ssize_t fan_mode_store(struct device *dev, struct device_attribute *attr,
+               const char *buf, size_t count)
+{
+       int result;
+       u8 new_mode;
+
+       struct asus_wmi *asus = dev_get_drvdata(dev);
+
+       result = kstrtou8(buf, 10, &new_mode);
+       if (result < 0) {
+               pr_warn("Trying to store invalid value\n");
+               return result;
+       }
+
+       if (new_mode == ASUS_FAN_MODE_OVERBOOST) {
+               if (!(asus->fan_mode_mask & ASUS_FAN_MODE_OVERBOOST_MASK))
+                       return -EINVAL;
+       } else if (new_mode == ASUS_FAN_MODE_SILENT) {
+               if (!(asus->fan_mode_mask & ASUS_FAN_MODE_SILENT_MASK))
+                       return -EINVAL;
+       } else if (new_mode != ASUS_FAN_MODE_NORMAL) {
+               return -EINVAL;
+       }
+
+       asus->fan_mode = new_mode;
+       fan_mode_write(asus);
+
+       return result;
+}
+
+// Fan mode: 0 - normal, 1 - overboost, 2 - silent
+static DEVICE_ATTR_RW(fan_mode);
+
+/* Backlight ******************************************************************/
+
 static int read_backlight_power(struct asus_wmi *asus)
 {
        int ret;
@@ -1611,6 +1778,8 @@ static int is_display_toggle(int code)
        return 0;
 }
 
+/* Fn-lock ********************************************************************/
+
 static bool asus_wmi_has_fnlock_key(struct asus_wmi *asus)
 {
        u32 result;
@@ -1628,88 +1797,148 @@ static void asus_wmi_fnlock_update(struct asus_wmi *asus)
        asus_wmi_set_devstate(ASUS_WMI_DEVID_FNLOCK, mode, NULL);
 }
 
-static void asus_wmi_notify(u32 value, void *context)
+/* WMI events *****************************************************************/
+
+static int asus_wmi_get_event_code(u32 value)
 {
-       struct asus_wmi *asus = context;
        struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
        union acpi_object *obj;
        acpi_status status;
        int code;
-       int orig_code;
-       unsigned int key_value = 1;
-       bool autorelease = 1;
 
        status = wmi_get_event_data(value, &response);
-       if (status != AE_OK) {
-               pr_err("bad event status 0x%x\n", status);
-               return;
+       if (ACPI_FAILURE(status)) {
+               pr_warn("Failed to get WMI notify code: %s\n",
+                               acpi_format_exception(status));
+               return -EIO;
        }
 
        obj = (union acpi_object *)response.pointer;
 
-       if (!obj || obj->type != ACPI_TYPE_INTEGER)
-               goto exit;
+       if (obj && obj->type == ACPI_TYPE_INTEGER)
+               code = (int)(obj->integer.value & WMI_EVENT_MASK);
+       else
+               code = -EIO;
+
+       kfree(obj);
+       return code;
+}
+
+static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
+{
+       int orig_code;
+       unsigned int key_value = 1;
+       bool autorelease = 1;
 
-       code = obj->integer.value;
        orig_code = code;
 
        if (asus->driver->key_filter) {
                asus->driver->key_filter(asus->driver, &code, &key_value,
                                         &autorelease);
                if (code == ASUS_WMI_KEY_IGNORE)
-                       goto exit;
+                       return;
        }
 
        if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX)
                code = ASUS_WMI_BRN_UP;
-       else if (code >= NOTIFY_BRNDOWN_MIN &&
-                code <= NOTIFY_BRNDOWN_MAX)
+       else if (code >= NOTIFY_BRNDOWN_MIN && code <= NOTIFY_BRNDOWN_MAX)
                code = ASUS_WMI_BRN_DOWN;
 
        if (code == ASUS_WMI_BRN_DOWN || code == ASUS_WMI_BRN_UP) {
                if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
                        asus_wmi_backlight_notify(asus, orig_code);
-                       goto exit;
+                       return;
                }
        }
 
        if (code == NOTIFY_KBD_BRTUP) {
                kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
-               goto exit;
+               return;
        }
        if (code == NOTIFY_KBD_BRTDWN) {
                kbd_led_set_by_kbd(asus, asus->kbd_led_wk - 1);
-               goto exit;
+               return;
        }
        if (code == NOTIFY_KBD_BRTTOGGLE) {
                if (asus->kbd_led_wk == asus->kbd_led.max_brightness)
                        kbd_led_set_by_kbd(asus, 0);
                else
                        kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
-               goto exit;
+               return;
        }
 
        if (code == NOTIFY_FNLOCK_TOGGLE) {
                asus->fnlock_locked = !asus->fnlock_locked;
                asus_wmi_fnlock_update(asus);
-               goto exit;
+               return;
        }
 
-       if (is_display_toggle(code) &&
-           asus->driver->quirks->no_display_toggle)
-               goto exit;
+       if (asus->fan_mode_available && code == NOTIFY_KBD_FBM) {
+               fan_mode_switch_next(asus);
+               return;
+       }
+
+       if (is_display_toggle(code) && asus->driver->quirks->no_display_toggle)
+               return;
 
        if (!sparse_keymap_report_event(asus->inputdev, code,
                                        key_value, autorelease))
                pr_info("Unknown key %x pressed\n", code);
+}
 
-exit:
-       kfree(obj);
+static void asus_wmi_notify(u32 value, void *context)
+{
+       struct asus_wmi *asus = context;
+       int code;
+       int i;
+
+       for (i = 0; i < WMI_EVENT_QUEUE_SIZE + 1; i++) {
+               code = asus_wmi_get_event_code(value);
+
+               if (code < 0) {
+                       pr_warn("Failed to get notify code: %d\n", code);
+                       return;
+               }
+
+               if (code == WMI_EVENT_QUEUE_END || code == WMI_EVENT_MASK)
+                       return;
+
+               asus_wmi_handle_event_code(code, asus);
+
+               /*
+                * Double check that queue is present:
+                * ATK (with queue) uses 0xff, ASUSWMI (without) 0xd2.
+                */
+               if (!asus->wmi_event_queue || value != WMI_EVENT_VALUE_ATK)
+                       return;
+       }
+
+       pr_warn("Failed to process event queue, last code: 0x%x\n", code);
 }
 
-/*
- * Sys helpers
- */
+static int asus_wmi_notify_queue_flush(struct asus_wmi *asus)
+{
+       int code;
+       int i;
+
+       for (i = 0; i < WMI_EVENT_QUEUE_SIZE + 1; i++) {
+               code = asus_wmi_get_event_code(WMI_EVENT_VALUE_ATK);
+
+               if (code < 0) {
+                       pr_warn("Failed to get event during flush: %d\n", code);
+                       return code;
+               }
+
+               if (code == WMI_EVENT_QUEUE_END || code == WMI_EVENT_MASK)
+                       return 0;
+       }
+
+       pr_warn("Failed to flush event queue\n");
+       return -EIO;
+}
+
+/* Sysfs **********************************************************************/
+
 static int parse_arg(const char *buf, unsigned long count, int *val)
 {
        if (!count)
@@ -1805,6 +2034,7 @@ static struct attribute *platform_attributes[] = {
        &dev_attr_touchpad.attr,
        &dev_attr_lid_resume.attr,
        &dev_attr_als_enable.attr,
+       &dev_attr_fan_mode.attr,
        NULL
 };
 
@@ -1826,6 +2056,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
                devid = ASUS_WMI_DEVID_LID_RESUME;
        else if (attr == &dev_attr_als_enable.attr)
                devid = ASUS_WMI_DEVID_ALS_ENABLE;
+       else if (attr == &dev_attr_fan_mode.attr)
+               ok = asus->fan_mode_available;
 
        if (devid != -1)
                ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0);
@@ -1848,11 +2080,12 @@ static int asus_wmi_sysfs_init(struct platform_device *device)
        return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
 }
 
-/*
- * Platform device
- */
+/* Platform device ************************************************************/
+
 static int asus_wmi_platform_init(struct asus_wmi *asus)
 {
+       struct device *dev = &asus->platform_device->dev;
+       char *wmi_uid;
        int rv;
 
        /* INIT enable hotkeys on some models */
@@ -1882,11 +2115,41 @@ static int asus_wmi_platform_init(struct asus_wmi *asus)
         * Note, on most Eeepc, there is no way to check if a method exist
         * or note, while on notebooks, they returns 0xFFFFFFFE on failure,
         * but once again, SPEC may probably be used for that kind of things.
+        *
+        * Additionally at least TUF Gaming series laptops return nothing for
+        * unknown methods, so the detection in this way is not possible.
+        *
+        * There is strong indication that only ACPI WMI devices that have _UID
+        * equal to "ASUSWMI" use DCTS whereas those with "ATK" use DSTS.
         */
-       if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, 0, 0, NULL))
+       wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID);
+       if (!wmi_uid)
+               return -ENODEV;
+
+       if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI)) {
+               dev_info(dev, "Detected ASUSWMI, use DCTS\n");
+               asus->dsts_id = ASUS_WMI_METHODID_DCTS;
+       } else {
+               dev_info(dev, "Detected %s, not ASUSWMI, use DSTS\n", wmi_uid);
                asus->dsts_id = ASUS_WMI_METHODID_DSTS;
-       else
-               asus->dsts_id = ASUS_WMI_METHODID_DSTS2;
+       }
+
+       /*
+        * Some devices can have multiple event codes stored in a queue before
+        * the module load if it was unloaded intermittently after calling
+        * the INIT method (enables event handling). The WMI notify handler is
+        * expected to retrieve all event codes until a retrieved code equals
+        * queue end marker (One or Ones). Old codes are flushed from the queue
+        * upon module load. Not enabling this when it should be has minimal
+        * visible impact so fall back if anything goes wrong.
+        */
+       wmi_uid = wmi_get_acpi_device_uid(asus->driver->event_guid);
+       if (wmi_uid && !strcmp(wmi_uid, ASUS_ACPI_UID_ATK)) {
+               dev_info(dev, "Detected ATK, enable event queue\n");
+
+               if (!asus_wmi_notify_queue_flush(asus))
+                       asus->wmi_event_queue = true;
+       }
 
        /* CWAP allow to define the behavior of the Fn+F2 key,
         * this method doesn't seems to be present on Eee PCs */
@@ -1894,17 +2157,11 @@ static int asus_wmi_platform_init(struct asus_wmi *asus)
                asus_wmi_set_devstate(ASUS_WMI_DEVID_CWAP,
                                      asus->driver->quirks->wapf, NULL);
 
-       return asus_wmi_sysfs_init(asus->platform_device);
+       return 0;
 }
 
-static void asus_wmi_platform_exit(struct asus_wmi *asus)
-{
-       asus_wmi_sysfs_exit(asus->platform_device);
-}
+/* debugfs ********************************************************************/
 
-/*
- * debugfs
- */
 struct asus_wmi_debugfs_node {
        struct asus_wmi *asus;
        char *name;
@@ -2005,74 +2262,33 @@ static void asus_wmi_debugfs_exit(struct asus_wmi *asus)
        debugfs_remove_recursive(asus->debug.root);
 }
 
-static int asus_wmi_debugfs_init(struct asus_wmi *asus)
+static void asus_wmi_debugfs_init(struct asus_wmi *asus)
 {
-       struct dentry *dent;
        int i;
 
        asus->debug.root = debugfs_create_dir(asus->driver->name, NULL);
-       if (!asus->debug.root) {
-               pr_err("failed to create debugfs directory\n");
-               goto error_debugfs;
-       }
 
-       dent = debugfs_create_x32("method_id", S_IRUGO | S_IWUSR,
-                                 asus->debug.root, &asus->debug.method_id);
-       if (!dent)
-               goto error_debugfs;
+       debugfs_create_x32("method_id", S_IRUGO | S_IWUSR, asus->debug.root,
+                          &asus->debug.method_id);
 
-       dent = debugfs_create_x32("dev_id", S_IRUGO | S_IWUSR,
-                                 asus->debug.root, &asus->debug.dev_id);
-       if (!dent)
-               goto error_debugfs;
+       debugfs_create_x32("dev_id", S_IRUGO | S_IWUSR, asus->debug.root,
+                          &asus->debug.dev_id);
 
-       dent = debugfs_create_x32("ctrl_param", S_IRUGO | S_IWUSR,
-                                 asus->debug.root, &asus->debug.ctrl_param);
-       if (!dent)
-               goto error_debugfs;
+       debugfs_create_x32("ctrl_param", S_IRUGO | S_IWUSR, asus->debug.root,
+                          &asus->debug.ctrl_param);
 
        for (i = 0; i < ARRAY_SIZE(asus_wmi_debug_files); i++) {
                struct asus_wmi_debugfs_node *node = &asus_wmi_debug_files[i];
 
                node->asus = asus;
-               dent = debugfs_create_file(node->name, S_IFREG | S_IRUGO,
-                                          asus->debug.root, node,
-                                          &asus_wmi_debugfs_io_ops);
-               if (!dent) {
-                       pr_err("failed to create debug file: %s\n", node->name);
-                       goto error_debugfs;
-               }
+               debugfs_create_file(node->name, S_IFREG | S_IRUGO,
+                                   asus->debug.root, node,
+                                   &asus_wmi_debugfs_io_ops);
        }
-
-       return 0;
-
-error_debugfs:
-       asus_wmi_debugfs_exit(asus);
-       return -ENOMEM;
 }
 
-static int asus_wmi_fan_init(struct asus_wmi *asus)
-{
-       int status;
-
-       asus->asus_hwmon_pwm = -1;
-       asus->asus_hwmon_num_fans = -1;
-       asus->asus_hwmon_fan_manual_mode = false;
-
-       status = asus_hwmon_get_fan_number(asus, &asus->asus_hwmon_num_fans);
-       if (status) {
-               asus->asus_hwmon_num_fans = 0;
-               pr_warn("Could not determine number of fans: %d\n", status);
-               return -ENXIO;
-       }
-
-       pr_info("Number of fans: %d\n", asus->asus_hwmon_num_fans);
-       return 0;
-}
+/* Init / exit ****************************************************************/
 
-/*
- * WMI Driver
- */
 static int asus_wmi_add(struct platform_device *pdev)
 {
        struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver);
@@ -2099,6 +2315,14 @@ static int asus_wmi_add(struct platform_device *pdev)
        if (err)
                goto fail_platform;
 
+       err = fan_mode_check_present(asus);
+       if (err)
+               goto fail_fan_mode;
+
+       err = asus_wmi_sysfs_init(asus->platform_device);
+       if (err)
+               goto fail_sysfs;
+
        err = asus_wmi_input_init(asus);
        if (err)
                goto fail_input;
@@ -2162,14 +2386,10 @@ static int asus_wmi_add(struct platform_device *pdev)
                goto fail_wmi_handler;
        }
 
-       err = asus_wmi_debugfs_init(asus);
-       if (err)
-               goto fail_debugfs;
+       asus_wmi_debugfs_init(asus);
 
        return 0;
 
-fail_debugfs:
-       wmi_remove_notify_handler(asus->driver->event_guid);
 fail_wmi_handler:
        asus_wmi_backlight_exit(asus);
 fail_backlight:
@@ -2180,7 +2400,9 @@ fail_leds:
 fail_hwmon:
        asus_wmi_input_exit(asus);
 fail_input:
-       asus_wmi_platform_exit(asus);
+       asus_wmi_sysfs_exit(asus->platform_device);
+fail_sysfs:
+fail_fan_mode:
 fail_platform:
        kfree(asus);
        return err;
@@ -2197,16 +2419,15 @@ static int asus_wmi_remove(struct platform_device *device)
        asus_wmi_led_exit(asus);
        asus_wmi_rfkill_exit(asus);
        asus_wmi_debugfs_exit(asus);
-       asus_wmi_platform_exit(asus);
+       asus_wmi_sysfs_exit(asus->platform_device);
        asus_hwmon_fan_set_auto(asus);
 
        kfree(asus);
        return 0;
 }
 
-/*
- * Platform driver - hibernate/resume callbacks
- */
+/* Platform driver - hibernate/resume callbacks *******************************/
+
 static int asus_hotk_thaw(struct device *device)
 {
        struct asus_wmi *asus = dev_get_drvdata(device);
@@ -2282,6 +2503,8 @@ static const struct dev_pm_ops asus_pm_ops = {
        .resume = asus_hotk_resume,
 };
 
+/* Registration ***************************************************************/
+
 static int asus_wmi_probe(struct platform_device *pdev)
 {
        struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver);