This driver has been available on
https://github.com/iksaif/thinkpad-wmi for
a few year and is already deployed on large
fleets of thinkpad laptops.

The WMI interface is documented here:
http://download.lenovo.com/ibmdl/pub/pc/pccbbs/thinkcentre_pdf/hrdeploy_en.pdf

It mostly focused on changing BIOS/Firmware settings.

Signed-off-by: Corentin Chary <corentin.ch...@gmail.com>
---
 .../ABI/testing/sysfs-platform-thinkpad-wmi        |   50 +
 Documentation/platform/thinkpad-wmi.txt            |   92 ++
 drivers/platform/x86/Kconfig                       |   10 +
 drivers/platform/x86/Makefile                      |    1 +
 drivers/platform/x86/thinkpad-wmi.c                | 1210 ++++++++++++++++++++
 5 files changed, 1363 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-platform-thinkpad-wmi
 create mode 100644 Documentation/platform/thinkpad-wmi.txt
 create mode 100644 drivers/platform/x86/thinkpad-wmi.c

diff --git a/Documentation/ABI/testing/sysfs-platform-thinkpad-wmi 
b/Documentation/ABI/testing/sysfs-platform-thinkpad-wmi
new file mode 100644
index 000000000000..c3673876c5b3
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-thinkpad-wmi
@@ -0,0 +1,50 @@
+What:          /sys/devices/platform/thinkpad-wmi/password
+Date:          Aug 2017
+KernelVersion: 4.14
+Contact:       "Corentin Chary" <corentin.ch...@gmail.com>
+Description:
+               BIOS password needs to be written in this file if set
+               to be able to change BIOS settings.
+
+What:          /sys/devices/platform/thinkpad-wmi/password_encoding
+Date:          Aug 2017
+KernelVersion: 4.14
+Contact:       "Corentin Chary" <corentin.ch...@gmail.com>
+Description:
+               Password encoding ('ascii' or 'scanmode').
+
+What:          /sys/devices/platform/thinkpad-wmi/password_kbd_lang
+Date:          Aug 2017
+KernelVersion: 4.14
+Contact:       "Corentin Chary" <corentin.ch...@gmail.com>
+Description:
+               Keyboard language used for password. One of 'us', 'fr' and 'gr'.
+
+What:          /sys/devices/platform/thinkpad-wmi/password_type
+Date:          Aug 2017
+KernelVersion: 4.14
+Contact:       "Corentin Chary" <corentin.ch...@gmail.com>
+Description:
+               Password type to be changed when password_change is written to, 
e.g. 'pap'.
+
+What:          /sys/devices/platform/thinkpad-wmi/password_change
+Date:          Aug 2017
+KernelVersion: 4.14
+Contact:       "Corentin Chary" <corentin.ch...@gmail.com>
+Description:
+               Writing to this file will set the password specified in 
password_type.
+               The new password will not take effect until the next reboot.
+
+What:          /sys/devices/platform/thinkpad-wmi/password_settings
+Date:          Oct 2015
+KernelVersion: 4.14
+Contact:       "Corentin Chary" <corentin.ch...@gmail.com>
+Description:
+               Display various password settings.
+
+What:          /sys/devices/platform/thinkpad-wmi/load_default_settings
+Date:          Oct 2015
+KernelVersion: 4.14
+Contact:       "Corentin Chary" <corentin.ch...@gmail.com>
+Description:
+               Write anything to this file to load default BIOS settings.
diff --git a/Documentation/platform/thinkpad-wmi.txt 
b/Documentation/platform/thinkpad-wmi.txt
new file mode 100644
index 000000000000..40d141aecc7b
--- /dev/null
+++ b/Documentation/platform/thinkpad-wmi.txt
@@ -0,0 +1,92 @@
+# thinkpad-wmi
+
+Linux Driver for Thinkpad WMI interface, allows you to control most
+BIOS settings from Linux, and maybe more.
+
+## sysfs interface
+
+Directory: /sys/bus/wmi/drivers/thinkpad-wmi/
+
+Each setting exposed by the WMI interface is available under its own name
+in this sysfs directory. Read from the file to get the current value (line 1)
+and list of options (line 2), and write an option to the file to set it.
+
+Additionally, there are some extra files for querying and managing BIOS
+password(s).
+
+### password
+
+Must contain the BIOS supervisor password (aka 'pap'), if set, to be able to do
+any change.
+
+Every subsequent password change will be authorized with this password. The
+password may be unloaded by writing an empty string. Writing an invalid
+password may trigger the BIOS' invalid password limit, such that a reboot will
+be required in order to make any further BIOS changes.
+
+### password_encoding
+
+Encoding used for the password, either '', 'scancode' or 'ascii'.
+
+Scan-code encoding appears to require the key-down scan codes, e.g. 0x1e, 0x30,
+0x2e for the ASCII encoded password 'abc'.
+
+### password_kbd_lang
+
+Keyboard language mapping, can be '', 'us', 'fr' or 'gr'.
+
+### password_type
+
+Specify the password type to be changed when password_change is written to.
+Can be:
+* 'pap': supervisor password
+* 'pop': power-on-password
+
+Other types may be valid, e.g. for user and master disk passwords.
+
+### password_change
+
+Writing to this file will change the password specified by password_type. The
+new password will not take effect until the next reboot.
+
+### password_settings
+
+Display password related settings. This includes:
+
+* password_state: which passwords are set, if any
+  * bit 0: user password (power on password) is installed / 'pop'
+  * bit 1: admin/supervisor password is installed / 'pap'
+  * bit 2: hdd password(s) installed
+* supported_encodings: supported keyboard encoding(s)
+  * bit 0: ASCII
+  * bit 1: scancode
+* supported_keyboard: support keyboard language(s)
+  * bit 0: us
+  * bit 1: fr
+  * bit 2: gr
+
+### load_default_settings
+
+Reset all settings to factory default.
+
+## debugfs interface
+
+The debugfs interface maps closely to the WMI Interface (see driver and doc).
+
+* bios_settings: show all BIOS settings
+* bios_setting: show BIOS setting for <instance>
+* list_valid_choices: list settings for <argument>
+* set_bios_settings: call set bios settings command with <argument>.
+* save_bios_settings call save bios settings command with <argument>.
+* discard_bios_settings: call discard bios settings command with <argument>.
+* load_default: call load default with <argument>.
+* set_bios_password: call set BIOS password with <argument>.
+* argument: argument to be used in various commands.
+* instance: setting instance.
+* instance_count: number of settings.
+* password_settings: password settings.
+
+## References
+
+Thinkpad WMI interface documentation:
+http://download.lenovo.com/ibmdl/pub/pc/pccbbs/thinkcentre_pdf/hrdeploy_en.pdf
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 80b87954f6dd..4e2e8a04228a 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -511,6 +511,16 @@ config THINKPAD_ACPI_HOTKEY_POLL
          If you are not sure, say Y here.  The driver enables polling only if
          it is strictly necessary to do so.
 
+config THINKPAD_WMI
+       tristate "THINKPAD WMI Driver (EXPERIMENTAL)"
+       depends on ACPI_WMI
+       ---help---
+         This driver allow you to modify BIOS passwords, settings, and boot 
order
+         using Windows Management Instrumentation (WMI) through the Lenovo
+         client-management interface.
+
+         Say Y here if you have a WMI aware Thinkpad.
+
 config SENSORS_HDAPS
        tristate "Thinkpad Hard Drive Active Protection System (hdaps)"
        depends on INPUT
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 91cec1751461..3b03f0744794 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_TC1100_WMI)      += tc1100-wmi.o
 obj-$(CONFIG_SONY_LAPTOP)      += sony-laptop.o
 obj-$(CONFIG_IDEAPAD_LAPTOP)   += ideapad-laptop.o
 obj-$(CONFIG_THINKPAD_ACPI)    += thinkpad_acpi.o
+obj-$(CONFIG_THINKPAD_WMI)     += thinkpad-wmi.o
 obj-$(CONFIG_SENSORS_HDAPS)    += hdaps.o
 obj-$(CONFIG_FUJITSU_LAPTOP)   += fujitsu-laptop.o
 obj-$(CONFIG_FUJITSU_TABLET)   += fujitsu-tablet.o
diff --git a/drivers/platform/x86/thinkpad-wmi.c 
b/drivers/platform/x86/thinkpad-wmi.c
new file mode 100644
index 000000000000..c102971f2979
--- /dev/null
+++ b/drivers/platform/x86/thinkpad-wmi.c
@@ -0,0 +1,1210 @@
+/*
+ * Thinkpad WMI configuration driver
+ *
+ * Copyright(C) 2017 Corentin Chary <corentin.ch...@gmail.com>
+ *
+ *  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/acpi.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/wmi.h>
+
+#define        THINKPAD_WMI_FILE       "thinkpad-wmi"
+
+MODULE_AUTHOR("Corentin Chary <corentin.ch...@gmail.com>");
+MODULE_DESCRIPTION("Thinkpad WMI Driver");
+MODULE_LICENSE("GPL");
+
+/* WMI interface */
+
+/**
+ * Name:
+ *  Lenovo_BiosSetting
+ * Description:
+ *  Get item name and settings for current WMI instance.
+ * Type:
+ *  Query
+ * Returns:
+ *  "Item,Value"
+ * Example:
+ *  "WakeOnLAN,Enable"
+ */
+#define LENOVO_BIOS_SETTING_GUID               \
+       "51F5230E-9677-46CD-A1CF-C0B23EE34DB7"
+
+/**
+ * Name:
+ *  Lenovo_SetBiosSetting
+ * Description:
+ *  Change the BIOS setting to the desired value using the 
Lenovo_SetBiosSetting
+ *  class. To save the settings, use the Lenovo_SaveBiosSetting class.
+ *  BIOS settings and values are case sensitive.
+ *  After making changes to the BIOS settings, you must reboot the computer
+ *  before the changes will take effect.
+ * Type:
+ *  Method
+ * Arguments:
+ *  "Item,Value,Password,Encoding,KbdLang;"
+ * Example:
+ *  "WakeOnLAN,Disable,pswd,ascii,us;"
+ */
+#define LENOVO_SET_BIOS_SETTINGS_GUID          \
+       "98479A64-33F5-4E33-A707-8E251EBBC3A1"
+
+/**
+ * Name:
+ *  Lenovo_SaveBiosSettings
+ * Description:
+ *  Save any pending changes in settings.
+ * Type:
+ *  Method
+ * Arguments:
+ *  "Password,Encoding,KbdLang;"
+ * Example:
+ * "pswd,ascii,us;"
+ */
+#define LENOVO_SAVE_BIOS_SETTINGS_GUID         \
+       "6A4B54EF-A5ED-4D33-9455-B0D9B48DF4B3"
+
+
+/**
+ * Name:
+ *  Lenovo_DiscardBiosSettings
+ * Description:
+ *  Discard any pending changes in settings.
+ * Type:
+ *  Method
+ * Arguments:
+ *  "Password,Encoding,KbdLang;"
+ * Example:
+ *  "pswd,ascii,us;"
+ */
+#define LENOVO_DISCARD_BIOS_SETTINGS_GUID      \
+       "74F1EBB6-927A-4C7D-95DF-698E21E80EB5"
+
+/**
+ * Name:
+ *  Lenovo_LoadDefaultSettings
+ * Description:
+ *  Load default BIOS settings. Use Lenovo_SaveBiosSettings to save the
+ *  settings.
+ * Type:
+ *  Method
+ * Arguments:
+ *  "Password,Encoding,KbdLang;"
+ * Example:
+ *  "pswd,ascii,us;"
+ */
+#define LENOVO_LOAD_DEFAULT_SETTINGS_GUID      \
+       "7EEF04FF-4328-447C-B5BB-D449925D538D"
+
+/**
+ * Name:
+ *  Lenovo_BiosPasswordSettings
+ * Description:
+ *  Return BIOS Password settings
+ * Type:
+ *  Query
+ * Returns:
+ *  PasswordMode, PasswordState, MinLength, MaxLength,
+ *  SupportedEncoding, SupportedKeyboard
+ */
+#define LENOVO_BIOS_PASSWORD_SETTINGS_GUID             \
+       "8ADB159E-1E32-455C-BC93-308A7ED98246"
+
+/**
+ * Name:
+ *  Lenovo_SetBiosPassword
+ * Description:
+ *  Change a specific password.
+ *  - BIOS settings cannot be changed at the same boot as power-on
+ *    passwords (POP) and hard disk passwords (HDP). If you want to change
+ *    BIOS settings and POP or HDP, you must reboot the system after changing
+ *    one of them.
+ *  - A password cannot be set using this method when one does not already
+ *    exist. Passwords can only be updated or cleared.
+ * Type:
+ *  Method
+ * Arguments:
+ *  "PasswordType,CurrentPassword,NewPassword,Encoding,KbdLang;"
+ * Example:
+ *  "pop,oldpop,newpop,ascii,us;”
+ */
+#define LENOVO_SET_BIOS_PASSWORD_GUID  \
+       "2651D9FD-911C-4B69-B94E-D0DED5963BD7"
+
+/**
+ * Name:
+ *  Lenovo_GetBiosSelections
+ * Description:
+ *  Return a list valid settings for a given item.
+ * Type:
+ *  Method
+ * Arguments:
+ *  "Item"
+ * Returns:
+ *  "Value1,Value2,Value3,..."
+ * Example:
+ *  -> "FlashOverLAN"
+ *  <- "Enabled,Disabled"
+ */
+#define LENOVO_GET_BIOS_SELECTIONS_GUID        \
+       "7364651A-132F-4FE7-ADAA-40C6C7EE2E3B"
+
+/**
+ * Name:
+ *  ???
+ * Type:
+ *  Method
+ * Arguments:
+ *  ???
+ * Example:
+ *  ???
+ * WMI-Internals:
+ *  Return big chunk of data
+ */
+#define LENOVO_QUERY_GUID                      \
+       "05901221-D566-11D1-B2F0-00A0C9062910"
+
+/* Return values */
+
+enum {
+       /*
+        * "Success"
+        * Operation completed successfully.
+        */
+       THINKPAD_WMI_SUCCESS = 0,
+       /*
+        * "Not Supported"
+        * The feature is not supported on this system.
+        */
+       THINKPAD_WMI_NOT_SUPPORTED = -ENODEV,
+       /*
+        * "Invalid"
+        * The item or value provided is not valid parameter
+        */
+       THINKPAD_WMI_INVALID = -EINVAL,
+       /*
+        * "Access Denied"
+        * The change could not be made due to an authentication problem.
+        * If a supervisor password exists, the correct supervisor password
+        * must be provided.
+        */
+       THINKPAD_WMI_ACCESS_DENIED = -EPERM,
+       /* "System Busy"
+        * BIOS changes have already been made that need to be committed.
+        * Reboot the system and try again.
+        */
+       THINKPAD_WMI_SYSTEM_BUSY = -EBUSY
+};
+
+/* Only add an alias on this one, since it's the one used
+ * in thinkpad_wmi_probe.
+ */
+MODULE_ALIAS("wmi:"LENOVO_BIOS_SETTING_GUID);
+
+struct thinkpad_wmi_pcfg {
+       uint32_t password_mode;
+       uint32_t password_state;
+       uint32_t min_length;
+       uint32_t max_length;
+       uint32_t supported_encodings;
+       uint32_t supported_keyboard;
+};
+
+/*
+ * thinkpad_wmi/       - debugfs root directory
+ *   bios_settings
+ *   bios_setting
+ *   list_valid_choices
+ *   set_bios_settings
+ *   save_bios_settings
+ *   discard_bios_settings
+ *   load_default
+ *   set_bios_password
+ *   argument
+ *   instance
+ *   instance_count
+ *   bios_password_settings
+ */
+struct thinkpad_wmi_debug {
+       struct dentry *root;
+
+       u8 instances_count;
+       u8 instance;
+       char argument[512];
+};
+
+struct thinkpad_wmi {
+       struct wmi_device *wmi_device;
+
+       int settings_count;
+
+       char password[64];
+       char password_encoding[64];
+       char password_kbdlang[4]; /* 2 bytes for \n\0 */
+       char auth_string[256];
+       char password_type[64];
+
+       bool can_set_bios_settings;
+       bool can_discard_bios_settings;
+       bool can_load_default_settings;
+       bool can_get_bios_selections;
+       bool can_set_bios_password;
+       bool can_get_password_settings;
+
+       char *settings[256];
+       struct dev_ext_attribute *devattrs;
+       struct thinkpad_wmi_debug debug;
+};
+
+/* helpers */
+static int thinkpad_wmi_errstr_to_err(const char *errstr)
+{
+       if (!strcmp(errstr, "Success"))
+               return THINKPAD_WMI_SUCCESS;
+       if (!strcmp(errstr, "Not Supported"))
+               return THINKPAD_WMI_NOT_SUPPORTED;
+       if (!strcmp(errstr, "Invalid"))
+               return THINKPAD_WMI_INVALID;
+       if (!strcmp(errstr, "Access Denied"))
+               return THINKPAD_WMI_ACCESS_DENIED;
+       if (!strcmp(errstr, "System Busy"))
+               return THINKPAD_WMI_SYSTEM_BUSY;
+
+       pr_debug("Unknown error string: '%s'", errstr);
+
+       return -EINVAL;
+}
+
+static int thinkpad_wmi_extract_error(const struct acpi_buffer *output)
+{
+       const union acpi_object *obj;
+       int ret;
+
+       obj = output->pointer;
+       if (!obj || obj->type != ACPI_TYPE_STRING || !obj->string.pointer)
+               return -EIO;
+
+       ret = thinkpad_wmi_errstr_to_err(obj->string.pointer);
+       kfree(obj);
+       return ret;
+}
+
+static int thinkpad_wmi_simple_call(const char *guid,
+                                   const char *arg)
+{
+       const struct acpi_buffer input = { strlen(arg), (char *)arg };
+       struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+       acpi_status status;
+
+       status = wmi_evaluate_method(guid, 0, 0, &input, &output);
+
+       if (ACPI_FAILURE(status))
+               return -EIO;
+
+       return thinkpad_wmi_extract_error(&output);
+}
+
+static int thinkpad_wmi_extract_output_string(const struct acpi_buffer *output,
+                                             char **string)
+{
+       const union acpi_object *obj;
+
+       obj = output->pointer;
+       if (!obj || obj->type != ACPI_TYPE_STRING || !obj->string.pointer)
+               return -EIO;
+
+       *string = kstrdup(obj->string.pointer, GFP_KERNEL);
+       kfree(obj);
+       return *string ? 0 : -ENOMEM;
+}
+
+static int thinkpad_wmi_bios_setting(int item, char **value)
+{
+       struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+       acpi_status status;
+
+       status = wmi_query_block(LENOVO_BIOS_SETTING_GUID, item, &output);
+       if (ACPI_FAILURE(status))
+               return -EIO;
+
+       return thinkpad_wmi_extract_output_string(&output, value);
+}
+
+static int thinkpad_wmi_get_bios_selections(const char *item, char **value)
+{
+       const struct acpi_buffer input = { strlen(item), (char *)item };
+       struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+       acpi_status status;
+
+       status = wmi_evaluate_method(LENOVO_GET_BIOS_SELECTIONS_GUID,
+                                    0, 0, &input, &output);
+
+       if (ACPI_FAILURE(status))
+               return -EIO;
+
+       return thinkpad_wmi_extract_output_string(&output, value);
+}
+
+static int thinkpad_wmi_set_bios_settings(const char *settings)
+{
+       return thinkpad_wmi_simple_call(LENOVO_SET_BIOS_SETTINGS_GUID,
+                                       settings);
+}
+
+static int thinkpad_wmi_save_bios_settings(const char *password)
+{
+       return thinkpad_wmi_simple_call(LENOVO_SAVE_BIOS_SETTINGS_GUID,
+                                       password);
+}
+
+static int thinkpad_wmi_discard_bios_settings(const char *password)
+{
+       return thinkpad_wmi_simple_call(LENOVO_DISCARD_BIOS_SETTINGS_GUID,
+                                       password);
+}
+
+static int thinkpad_wmi_load_default(const char *password)
+{
+       return thinkpad_wmi_simple_call(LENOVO_LOAD_DEFAULT_SETTINGS_GUID,
+                                       password);
+}
+
+static int thinkpad_wmi_set_bios_password(const char *settings)
+{
+       return thinkpad_wmi_simple_call(LENOVO_SET_BIOS_PASSWORD_GUID,
+                                       settings);
+}
+
+static int thinkpad_wmi_password_settings(struct thinkpad_wmi_pcfg *pcfg)
+{
+       struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+       const union acpi_object *obj;
+       acpi_status status;
+
+       status = wmi_query_block(LENOVO_BIOS_PASSWORD_SETTINGS_GUID, 0,
+                                &output);
+       if (ACPI_FAILURE(status))
+               return -EIO;
+
+       obj = output.pointer;
+       if (!obj || obj->type != ACPI_TYPE_BUFFER || !obj->buffer.pointer)
+               return -EIO;
+       if (obj->buffer.length != sizeof(*pcfg)) {
+               pr_warn("Unknown pcfg buffer length %d\n", obj->buffer.length);
+               kfree(obj);
+               return -EIO;
+       }
+
+       memcpy(pcfg, obj->buffer.pointer, obj->buffer.length);
+       kfree(obj);
+       return 0;
+}
+
+/* sysfs */
+
+#define to_ext_attr(x) container_of(x, struct dev_ext_attribute, attr)
+
+static ssize_t show_setting(struct device *dev,
+                           struct device_attribute *attr,
+                           char *buf)
+{
+       struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev);
+       struct dev_ext_attribute *ea = to_ext_attr(attr);
+       int item = (uintptr_t)ea->var;
+       char *name = thinkpad->settings[item];
+       char *settings = NULL, *choices = NULL, *value;
+       ssize_t count = 0;
+       int ret;
+
+       ret = thinkpad_wmi_bios_setting(item, &settings);
+       if (ret)
+               return ret;
+       if (!settings)
+               return -EIO;
+
+       if (thinkpad->can_get_bios_selections) {
+               ret = thinkpad_wmi_get_bios_selections(name, &choices);
+               if (ret)
+                       goto error;
+               if (!choices || !*choices) {
+                       ret = -EIO;
+                       goto error;
+               }
+       }
+
+       value = strchr(settings, ',');
+       if (!value)
+               goto error;
+       value++;
+
+       count = sprintf(buf, "%s\n", value);
+       if (choices)
+               count += sprintf(buf + count, "%s\n", choices);
+
+error:
+       kfree(settings);
+       kfree(choices);
+       return ret ? ret : count;
+}
+
+static ssize_t store_setting(struct device *dev,
+                             struct device_attribute *attr,
+                             const char *buf, size_t count)
+{
+       struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev);
+       struct dev_ext_attribute *ea = to_ext_attr(attr);
+       int item_idx = (uintptr_t)ea->var;
+       const char *item = thinkpad->settings[item_idx];
+       int ret;
+       size_t buffer_size;
+       char *buffer;
+
+       /* Format: 'Item,Value,Authstring;' */
+       buffer_size = (strlen(item) + 1 + count + 1 +
+                      sizeof(thinkpad->auth_string) + 2);
+       buffer = kmalloc(buffer_size, GFP_KERNEL);
+       if (!buffer)
+               return -ENOMEM;
+
+       strcpy(buffer, item);
+       strcat(buffer, ",");
+       strncat(buffer, buf, count);
+       if (count)
+               strim(buffer);
+       if (*thinkpad->auth_string) {
+               strcat(buffer, ",");
+               strcat(buffer, thinkpad->auth_string);
+       }
+       strcat(buffer, ";");
+
+       ret = thinkpad_wmi_set_bios_settings(buffer);
+       if (ret)
+               goto end;
+
+       ret = thinkpad_wmi_save_bios_settings(thinkpad->auth_string);
+       if (ret) {
+               /* Try to discard the settings if we failed to apply them. */
+               thinkpad_wmi_discard_bios_settings(thinkpad->auth_string);
+               goto end;
+       }
+       ret = count;
+
+end:
+       kfree(buffer);
+       return ret;
+}
+
+
+/* Password related sysfs methods */
+static ssize_t show_auth(struct thinkpad_wmi *thinkpad, char *buf,
+                        const char *data, size_t size)
+{
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       return sprintf(buf, "%s\n", data ? : "(nil)");
+}
+
+/* Create the auth string from password chunks */
+static void update_auth_string(struct thinkpad_wmi *thinkpad)
+{
+       if (!*thinkpad->password) {
+               /* No password at all */
+               thinkpad->auth_string[0] = '\0';
+               return;
+       }
+       strcpy(thinkpad->auth_string, thinkpad->password);
+
+       if (*thinkpad->password_encoding) {
+               strcat(thinkpad->auth_string, ",");
+               strcat(thinkpad->auth_string, thinkpad->password_encoding);
+       }
+
+       if (*thinkpad->password_kbdlang) {
+               strcat(thinkpad->auth_string, ",");
+               strcat(thinkpad->auth_string, thinkpad->password_kbdlang);
+       }
+}
+
+static ssize_t store_auth(struct thinkpad_wmi *thinkpad,
+                         const char *buf, size_t count,
+                         char *dst, size_t size)
+{
+       ssize_t ret;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       if (count > size - 1)
+               return -EINVAL;
+
+       /* dst may be being reused, NUL-terminate */
+       ret = strscpy(dst, buf, size);
+       if (ret < 0)
+               return ret;
+       if (count)
+               strim(dst);
+
+       update_auth_string(thinkpad);
+
+       return count;
+}
+
+#define THINKPAD_WMI_CREATE_AUTH_ATTR(_name, _uname, _mode)            \
+       static ssize_t show_##_name(struct device *dev,                 \
+                                   struct device_attribute *attr,      \
+                                   char *buf)                          \
+       {                                                               \
+               struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev);   \
+                                                                       \
+               return show_auth(thinkpad, buf,                         \
+                                thinkpad->_name,                       \
+                                sizeof(thinkpad->_name));              \
+       }                                                               \
+       static ssize_t store_##_name(struct device *dev,                \
+                                    struct device_attribute *attr,     \
+                                    const char *buf, size_t count)     \
+       {                                                               \
+               struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev);   \
+                                                                       \
+               return store_auth(thinkpad, buf, count,                 \
+                                 thinkpad->_name,                      \
+                                 sizeof(thinkpad->_name));             \
+       }                                                               \
+       static struct device_attribute dev_attr_##_name = {             \
+               .attr = {                                               \
+                       .name = _uname,                                 \
+                       .mode = _mode },                                \
+               .show   = show_##_name,                                 \
+               .store  = store_##_name,                                \
+       }
+
+THINKPAD_WMI_CREATE_AUTH_ATTR(password, "password", 0600);
+THINKPAD_WMI_CREATE_AUTH_ATTR(password_encoding, "password_encoding",
+                             0600);
+THINKPAD_WMI_CREATE_AUTH_ATTR(password_kbdlang, "password_kbd_lang",
+                             0600);
+THINKPAD_WMI_CREATE_AUTH_ATTR(password_type, "password_type", 0600);
+
+static ssize_t show_password_settings(struct device *dev,
+                                     struct device_attribute *attr,
+                                     char *buf)
+{
+       struct thinkpad_wmi_pcfg pcfg;
+       ssize_t ret;
+
+       ret = thinkpad_wmi_password_settings(&pcfg);
+       if (ret)
+               return ret;
+       ret += sprintf(buf, "password_mode:       %#x\n", pcfg.password_mode);
+       ret += sprintf(buf + ret, "password_state:      %#x\n",
+                      pcfg.password_state);
+       ret += sprintf(buf + ret, "min_length:          %d\n", pcfg.min_length);
+       ret += sprintf(buf + ret, "max_length:          %d\n", pcfg.max_length);
+       ret += sprintf(buf + ret, "supported_encodings: %#x\n",
+                      pcfg.supported_encodings);
+       ret += sprintf(buf + ret, "supported_keyboard:  %#x\n",
+                      pcfg.supported_keyboard);
+       return ret;
+}
+
+static DEVICE_ATTR(password_settings, 0400, show_password_settings, NULL);
+
+static ssize_t store_password_change(struct device *dev,
+                                    struct device_attribute *attr,
+                                    const char *buf, size_t count)
+{
+       struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev);
+       size_t buffer_size;
+       char *buffer;
+       ssize_t ret;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       /* Format: 'PasswordType,CurrentPw,NewPw,Encoding,KbdLang;' */
+
+       /* auth_string is the size of CurrentPassword,Encoding,KbdLang */
+       buffer_size = (sizeof(thinkpad->password_type) + 1 + count + 1 +
+                      sizeof(thinkpad->auth_string) + 2);
+       buffer = kmalloc(buffer_size, GFP_KERNEL);
+       if (!buffer)
+               return -ENOMEM;
+
+       strcpy(buffer, thinkpad->password_type);
+
+       if (*thinkpad->password) {
+               strcat(buffer, ",");
+               strcat(buffer, thinkpad->password);
+       }
+       strcat(buffer, ",");
+       strncat(buffer, buf, count);
+       if (count)
+               strim(buffer);
+
+       if (*thinkpad->password_encoding) {
+               strcat(buffer, ",");
+               strcat(buffer, thinkpad->password_encoding);
+       }
+       if (*thinkpad->password_kbdlang) {
+               strcat(buffer, ",");
+               strcat(buffer, thinkpad->password_kbdlang);
+       }
+       strcat(buffer, ";");
+
+       ret = thinkpad_wmi_set_bios_password(buffer);
+       if (ret)
+               return ret;
+
+       return count;
+}
+
+static struct device_attribute dev_attr_password_change = {
+       .attr = {
+               .name = "password_change",
+               .mode = 0200 },
+       .store  = store_password_change,
+};
+
+
+static ssize_t store_load_default(struct device *dev,
+                                 struct device_attribute *attr,
+                                 const char *buf, size_t count)
+{
+       struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev);
+
+       return thinkpad_wmi_load_default(thinkpad->auth_string);
+}
+
+static DEVICE_ATTR(load_default_settings, 0200, NULL, store_load_default);
+
+static struct attribute *platform_attributes[] = {
+       &dev_attr_password_settings.attr,
+       &dev_attr_password.attr,
+       &dev_attr_password_encoding.attr,
+       &dev_attr_password_kbdlang.attr,
+       &dev_attr_password_type.attr,
+       &dev_attr_password_change.attr,
+       &dev_attr_load_default_settings.attr,
+       NULL
+};
+
+static umode_t thinkpad_sysfs_is_visible(struct kobject *kobj,
+                                        struct attribute *attr,
+                                        int idx)
+{
+       bool supported = true;
+
+       return supported ? attr->mode : 0;
+}
+
+static struct attribute_group platform_attribute_group = {
+       .is_visible     = thinkpad_sysfs_is_visible,
+       .attrs          = platform_attributes
+};
+
+static void thinkpad_wmi_sysfs_exit(struct wmi_device *wdev)
+{
+       struct thinkpad_wmi *thinkpad = dev_get_drvdata(&wdev->dev);
+       int i;
+
+       sysfs_remove_group(&wdev->dev.kobj, &platform_attribute_group);
+
+       if (!thinkpad->devattrs)
+               return;
+
+       for (i = 0; i < thinkpad->settings_count; ++i) {
+               struct dev_ext_attribute *deveattr = &thinkpad->devattrs[i];
+               struct device_attribute *devattr = &deveattr->attr;
+
+               if (devattr->attr.name)
+                       device_remove_file(&wdev->dev, devattr);
+       }
+       kfree(thinkpad->devattrs);
+       thinkpad->devattrs = NULL;
+}
+
+static int __init thinkpad_wmi_sysfs_init(struct wmi_device *wdev)
+{
+       struct thinkpad_wmi *thinkpad = dev_get_drvdata(&wdev->dev);
+       struct dev_ext_attribute *devattrs;
+       int count = thinkpad->settings_count;
+       int i, ret;
+
+       devattrs = kmalloc_array(count, sizeof(*devattrs), GFP_KERNEL);
+       if (!devattrs)
+               return -ENOMEM;
+       thinkpad->devattrs = devattrs;
+
+       for (i = 0; i < count; ++i) {
+               struct dev_ext_attribute *deveattr = &devattrs[i];
+               struct device_attribute *devattr = &deveattr->attr;
+
+               sysfs_attr_init(&devattr->attr);
+               devattr->attr.name = thinkpad->settings[i];
+               devattr->attr.mode = 0644;
+               devattr->show = show_setting;
+               devattr->store = store_setting;
+               deveattr->var = (void *)(uintptr_t)i;
+               ret = device_create_file(&wdev->dev, devattr);
+               if (ret) {
+                       /* Name is used to check is file has been created. */
+                       devattr->attr.name = NULL;
+                       return ret;
+               }
+       }
+
+       return sysfs_create_group(&wdev->dev.kobj, &platform_attribute_group);
+}
+
+/*
+ * Platform device
+ */
+static int __init thinkpad_wmi_platform_init(struct thinkpad_wmi *thinkpad)
+{
+       return thinkpad_wmi_sysfs_init(thinkpad->wmi_device);
+}
+
+static void thinkpad_wmi_platform_exit(struct thinkpad_wmi *thinkpad)
+{
+       thinkpad_wmi_sysfs_exit(thinkpad->wmi_device);
+}
+
+/* debugfs */
+
+static ssize_t dbgfs_write_argument(struct file *file,
+                                   const char __user *userbuf,
+                                   size_t count, loff_t *pos)
+{
+       struct thinkpad_wmi *thinkpad = file->f_path.dentry->d_inode->i_private;
+       char *kernbuf = thinkpad->debug.argument;
+       size_t size = sizeof(thinkpad->debug.argument);
+
+       if (count > PAGE_SIZE - 1)
+               return -EINVAL;
+
+       if (count > size - 1)
+               return -EINVAL;
+
+       if (copy_from_user(kernbuf, userbuf, count))
+               return -EFAULT;
+
+       kernbuf[count] = 0;
+
+       strim(kernbuf);
+
+       return count;
+}
+
+static int dbgfs_show_argument(struct seq_file *m, void *v)
+{
+       struct thinkpad_wmi *thinkpad = m->private;
+
+       seq_printf(m, "%s\n", thinkpad->debug.argument);
+       return 0;
+}
+
+static int thinkpad_wmi_debugfs_argument_open(struct inode *inode,
+                                             struct file *file)
+{
+       struct thinkpad_wmi *thinkpad = inode->i_private;
+
+       return single_open(file, dbgfs_show_argument, thinkpad);
+}
+
+static const struct file_operations thinkpad_wmi_debugfs_argument_fops = {
+       .open           = thinkpad_wmi_debugfs_argument_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+       .write          = dbgfs_write_argument,
+};
+
+struct thinkpad_wmi_debugfs_node {
+       struct thinkpad_wmi *thinkpad;
+       char *name;
+       int (*show)(struct seq_file *m, void *data);
+};
+
+static void show_bios_setting_line(struct thinkpad_wmi *thinkpad,
+                                  struct seq_file *m, int i, bool list_valid)
+{
+       int ret;
+       char *settings = NULL, *choices = NULL, *p;
+
+       ret = thinkpad_wmi_bios_setting(i, &settings);
+       if (ret || !settings)
+               return;
+
+       p = strchr(settings, ',');
+       if (p)
+               *p = '=';
+       seq_printf(m, "%s", settings);
+
+
+       if (!thinkpad->can_get_bios_selections)
+               goto line_feed;
+
+       if (p)
+               *p = '\0';
+
+       ret = thinkpad_wmi_get_bios_selections(settings, &choices);
+       if (ret || !choices || !*choices)
+               goto line_feed;
+
+       seq_printf(m, "\t[%s]", choices);
+
+line_feed:
+       kfree(settings);
+       kfree(choices);
+       seq_puts(m, "\n");
+}
+
+static int dbgfs_bios_settings(struct seq_file *m, void *data)
+{
+       struct thinkpad_wmi *thinkpad = m->private;
+       int i;
+
+       for (i = 0; i < thinkpad->settings_count; ++i)
+               show_bios_setting_line(thinkpad, m, i, true);
+
+       return 0;
+}
+
+static int dbgfs_bios_setting(struct seq_file *m, void *data)
+{
+       struct thinkpad_wmi *thinkpad = m->private;
+
+       show_bios_setting_line(m->private, m, thinkpad->debug.instance, false);
+       return 0;
+}
+
+static int dbgfs_list_valid_choices(struct seq_file *m, void *data)
+{
+       struct thinkpad_wmi *thinkpad = m->private;
+       char *choices = NULL;
+       int ret;
+
+       ret = thinkpad_wmi_get_bios_selections(thinkpad->debug.argument,
+                                              &choices);
+
+       if (ret || !choices || !*choices) {
+               kfree(choices);
+               return -EIO;
+       }
+
+       seq_printf(m, "%s\n", choices);
+       kfree(choices);
+       return 0;
+}
+
+static int dbgfs_set_bios_settings(struct seq_file *m, void *data)
+{
+       struct thinkpad_wmi *thinkpad = m->private;
+
+       return thinkpad_wmi_set_bios_settings(thinkpad->debug.argument);
+}
+
+static int dbgfs_save_bios_settings(struct seq_file *m, void *data)
+{
+       struct thinkpad_wmi *thinkpad = m->private;
+
+       return thinkpad_wmi_save_bios_settings(thinkpad->debug.argument);
+}
+
+static int dbgfs_discard_bios_settings(struct seq_file *m, void *data)
+{
+       struct thinkpad_wmi *thinkpad = m->private;
+
+       return thinkpad_wmi_discard_bios_settings(thinkpad->debug.argument);
+}
+
+static int dbgfs_load_default(struct seq_file *m, void *data)
+{
+       struct thinkpad_wmi *thinkpad = m->private;
+
+       return thinkpad_wmi_load_default(thinkpad->debug.argument);
+}
+
+static int dbgfs_set_bios_password(struct seq_file *m, void *data)
+{
+       struct thinkpad_wmi *thinkpad = m->private;
+
+       return thinkpad_wmi_set_bios_password(thinkpad->debug.argument);
+}
+
+static int dbgfs_bios_password_settings(struct seq_file *m, void *data)
+{
+       struct thinkpad_wmi_pcfg pcfg;
+       int ret;
+
+       ret = thinkpad_wmi_password_settings(&pcfg);
+       if (ret)
+               return ret;
+       seq_printf(m, "password_mode:       %#x\n", pcfg.password_mode);
+       seq_printf(m, "password_state:      %#x\n", pcfg.password_state);
+       seq_printf(m, "min_length:          %d\n", pcfg.min_length);
+       seq_printf(m, "max_length:          %d\n", pcfg.max_length);
+       seq_printf(m, "supported_encodings: %#x\n", pcfg.supported_encodings);
+       seq_printf(m, "supported_keyboard:  %#x\n", pcfg.supported_keyboard);
+       return 0;
+}
+
+static struct thinkpad_wmi_debugfs_node thinkpad_wmi_debug_files[] = {
+       { NULL, "bios_settings", dbgfs_bios_settings },
+       { NULL, "bios_setting", dbgfs_bios_setting },
+       { NULL, "list_valid_choices", dbgfs_list_valid_choices },
+       { NULL, "set_bios_settings", dbgfs_set_bios_settings },
+       { NULL, "save_bios_settings", dbgfs_save_bios_settings },
+       { NULL, "discard_bios_settings", dbgfs_discard_bios_settings },
+       { NULL, "load_default", dbgfs_load_default },
+       { NULL, "set_bios_password", dbgfs_set_bios_password },
+       { NULL, "bios_password_settings", dbgfs_bios_password_settings },
+};
+
+static int thinkpad_wmi_debugfs_open(struct inode *inode, struct file *file)
+{
+       struct thinkpad_wmi_debugfs_node *node = inode->i_private;
+
+       return single_open(file, node->show, node->thinkpad);
+}
+
+static const struct file_operations thinkpad_wmi_debugfs_io_ops = {
+       .owner = THIS_MODULE,
+       .open  = thinkpad_wmi_debugfs_open,
+       .read = seq_read,
+       .llseek = seq_lseek,
+       .release = single_release,
+};
+
+static void __init thinkpad_wmi_debugfs_exit(struct thinkpad_wmi *thinkpad)
+{
+       debugfs_remove_recursive(thinkpad->debug.root);
+}
+
+static int thinkpad_wmi_debugfs_init(struct thinkpad_wmi *thinkpad)
+{
+       struct dentry *dent;
+       int i;
+
+       thinkpad->debug.instances_count = thinkpad->settings_count;
+
+       thinkpad->debug.root = debugfs_create_dir(THINKPAD_WMI_FILE, NULL);
+       if (!thinkpad->debug.root) {
+               pr_err("failed to create debugfs directory");
+               goto error_debugfs;
+       }
+
+       dent = debugfs_create_file("argument", 0644,
+                                  thinkpad->debug.root, thinkpad,
+                                  &thinkpad_wmi_debugfs_argument_fops);
+       if (!dent)
+               goto error_debugfs;
+
+       dent = debugfs_create_u8("instance", 0644,
+                                thinkpad->debug.root,
+                                &thinkpad->debug.instance);
+       if (!dent)
+               goto error_debugfs;
+
+       dent = debugfs_create_u8("instances_count", 0444,
+                                thinkpad->debug.root,
+                                &thinkpad->debug.instances_count);
+       if (!dent)
+               goto error_debugfs;
+
+       for (i = 0; i < ARRAY_SIZE(thinkpad_wmi_debug_files); i++) {
+               struct thinkpad_wmi_debugfs_node *node;
+
+               node = &thinkpad_wmi_debug_files[i];
+
+               /* Filter non-present interfaces */
+               if (!strcmp(node->name, "set_bios_settings") &&
+                   !thinkpad->can_set_bios_settings)
+                       continue;
+               if (!strcmp(node->name, "dicard_bios_settings") &&
+                   !thinkpad->can_discard_bios_settings)
+                       continue;
+               if (!strcmp(node->name, "load_default_settings") &&
+                   !thinkpad->can_load_default_settings)
+                       continue;
+               if (!strcmp(node->name, "get_bios_selections") &&
+                   !thinkpad->can_get_bios_selections)
+                       continue;
+               if (!strcmp(node->name, "set_bios_password") &&
+                   !thinkpad->can_set_bios_password)
+                       continue;
+               if (!strcmp(node->name, "bios_password_settings") &&
+                   !thinkpad->can_get_password_settings)
+                       continue;
+
+               node->thinkpad = thinkpad;
+               dent = debugfs_create_file(node->name, S_IFREG | 0444,
+                                          thinkpad->debug.root, node,
+                                          &thinkpad_wmi_debugfs_io_ops);
+               if (!dent) {
+                       pr_err("failed to create debug file: %s\n", node->name);
+                       goto error_debugfs;
+               }
+       }
+
+
+       return 0;
+
+error_debugfs:
+       thinkpad_wmi_debugfs_exit(thinkpad);
+       return -ENOMEM;
+}
+
+/* Base driver */
+static void __init thinkpad_wmi_analyze(struct thinkpad_wmi *thinkpad)
+{
+       acpi_status status;
+       int i = 0;
+
+       /* Try to find the number of valid settings of this machine
+        * and use it to create sysfs attributes.
+        */
+       for (i = 0; i < 0xFF; ++i) {
+               char *item = NULL;
+               char *p;
+
+               status = thinkpad_wmi_bios_setting(i, &item);
+               if (ACPI_FAILURE(status))
+                       break;
+               if (!item || !*item)
+                       break;
+               /* Remove the value part */
+               p = strchr(item, ',');
+               if (p)
+                       *p = '\0';
+               thinkpad->settings[i] = item; /* Cache setting name */
+       }
+
+       thinkpad->settings_count = i;
+       pr_info("Found %d settings", thinkpad->settings_count);
+
+       if (wmi_has_guid(LENOVO_SET_BIOS_SETTINGS_GUID) &&
+           wmi_has_guid(LENOVO_SAVE_BIOS_SETTINGS_GUID)) {
+               thinkpad->can_set_bios_settings = true;
+       }
+
+       if (wmi_has_guid(LENOVO_DISCARD_BIOS_SETTINGS_GUID))
+               thinkpad->can_discard_bios_settings = true;
+
+       if (wmi_has_guid(LENOVO_LOAD_DEFAULT_SETTINGS_GUID))
+               thinkpad->can_load_default_settings = true;
+
+       if (wmi_has_guid(LENOVO_GET_BIOS_SELECTIONS_GUID))
+               thinkpad->can_get_bios_selections = true;
+
+       if (wmi_has_guid(LENOVO_SET_BIOS_PASSWORD_GUID))
+               thinkpad->can_set_bios_password = true;
+
+       if (wmi_has_guid(LENOVO_BIOS_PASSWORD_SETTINGS_GUID))
+               thinkpad->can_get_password_settings = true;
+}
+
+static int __init thinkpad_wmi_add(struct wmi_device *wdev)
+{
+       struct thinkpad_wmi *thinkpad;
+       int err;
+
+       thinkpad = kzalloc(sizeof(struct thinkpad_wmi), GFP_KERNEL);
+       if (!thinkpad)
+               return -ENOMEM;
+
+       thinkpad->wmi_device = wdev;
+       dev_set_drvdata(&wdev->dev, thinkpad);
+
+       thinkpad_wmi_analyze(thinkpad);
+
+       err = thinkpad_wmi_platform_init(thinkpad);
+       if (err)
+               goto error_platform;
+
+       err = thinkpad_wmi_debugfs_init(thinkpad);
+       if (err)
+               goto error_debugfs;
+
+       return 0;
+
+error_debugfs:
+       thinkpad_wmi_platform_exit(thinkpad);
+error_platform:
+       kfree(thinkpad);
+       return err;
+}
+
+static int __exit thinkpad_wmi_remove(struct wmi_device *wdev)
+{
+       struct thinkpad_wmi *thinkpad;
+       int i;
+
+       thinkpad = dev_get_drvdata(&wdev->dev);
+       thinkpad_wmi_debugfs_exit(thinkpad);
+       thinkpad_wmi_platform_exit(thinkpad);
+
+       for (i = 0; thinkpad->settings[i]; ++i) {
+               kfree(thinkpad->settings[i]);
+               thinkpad->settings[i] = NULL;
+       }
+
+       kfree(thinkpad);
+       return 0;
+}
+
+static int __init thinkpad_wmi_probe(struct wmi_device *wdev)
+{
+       return thinkpad_wmi_add(wdev);
+}
+
+static const struct wmi_device_id thinkpad_wmi_id_table[] = {
+       // Search for Lenovo_BiosSetting
+       { .guid_string = LENOVO_BIOS_SETTING_GUID },
+       { },
+};
+
+static struct wmi_driver thinkpad_wmi_driver = {
+       .driver = {
+               .name = "thinkpad-wmi",
+       },
+       .id_table = thinkpad_wmi_id_table,
+       .probe = thinkpad_wmi_probe,
+       .remove = thinkpad_wmi_remove,
+};
+
+static int __init thinkpad_wmi_init(void)
+{
+       return wmi_driver_register(&thinkpad_wmi_driver);
+}
+
+static void __exit thinkpad_wmi_exit(void)
+{
+       wmi_driver_unregister(&thinkpad_wmi_driver);
+}
+
+module_init(thinkpad_wmi_init);
+module_exit(thinkpad_wmi_exit);
-- 
2.14.1

Reply via email to