diff mbox

[1/2] Add shuttle-wmi driver

Message ID 1295376114-5685-1-git-send-email-herton@mandriva.com.br (mailing list archive)
State New, archived
Headers show

Commit Message

Herton Ronaldo Krzesinski Jan. 18, 2011, 6:41 p.m. UTC
None
diff mbox

Patch

diff --git a/Documentation/ABI/testing/sysfs-platform-shuttle-wmi b/Documentation/ABI/testing/sysfs-platform-shuttle-wmi
new file mode 100644
index 0000000..787e16b
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-shuttle-wmi
@@ -0,0 +1,72 @@ 
+What:		/sys/devices/platform/shuttle_wmi/lcd_auto_adjust
+Date:		December 2010
+KernelVersion:	2.6.39
+Contact:	"Herton Ronaldo Krzesinski" <herton@mandriva.com.br>
+Description:
+		This is a write only option (accepts any single value, eg.
+		"echo 1 > lcd_auto_adjust") that starts LCD auto-adjust
+		function, if the machine has this function enabled. Some
+		shuttle machines have LCD attached to analog VGA connector,
+		so uses/needs auto-adjust.
+
+What:		/sys/devices/platform/shuttle_wmi/model_name
+Date:		December 2010
+KernelVersion:	2.6.39
+Contact:	"Herton Ronaldo Krzesinski" <herton@mandriva.com.br>
+Description:
+		This is a read only attribute which outputs a string with model
+		name of the machine. When shuttle-wmi can't determine which
+		model it is, "Unknown" is returned. Otherwise, the possible
+		models are "Shuttle MA", "Shuttle DA18IE", "Shuttle DA18IM",
+		"Shuttle X50 V2", "Positivo A14IE01", "Positivo P13",
+		"Positivo P14".
+
+What:		/sys/devices/platform/shuttle_wmi/panel_set_default
+Date:		December 2010
+KernelVersion:	2.6.39
+Contact:	"Herton Ronaldo Krzesinski" <herton@mandriva.com.br>
+Description:
+		This is a write only option (accepts any single value, eg.
+		"echo 1 > panel_set_default"). Probably resets panel/lcd to
+		default configuration, function not explained in shuttle wmi
+		documentation. It also starts an auto adjust and color adjust
+		cycle. The function should only work in shuttle machines with
+		LCD attached to an analog VGA connector.
+
+What:		/sys/devices/platform/shuttle_wmi/powersave
+Date:		December 2010
+KernelVersion:	2.6.39
+Contact:	"Herton Ronaldo Krzesinski" <herton@mandriva.com.br>
+Description:
+		Control powersave state. 1 means on, 0 means off.
+		When enabled, it basically forces the cpu to stay on powersave
+		state (only works if cpu has P-states support, it is similar to
+		powersave governor in cpufreq) when machine is only running on
+		battery. If not running on battery, this function isn't expected
+		to work, any attempt to enable this returns -EIO.
+
+What:		/sys/devices/platform/shuttle_wmi/touchpad_off
+Date:		December 2010
+KernelVersion:	2.6.39
+Contact:	"Herton Ronaldo Krzesinski" <herton@mandriva.com.br>
+Description:
+		Control touchpad state. 1 means off, 0 means on.
+
+What:		/sys/devices/platform/shuttle_wmi/webcam
+Date:		December 2010
+KernelVersion:	2.6.39
+Contact:	"Herton Ronaldo Krzesinski" <herton@mandriva.com.br>
+Description:
+		Control webcam state. 1 means on, 0 means off.
+
+What:		/sys/devices/platform/shuttle_wmi/white_balance
+Date:		December 2010
+KernelVersion:	2.6.39
+Contact:	"Herton Ronaldo Krzesinski" <herton@mandriva.com.br>
+Description:
+		This is a write only option (accepts any single value, eg.
+		"echo 1 > white_balance"). Probably triggers an automatic
+		white balance adjustment for lcd, function not explained in
+		shuttle	wmi documentation. It also starts an auto adjust and
+		color adjust cycle. The function should only work in shuttle
+		machines with LCD attached to an analog VGA connector.
diff --git a/MAINTAINERS b/MAINTAINERS
index 9a78175..457b696 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5445,6 +5445,12 @@  F:	drivers/serial/serial_lh7a40x.c
 F:	drivers/usb/gadget/lh7a40*
 F:	drivers/usb/host/ohci-lh7a40*
 
+SHUTTLE WMI EXTRAS DRIVER
+M:	Herton Ronaldo Krzesinski <herton@mandriva.com.br>
+L:	platform-driver-x86@vger.kernel.org
+S:	Maintained
+F:	drivers/platform/x86/shuttle-wmi.c
+
 SIMPLE FIRMWARE INTERFACE (SFI)
 M:	Len Brown <lenb@kernel.org>
 L:	sfi-devel@simplefirmware.org
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index d163bc2..b81ebc4 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -654,4 +654,19 @@  config XO1_RFKILL
 	  Support for enabling/disabling the WLAN interface on the OLPC XO-1
 	  laptop.
 
+config SHUTTLE_WMI
+	tristate "Shuttle WMI Extras Driver"
+	depends on ACPI_WMI
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on RFKILL
+	depends on INPUT
+	select INPUT_SPARSEKMAP
+	---help---
+	  This is a driver for the WMI interface present on some Shuttle
+	  machines. It adds controls for wireless, bluetooth and 3g radios,
+	  webcam switch, backlight controls, among others.
+
+	  If you have a Shuttle machine with ACPI-WMI interface say Y or M
+	  here.
+
 endif # X86_PLATFORM_DEVICES
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 4ec4ff8..5fb4ed6 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -34,3 +34,4 @@  obj-$(CONFIG_INTEL_IPS)		+= intel_ips.o
 obj-$(CONFIG_GPIO_INTEL_PMIC)	+= intel_pmic_gpio.o
 obj-$(CONFIG_XO1_RFKILL)	+= xo1-rfkill.o
 obj-$(CONFIG_IBM_RTL)		+= ibm_rtl.o
+obj-$(CONFIG_SHUTTLE_WMI)	+= shuttle-wmi.o
diff --git a/drivers/platform/x86/shuttle-wmi.c b/drivers/platform/x86/shuttle-wmi.c
new file mode 100644
index 0000000..59e2d2f
--- /dev/null
+++ b/drivers/platform/x86/shuttle-wmi.c
@@ -0,0 +1,1297 @@ 
+/*
+ * ACPI-WMI driver for Shuttle WMI interface
+ *
+ * Copyright (c) 2010 Herton Ronaldo Krzesinski <herton@mandriva.com.br>
+ *
+ * Development of this driver was funded by Positivo Informatica S.A.
+ * Parts of the driver were based on some WMI documentation provided by Shuttle
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/dmi.h>
+#include <linux/acpi.h>
+#include <linux/platform_device.h>
+#include <linux/debugfs.h>
+#include <linux/rfkill.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+
+MODULE_AUTHOR("Herton Ronaldo Krzesinski");
+MODULE_DESCRIPTION("Shuttle WMI Extras Driver");
+MODULE_LICENSE("GPL");
+
+#define SHUTTLE_WMI_SETGET_GUID "abbc0f6f-8ea1-11d1-00a0-c90629100000"
+#define SHUTTLE_WMI_EVENT_GUID "abbc0f72-8ea1-11d1-00a0-c90629100000"
+MODULE_ALIAS("wmi:"SHUTTLE_WMI_SETGET_GUID);
+MODULE_ALIAS("wmi:"SHUTTLE_WMI_EVENT_GUID);
+
+#define CMD_WRITEEC     0x00
+#define CMD_READEC      0x01
+#define CMD_SCMD        0x02
+#define CMD_INT15       0x03
+#define CMD_HWSW        0x07
+#define CMD_LCTRL       0x09
+#define CMD_CUTLVDS     0x11
+#define CMD_MA          0x18
+#define CMD_DA18IE      0x19
+#define CMD_DA18IM      0x20
+
+#define ECRAM_ER0       0x443
+#define ECRAM_ER1       0x45a
+#define ECRAM_ER2       0x47b
+#define ECRAM_ER3       0x758
+#define ECRAM_ER4       0x759
+
+struct shuttle_ecram {
+	unsigned short addr;
+	u32 mask;
+};
+
+struct shuttle_state {
+	struct shuttle_ecram ecram;
+	struct device_attribute *dev_attr;
+};
+
+static struct shuttle_state state_powersave = {
+	.ecram = {
+		.addr = ECRAM_ER3,
+		.mask = 0x10,
+	},
+};
+
+static struct shuttle_state state_touchpad_off = {
+	.ecram = {
+		.addr = ECRAM_ER2,
+		.mask = 0x02,
+	},
+};
+
+static struct shuttle_state state_webcam = {
+	.ecram = {
+		.addr = ECRAM_ER2,
+		.mask = 0x10,
+	},
+};
+
+struct shuttle_rfkill {
+	struct rfkill *rfk;
+	enum rfkill_type type;
+	struct shuttle_ecram ecram_state;
+	struct shuttle_ecram ecram_present;
+	/* lists of rf state switch notification codes */
+	u32 rf_on[3];
+	u32 rf_off[3];
+};
+
+static struct shuttle_rfkill srfk_3g = {
+	.type = RFKILL_TYPE_WWAN,
+	.ecram_state = {
+		.addr = ECRAM_ER2,
+		.mask = 0x40,
+	},
+	.rf_on = { 0x10, 0x29 },
+	.rf_off = { 0x11, 0x2a },
+};
+
+static struct shuttle_rfkill srfk_bluetooth = {
+	.type = RFKILL_TYPE_BLUETOOTH,
+	.ecram_state = {
+		.addr = ECRAM_ER2,
+		.mask = 0x20,
+	},
+	.rf_on = { 0x0c, 0x29 },
+	.rf_off = { 0x0d, 0x2a },
+};
+
+static struct shuttle_rfkill srfk_wlan = {
+	.type = RFKILL_TYPE_WLAN,
+	.ecram_state = {
+		.addr = ECRAM_ER2,
+		.mask = 0x80,
+	},
+	.ecram_present = {
+		.addr = ECRAM_ER1,
+		.mask = 0x80,
+	},
+	.rf_on = { 0x08 },
+	.rf_off = { 0x09 },
+};
+
+enum fn_type {
+	FN_CMD,
+	FN_CMD_DEBUG,
+	FN_RFKILL,
+	FN_STATE
+};
+
+struct shuttle_fn_map {
+	char *name;
+	enum fn_type type;
+	unsigned short cmd;
+	unsigned short arg;
+	unsigned short fn;
+	void *data;
+};
+
+static struct shuttle_fn_map unknown_fn_map[] = {
+	{ "fn_f1", FN_CMD_DEBUG, CMD_SCMD, 0, 0x01, NULL },
+	{ "fn_f2", FN_CMD_DEBUG, CMD_SCMD, 0, 0x02, NULL },
+	{ "fn_f3", FN_CMD_DEBUG, CMD_SCMD, 0, 0x03, NULL },
+	{ "fn_f4", FN_CMD_DEBUG, CMD_SCMD, 0, 0x04, NULL },
+	{ "fn_f5", FN_CMD_DEBUG, CMD_SCMD, 0, 0x05, NULL },
+	{ "fn_f6", FN_CMD_DEBUG, CMD_SCMD, 0, 0x06, NULL },
+	{ "fn_f7", FN_CMD_DEBUG, CMD_SCMD, 0, 0x07, NULL },
+	{ "fn_f8", FN_CMD_DEBUG, CMD_SCMD, 0, 0x08, NULL },
+	{ "fn_f9", FN_CMD_DEBUG, CMD_SCMD, 0, 0x09, NULL },
+	{ "fn_f10", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0a, NULL },
+	{ "fn_f11", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0b, NULL },
+	{ "fn_f12", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0c, NULL },
+	{ "fn_f13", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0d, NULL },
+	{ "fn_f14", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0e, NULL },
+	{ "fn_f15", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0f, NULL },
+	{ "lcd_auto_adjust", FN_CMD, CMD_SCMD, 0, 0x81, NULL },
+	{ "lightbar_brightness_down", FN_CMD_DEBUG, CMD_LCTRL, 1, 0x00, NULL },
+	{ "lightbar_brightness_up", FN_CMD_DEBUG, CMD_LCTRL, 1, 0x01, NULL },
+	{ "panel_set_default", FN_CMD, CMD_SCMD, 0, 0x83, NULL },
+	{ "video_output_on", FN_CMD_DEBUG, CMD_CUTLVDS, 0, 0x01, NULL},
+	{ "video_output_off", FN_CMD_DEBUG, CMD_CUTLVDS, 0, 0x00, NULL},
+	{ "white_balance", FN_CMD, CMD_SCMD, 0, 0x82, NULL },
+	{ }
+};
+
+static struct shuttle_fn_map fn_map_1[] = {
+	{ "brightness_down", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0b, NULL },
+	{ "brightness_up", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0c, NULL },
+	{ "lcd_auto_adjust", FN_CMD, CMD_SCMD, 0, 0x81, NULL },
+	{ "lightbar_brightness_down", FN_CMD_DEBUG, CMD_LCTRL, 1, 0x00, NULL },
+	{ "lightbar_brightness_up", FN_CMD_DEBUG, CMD_LCTRL, 1, 0x01, NULL },
+	{ "panel_set_default", FN_CMD, CMD_SCMD, 0, 0x83, NULL },
+	{ "powersave", FN_STATE, CMD_SCMD, 0, 0x02, &state_powersave },
+	{ "shuttle_3g", FN_RFKILL, CMD_SCMD, 0, 0x05, &srfk_3g },
+	{ "shuttle_bluetooth", FN_RFKILL, CMD_SCMD, 0, 0x0d, &srfk_bluetooth },
+	{ "shuttle_wlan", FN_RFKILL, CMD_SCMD, 0, 0x04, &srfk_wlan },
+	{ "sleep", FN_CMD_DEBUG, CMD_SCMD, 0, 0x01, NULL },
+	{ "sound_mute", FN_CMD_DEBUG, CMD_SCMD, 0, 0x08, NULL },
+	{ "switch_video", FN_CMD_DEBUG, CMD_SCMD, 0, 0x03, NULL },
+	{ "touchpad_off", FN_STATE, CMD_SCMD, 0, 0x06, &state_touchpad_off },
+	{ "volume_down", FN_CMD_DEBUG, CMD_SCMD, 0, 0x09, NULL },
+	{ "volume_up", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0a, NULL },
+	{ "video_output_on", FN_CMD_DEBUG, CMD_CUTLVDS, 0, 0x01, NULL},
+	{ "video_output_off", FN_CMD_DEBUG, CMD_CUTLVDS, 0, 0x00, NULL},
+	{ "webcam", FN_STATE, CMD_SCMD, 0, 0x07, &state_webcam },
+	{ "white_balance", FN_CMD, CMD_SCMD, 0, 0x82, NULL },
+	{ }
+};
+
+static struct shuttle_fn_map fn_map_2[] = {
+	{ "brightness_down", FN_CMD_DEBUG, CMD_SCMD, 0, 0x08, NULL },
+	{ "brightness_up", FN_CMD_DEBUG, CMD_SCMD, 0, 0x09, NULL },
+	{ "video_output", FN_CMD_DEBUG, CMD_SCMD, 0, 0x02, NULL },
+	{ "video_output_on", FN_CMD_DEBUG, CMD_CUTLVDS, 0, 0x01, NULL},
+	{ "video_output_off", FN_CMD_DEBUG, CMD_CUTLVDS, 0, 0x00, NULL},
+	{ "shuttle_wlan", FN_RFKILL, CMD_SCMD, 0, 0x0b, &srfk_wlan },
+	{ "sleep", FN_CMD_DEBUG, CMD_SCMD, 0, 0x03, NULL },
+	{ "sound_mute", FN_CMD_DEBUG, CMD_SCMD, 0, 0x04, NULL },
+	{ "switch_video", FN_CMD_DEBUG, CMD_SCMD, 0, 0x07, NULL },
+	{ "touchpad_off", FN_STATE, CMD_SCMD, 0, 0x01, &state_touchpad_off },
+	{ "volume_down", FN_CMD_DEBUG, CMD_SCMD, 0, 0x05, NULL },
+	{ "volume_up", FN_CMD_DEBUG, CMD_SCMD, 0, 0x06, NULL },
+	{ "webcam", FN_STATE, CMD_SCMD, 0, 0x0a, &state_webcam },
+	{ }
+};
+
+struct shuttle_backlight {
+	u8 ec_addr;
+	struct shuttle_fn_map *fn_bl_down;
+	struct shuttle_fn_map *fn_bl_up;
+};
+
+static struct shuttle_backlight common_bl_desc = {
+	.ec_addr = 0x79,
+};
+
+static struct shuttle_backlight quirk_bl_desc = {
+	.ec_addr = 0x79,
+	.fn_bl_down = &fn_map_1[0],
+	.fn_bl_up = &fn_map_1[1],
+};
+
+struct shuttle_id {
+	unsigned char cmd_id;
+	const char *model_name;
+	struct shuttle_backlight *bl_desc;
+	struct shuttle_fn_map *fn_map;
+};
+
+static struct shuttle_id shuttle_ids[] = {
+	{ CMD_MA, "Shuttle MA", &common_bl_desc, fn_map_1 },
+	{ CMD_DA18IE, "Shuttle DA18IE", &quirk_bl_desc, fn_map_1 },
+	{ CMD_DA18IM, "Shuttle DA18IM", &common_bl_desc, fn_map_1 }
+};
+
+static struct shuttle_id id_unknown = {
+	.model_name = "Unknown",
+	.fn_map = unknown_fn_map,
+};
+
+static struct shuttle_id shuttle_dmi_id;
+
+static int shuttle_dmi_matched(const struct dmi_system_id *dmi)
+{
+	shuttle_dmi_id.model_name = dmi->ident;
+	shuttle_dmi_id.bl_desc = &common_bl_desc;
+	shuttle_dmi_id.fn_map = dmi->driver_data;
+	return 1;
+}
+
+static struct dmi_system_id shuttle_dmi_ids[] = {
+	{
+		.callback = shuttle_dmi_matched,
+		.ident = "Shuttle X50 V2",
+		.matches = {
+			DMI_MATCH(DMI_PRODUCT_NAME, "X50-V2"),
+		},
+		.driver_data = fn_map_1,
+	},
+	{
+		.callback = shuttle_dmi_matched,
+		.ident = "Positivo A14IE01",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "POSITIVO"),
+			DMI_MATCH(DMI_BOARD_NAME, "A14IE01"),
+		},
+		.driver_data = fn_map_2,
+	},
+	{
+		.callback = shuttle_dmi_matched,
+		.ident = "Positivo P13",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "POSITIVO"),
+			DMI_MATCH(DMI_BOARD_NAME, "P13"),
+		},
+		.driver_data = fn_map_2,
+	},
+	{
+		.callback = shuttle_dmi_matched,
+		.ident = "Positivo P14",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "POSITIVO"),
+			DMI_MATCH(DMI_BOARD_NAME, "P14"),
+		},
+		.driver_data = fn_map_2,
+	},
+	{}
+};
+
+static struct dmi_system_id __devinitdata shuttle_quirk_bl_dmi_ids[] = {
+	{
+		.ident = "Positivo M13",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "POSITIVO"),
+			DMI_MATCH(DMI_BOARD_NAME, "M13"),
+		},
+	},
+	{
+		.ident = "Positivo M14",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "POSITIVO"),
+			DMI_MATCH(DMI_BOARD_NAME, "M14"),
+		},
+	},
+	{
+		.ident = "Positivo A14IM01",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "POSITIVO"),
+			DMI_MATCH(DMI_BOARD_NAME, "A14IM01"),
+		},
+	},
+	{
+		.ident = "Positivo J14IM21",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "POSITIVO"),
+			DMI_MATCH(DMI_BOARD_NAME, "J14IM21"),
+		},
+	},
+	{}
+};
+
+struct shuttle_wmi {
+	struct platform_device *pdev;
+	struct shuttle_id *id;
+	struct dentry *dbg_root;
+	struct attribute_group *attr_group;
+	struct input_dev *inputdev;
+	struct backlight_device *bd;
+};
+
+struct shuttle_cmd {
+	u16 param2;
+	u16 param1;
+	u8 arg;
+	u8 cmd;
+	u16 hdr;
+};
+
+static int wmi_setget_mtd(struct shuttle_cmd *scmd, u32 *res)
+{
+	acpi_status status;
+	union acpi_object *obj;
+	struct acpi_buffer input;
+	static DEFINE_MUTEX(mtd_lock);
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+
+	input.length = sizeof(struct shuttle_cmd);
+	scmd->hdr = 0xec00;
+	input.pointer = (u8 *) scmd;
+
+	/* We must serialize access to wmi_evaluate_method: the wmi interface
+	 * functions in the bios save its parameters on a common shared buffer,
+	 * which gets overwritten on parallel calls with unpredicted results;
+	 * AML code doesn't have any locking, so we must do this here */
+	mutex_lock(&mtd_lock);
+	status = wmi_evaluate_method(SHUTTLE_WMI_SETGET_GUID, 0, 2,
+				     &input, &output);
+	mutex_unlock(&mtd_lock);
+	if (ACPI_FAILURE(status))
+		return -1;
+
+	obj = output.pointer;
+	if (obj) {
+		if (obj->type == ACPI_TYPE_INTEGER) {
+			if (res)
+				*res = obj->integer.value;
+		} else {
+			pr_err("Unsupported object returned (%s)", __func__);
+			res = NULL;
+		}
+		kfree(obj);
+	} else {
+		if (res) {
+			pr_warning("No result from WMI method (%s)", __func__);
+			res = NULL;
+		}
+	}
+
+	return (res) ? 0 : 1;
+}
+
+static int wmi_ec_cmd(unsigned char cmd, unsigned char arg,
+		      unsigned short param1, unsigned short param2,
+		      u32 *res)
+{
+	struct shuttle_cmd scmd = {
+		.cmd = cmd,
+		.arg = arg,
+		.param1 = param1,
+		.param2 = param2
+	};
+
+	return wmi_setget_mtd(&scmd, res);
+}
+
+static int wmi_ec_state(struct shuttle_ecram *ecram)
+{
+	u32 val;
+
+	if (wmi_ec_cmd(CMD_READEC, 0, 0, ecram->addr, &val))
+		return -EIO;
+	return (val & ecram->mask) ? 1 : 0;
+}
+
+static int rfkill_common_set_block(void *data, bool blocked)
+{
+	int sw;
+	struct shuttle_fn_map *fn_map = data;
+	struct shuttle_rfkill *srfk = fn_map->data;
+
+	sw = wmi_ec_state(&srfk->ecram_state);
+	if (sw < 0)
+		return sw;
+
+	if (blocked == sw)
+		wmi_ec_cmd(fn_map->cmd, fn_map->arg, 0, fn_map->fn, NULL);
+	else
+		return 0;
+
+	sw = wmi_ec_state(&srfk->ecram_state);
+	if (sw < 0)
+		return sw;
+
+	return (sw != blocked) ? 0 : -EIO;
+}
+
+static const struct rfkill_ops rfkill_common_ops = {
+	.set_block = rfkill_common_set_block,
+};
+
+static void pr_possible_dev_state(void)
+{
+	static bool pr;
+	u32 val;
+
+	if (!pr) {
+		pr = true;
+		pr_info("need to unblock some rfkills to check device"
+			" presence\n");
+		if (!wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER1, &val))
+			pr_info("possible device present state at address"
+				" 0x%04x is 0x%08x\n", ECRAM_ER1, val);
+	}
+}
+
+static int shuttle_rfkill_init(struct shuttle_fn_map *fn_map,
+			       struct device *dev)
+{
+	int rc;
+	struct shuttle_rfkill *srfk = fn_map->data;
+
+	/* Try to detect if device controlled by this rfkill is present, to
+	 * avoid having an rfkill switch when not needed */
+	if (srfk->ecram_present.mask && srfk->ecram_present.addr) {
+		/* we have an address to read to check if device is present */
+		rc = wmi_ec_state(&srfk->ecram_present);
+		if (rc <= 0)
+			return rc;
+
+		rc = wmi_ec_state(&srfk->ecram_state);
+		if (rc < 0)
+			return rc;
+	} else {
+		/* print only once the possible value of devices presence, for
+		 * extra information (useful to check if really device presence
+		 * is or isn't available at usual ECRAM_ER1 address) */
+		pr_possible_dev_state();
+
+		/* no address/mask to check, detect if device is available by
+		 * trying to enable it, in case it's disabled */
+		rc = wmi_ec_state(&srfk->ecram_state);
+		if (rc < 0)
+			return rc;
+		if (!rc) {
+			if (rfkill_common_set_block(fn_map, false))
+				return 0;
+
+			/* after check, reset to initial setting; should be
+			 * unlikely this returns with error, but really check if
+			 * we could reset to initial blocked setting, otherwise
+			 * don't make it a fatal error and assume rfkill not
+			 * blocked */
+			if (rfkill_common_set_block(fn_map, true))
+				rc = 1;
+		}
+	}
+
+	srfk->rfk = rfkill_alloc(fn_map->name, dev, srfk->type,
+				 &rfkill_common_ops, fn_map);
+	if (!srfk->rfk)
+		return -ENOMEM;
+
+	rfkill_init_sw_state(srfk->rfk, !rc);
+
+	rc = rfkill_register(srfk->rfk);
+	if (rc) {
+		rfkill_destroy(srfk->rfk);
+		srfk->rfk = NULL;
+		return rc;
+	}
+
+	return 0;
+}
+
+static void shuttle_rfkill_remove(struct shuttle_fn_map *fn_map)
+{
+	struct shuttle_rfkill *srfk = fn_map->data;
+
+	if (srfk->rfk) {
+		rfkill_unregister(srfk->rfk);
+		rfkill_destroy(srfk->rfk);
+		srfk->rfk = NULL;
+	}
+}
+
+static int shuttle_rfkill_resume(struct device *dev)
+{
+	struct shuttle_fn_map *fn_map;
+	struct shuttle_rfkill *srfk;
+	int rc;
+	struct platform_device *pdev = to_platform_device(dev);
+	struct shuttle_wmi *priv = platform_get_drvdata(pdev);
+
+	for (fn_map = priv->id->fn_map; fn_map->name; fn_map++) {
+		if (fn_map->type != FN_RFKILL)
+			continue;
+
+		srfk = fn_map->data;
+		if (srfk->rfk) {
+			rc = wmi_ec_state(&srfk->ecram_state);
+			if (rc < 0)
+				return rc;
+			rfkill_set_sw_state(srfk->rfk, rc);
+		}
+	}
+	return 0;
+}
+
+static bool set_rfkill_sw(u32 *list, u32 code, struct rfkill *rfk, bool blocked)
+{
+	while (*list) {
+		if (*list == code) {
+			rfkill_set_sw_state(rfk, blocked);
+			return true;
+		}
+		list++;
+	}
+	return false;
+}
+
+static bool notify_switch_rfkill(struct shuttle_wmi *priv, u32 code)
+{
+	struct shuttle_fn_map *fn_map;
+	struct shuttle_rfkill *srfk;
+	struct rfkill *rfk;
+	bool res = false;
+
+	for (fn_map = priv->id->fn_map; fn_map->name; fn_map++) {
+		if (fn_map->type != FN_RFKILL)
+			continue;
+
+		srfk = fn_map->data;
+		rfk = srfk->rfk;
+		if (!rfk)
+			continue;
+
+		/* check if notification code means radio turned on, looking
+		 * at list_on array of "on" notification codes for this rfkill;
+		 * if code is in this list, we notify rfkill core (set_rfkill_sw
+		 * does the check, notification and returns true) and we can
+		 * skip to next rfkill on the list (some notification codes are
+		 * shared, firmware may want to turn on two radios at same time,
+		 * so we must check all rfkills with this code) */
+		if (set_rfkill_sw(srfk->rf_on, code, rfk, false)) {
+			res = true;
+			continue;
+		}
+
+		/* same as above, but we check the list_off to see if
+		 * notification code means radio turned off */
+		if (set_rfkill_sw(srfk->rf_off, code, rfk, true))
+			res = true;
+	}
+	/* if we found that notification code was indeed a radio on/off event,
+	 * return true here */
+	return res;
+}
+
+static bool notify_switch_attr(struct platform_device *pdev, u32 code)
+{
+	int i;
+	struct shuttle_switch {
+		u32 switch_on;
+		u32 switch_off;
+		char *sys_attr;
+	};
+	static const struct shuttle_switch codes[] = {
+		{ 0x04, 0x05, "touchpad_off" },
+		{ 0x12, 0x13, "webcam" },
+		{ 0x31, 0x32, "powersave" }
+	};
+
+	for (i = 0; i < ARRAY_SIZE(codes); i++) {
+		if (codes[i].switch_on == code || codes[i].switch_off == code) {
+			sysfs_notify(&pdev->dev.kobj, NULL, codes[i].sys_attr);
+			return true;
+		}
+	}
+	return false;
+}
+
+static void shuttle_wmi_notify(u32 value, void *data)
+{
+	acpi_status status;
+	union acpi_object *obj;
+	u8 type;
+	u32 code;
+	struct acpi_buffer res = { ACPI_ALLOCATE_BUFFER, NULL };
+	struct shuttle_wmi *priv = data;
+
+	status = wmi_get_event_data(value, &res);
+	if (status != AE_OK) {
+		pr_warning("unable to retrieve wmi event status"
+			   " (error=0x%x)\n", status);
+		return;
+	}
+
+	obj = (union acpi_object *) res.pointer;
+	if (!obj)
+		return;
+	if (obj->type != ACPI_TYPE_INTEGER) {
+		pr_info("unknown object returned in wmi event\n");
+		goto notify_exit;
+	}
+
+	type = (obj->integer.value >> 24) & 0xFF;
+	switch (type) {
+	case 0: /* OSD/Scancode event */
+		code = obj->integer.value & 0xFFFFFF;
+
+		/* update rfkill switches */
+		if (notify_switch_rfkill(priv, code))
+			break;
+
+		/* send notification on state switch attributes */
+		if (notify_switch_attr(priv->pdev, code))
+			break;
+
+		if (priv->bd && (code == 0x14 || code == 0x15)) {
+			backlight_force_update(priv->bd,
+					       BACKLIGHT_UPDATE_HOTKEY);
+			break;
+		}
+
+		if (!sparse_keymap_report_event(priv->inputdev, code, 1, true))
+			pr_info("unhandled scancode (0x%06x)\n", code);
+		break;
+	case 1: /* Power management event */
+		/* Events not used.
+		 * Possible values for obj->integer.value:
+		 * 0x01000000 - silent mode
+		 * 0x01010000 - brightness sync */
+	case 2: /* i-PowerXross event */
+		/* i-PowerXross is a overclocking feature, not
+		 * implemented, there are no further details, possible
+		 * values for obj->integer.value in documentation:
+		 * 0x02000000 - idle mode
+		 * 0x02010000 - action mode
+		 * 0x02020000 - entry s3 */
+		break;
+	case 0xec: /* Lost event */
+		if (printk_ratelimit())
+			pr_warning("lost event because of buggy BIOS");
+		break;
+	default:
+		pr_info("unknown wmi notification type (0x%02x)\n", type);
+	}
+
+notify_exit:
+	kfree(obj);
+}
+
+static const struct key_entry shuttle_wmi_keymap[] = {
+	{ KE_IGNORE, 0x14, { KEY_BRIGHTNESSUP } },
+	{ KE_IGNORE, 0x15, { KEY_BRIGHTNESSDOWN } },
+	{ KE_KEY, 0x16, { KEY_FASTFORWARD } },
+	{ KE_KEY, 0x17, { KEY_REWIND } },
+	{ KE_KEY, 0x18, { KEY_F13 } }, /* OSD Beep */
+	{ KE_KEY, 0x2b, { KEY_F14 } }, /* OSD menu 1 */
+	{ KE_KEY, 0x2c, { KEY_F15 } }, /* OSD menu 2 */
+	{ KE_KEY, 0x2d, { KEY_F16 } }, /* OSD menu 3 */
+	{ KE_KEY, 0x2e, { KEY_F17 } }, /* OSD menu 4 */
+	{ KE_KEY, 0x33, { KEY_F18 } }, /* Update OSD bar status */
+	{ KE_KEY, 0x90, { KEY_WWW } },
+	{ KE_KEY, 0x95, { KEY_PREVIOUSSONG } },
+	{ KE_KEY, 0xa0, { KEY_PROG1 } }, /* Call OSD software */
+	{ KE_KEY, 0xa1, { KEY_VOLUMEDOWN } },
+	{ KE_KEY, 0xa3, { KEY_MUTE } },
+	{ KE_KEY, 0xb2, { KEY_VOLUMEUP } },
+	{ KE_KEY, 0xb4, { KEY_PLAYPAUSE } },
+	{ KE_KEY, 0xbb, { KEY_STOPCD } },
+	{ KE_KEY, 0xc8, { KEY_MAIL } },
+	{ KE_KEY, 0xcd, { KEY_NEXTSONG } },
+	{ KE_KEY, 0xd0, { KEY_MEDIA } },
+
+	/* Known non hotkey events don't handled, that we don't care or
+	 * which we must ignore */
+	{ KE_IGNORE, 0x01, }, /* Caps Lock toggled */
+	{ KE_IGNORE, 0x02, }, /* Num Lock toggled */
+	{ KE_IGNORE, 0x03, }, /* Scroll Lock toggled */
+	{ KE_IGNORE, 0x06, }, /* Downclock/Silent on */
+	{ KE_IGNORE, 0x07, }, /* Downclock/Silent off */
+	{ KE_IGNORE, 0x0a, }, /* WiMax on */
+	{ KE_IGNORE, 0x0b, }, /* WiMax off */
+	{ KE_IGNORE, 0x0e, }, /* RF on */
+	{ KE_IGNORE, 0x0f, }, /* RF off */
+	{ KE_IGNORE, 0x1a, }, /* Auto Brightness on */
+	{ KE_IGNORE, 0x1b, }, /* Auto Brightness off */
+	{ KE_IGNORE, 0x1c, }, /* Auto-KB Brightness on */
+	{ KE_IGNORE, 0x1d, }, /* Auto-KB Brightness off */
+	{ KE_IGNORE, 0x1e, }, /* Light Bar Brightness up */
+	{ KE_IGNORE, 0x1f, }, /* Light Bar Brightness down */
+	{ KE_IGNORE, 0x20, }, /* China Telecom AP enable */
+	{ KE_IGNORE, 0x21, }, /* China Mobile AP enable */
+	{ KE_IGNORE, 0x22, }, /* Huawei AP enable */
+	{ KE_IGNORE, 0x23, }, /* Docking in */
+	{ KE_IGNORE, 0x24, }, /* Docking out */
+	{ KE_IGNORE, 0x25, }, /* Device no function */
+	{ KE_IGNORE, 0x26, }, /* i-PowerXross OverClocking */
+	{ KE_IGNORE, 0x27, }, /* i-PowerXross PowerSaving */
+	{ KE_IGNORE, 0x28, }, /* i-PowerXross off */
+	{ KE_IGNORE, 0x2f, }, /* Optimus on */
+	{ KE_IGNORE, 0x30, }, /* Optimus off */
+	{ KE_IGNORE, 0x91, }, /* ICO 2 on */
+	{ KE_IGNORE, 0x92, }, /* ICO 2 off */
+
+	{ KE_END, 0 }
+};
+
+static int shuttle_wmi_input_init(struct shuttle_wmi *priv)
+{
+	struct input_dev *input;
+	int rc;
+
+	input = input_allocate_device();
+	if (!input)
+		return -ENOMEM;
+
+	input->name = "Shuttle WMI hotkeys";
+	input->phys = KBUILD_MODNAME "/input0";
+	input->id.bustype = BUS_HOST;
+
+	rc = sparse_keymap_setup(input, shuttle_wmi_keymap, NULL);
+	if (rc)
+		goto err_free_dev;
+
+	rc = input_register_device(input);
+	if (rc)
+		goto err_free_keymap;
+
+	priv->inputdev = input;
+	return 0;
+
+err_free_keymap:
+	sparse_keymap_free(input);
+err_free_dev:
+	input_free_device(input);
+	return rc;
+}
+
+static void shuttle_wmi_input_remove(struct shuttle_wmi *priv)
+{
+	struct input_dev *input = priv->inputdev;
+
+	sparse_keymap_free(input);
+	input_unregister_device(input);
+}
+
+static int shuttle_wmi_get_bl(struct backlight_device *bd)
+{
+	u8 val;
+	int rc;
+	struct shuttle_wmi *priv = bl_get_data(bd);
+	struct shuttle_backlight *sbl = priv->id->bl_desc;
+
+	rc = ec_read(sbl->ec_addr, &val);
+	if (rc)
+		return rc;
+	return val & 7;
+}
+
+static int shuttle_wmi_update_bl(struct backlight_device *bd)
+{
+	int rc, steps;
+	u8 val;
+	struct shuttle_fn_map *fn_down, *fn_up;
+	struct shuttle_wmi *priv = bl_get_data(bd);
+	struct shuttle_backlight *sbl = priv->id->bl_desc;
+
+	fn_down = sbl->fn_bl_down;
+	fn_up = sbl->fn_bl_up;
+	if (!fn_down || !fn_up) {
+		rc = ec_write(sbl->ec_addr, bd->props.brightness);
+		if (rc)
+			return rc;
+	} else {
+		/* change brightness by steps, this is a quirk for shuttle
+		 * machines which don't accept direct write to ec for this */
+		rc = ec_read(sbl->ec_addr, &val);
+		if (rc)
+			return rc;
+		steps = bd->props.brightness - (val & 7);
+		while (steps > 0) {
+			wmi_ec_cmd(fn_up->cmd, fn_up->arg, 0, fn_up->fn, NULL);
+			steps--;
+		}
+		while (steps < 0) {
+			wmi_ec_cmd(fn_down->cmd, fn_down->arg, 0, fn_down->fn,
+				   NULL);
+			steps++;
+		}
+	}
+
+	wmi_ec_cmd(CMD_CUTLVDS, 0, 0,
+		   (bd->props.power == FB_BLANK_UNBLANK) ? 1 : 0,
+		   NULL);
+
+	return 0;
+}
+
+static const struct backlight_ops shuttle_wmi_bl_ops = {
+	.get_brightness = shuttle_wmi_get_bl,
+	.update_status = shuttle_wmi_update_bl,
+};
+
+static int shuttle_wmi_backlight_init(struct shuttle_wmi *priv)
+{
+	int rc;
+	u8 val;
+	struct backlight_properties props;
+	struct backlight_device *bd;
+	struct shuttle_backlight *sbl = priv->id->bl_desc;
+
+	rc = ec_read(sbl->ec_addr, &val);
+	if (rc)
+		return rc;
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.max_brightness = 7;
+	props.brightness = val & 7;
+	props.power = FB_BLANK_UNBLANK;
+
+	bd = backlight_device_register(KBUILD_MODNAME, &priv->pdev->dev, priv,
+				       &shuttle_wmi_bl_ops, &props);
+	if (IS_ERR(bd))
+		return PTR_ERR(bd);
+	priv->bd = bd;
+	return 0;
+}
+
+static void shuttle_wmi_backlight_exit(struct shuttle_wmi *priv)
+{
+	if (priv->bd)
+		backlight_device_unregister(priv->bd);
+}
+
+static ssize_t store_fn_cmd(struct device *dev, struct device_attribute *attr,
+			    const char *buf, size_t count)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct shuttle_wmi *priv = platform_get_drvdata(pdev);
+	struct shuttle_fn_map *fn_map;
+
+	for (fn_map = priv->id->fn_map; fn_map->name; fn_map++) {
+		if (fn_map->name == attr->attr.name) {
+			wmi_ec_cmd(fn_map->cmd, fn_map->arg, 0, fn_map->fn,
+				   NULL);
+			return count;
+		}
+	}
+	return -EIO;
+}
+
+static int set_fn_cmd_debug(void *data, u64 val)
+{
+	struct shuttle_fn_map *fn_map = data;
+
+	wmi_ec_cmd(fn_map->cmd, fn_map->arg, 0, fn_map->fn, NULL);
+	/* we don't know yet how many brightness values or maximum brightness
+	 * values for lightbar, for now print possible brightness value change
+	 * to aid in discovering these */
+	if (fn_map->cmd == CMD_LCTRL) {
+		u32 val;
+		if (!wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER4, &val))
+			pr_info("possible lightbar brightness change to value"
+				" 0x%08x\n", val);
+	}
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_fn_cmd_debug, NULL, set_fn_cmd_debug, "%llu");
+
+static ssize_t show_fn_state(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	struct shuttle_fn_map *fn_map;
+	struct shuttle_state *state;
+	int sw;
+	struct platform_device *pdev = to_platform_device(dev);
+	struct shuttle_wmi *priv = platform_get_drvdata(pdev);
+
+	for (fn_map = priv->id->fn_map; fn_map->name; fn_map++) {
+		if (fn_map->name != attr->attr.name)
+			continue;
+
+		state = fn_map->data;
+		sw = wmi_ec_state(&state->ecram);
+		if (sw < 0)
+			return sw;
+		return sprintf(buf, "%d\n", sw);
+	}
+	return -EIO;
+}
+
+static ssize_t store_fn_state(struct device *dev, struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	int enable, sw;
+	struct shuttle_fn_map *fn_map;
+	struct shuttle_state *state;
+	struct platform_device *pdev = to_platform_device(dev);
+	struct shuttle_wmi *priv = platform_get_drvdata(pdev);
+
+	if (sscanf(buf, "%i", &enable) != 1)
+		return -EINVAL;
+
+	for (fn_map = priv->id->fn_map; fn_map->name; fn_map++) {
+		if (fn_map->name != attr->attr.name)
+			continue;
+
+		state = fn_map->data;
+		sw = wmi_ec_state(&state->ecram);
+		if (sw < 0)
+			return sw;
+		enable = enable ? 1 : 0;
+		if (enable != sw) {
+			wmi_ec_cmd(fn_map->cmd, fn_map->arg, 0, fn_map->fn,
+				   NULL);
+			sw = wmi_ec_state(&state->ecram);
+			if (sw < 0)
+				return sw;
+			if (enable != sw)
+				return -EIO;
+		}
+		return count;
+	}
+	return -EIO;
+}
+
+static void shuttle_fn_exit(struct shuttle_wmi *priv)
+{
+	struct shuttle_fn_map *fn_map;
+	struct dentry *dbg_entry;
+	struct shuttle_state *state;
+
+	if (priv->attr_group) {
+		sysfs_remove_group(&priv->pdev->dev.kobj, priv->attr_group);
+		kfree(priv->attr_group->attrs);
+		kfree(priv->attr_group);
+		priv->attr_group = NULL;
+	}
+
+	fn_map = priv->id->fn_map;
+	while (fn_map->name) {
+		switch (fn_map->type) {
+		case FN_CMD:
+			kfree(fn_map->data);
+			fn_map->data = NULL;
+			break;
+		case FN_CMD_DEBUG:
+			dbg_entry = fn_map->data;
+			if (dbg_entry) {
+				debugfs_remove(dbg_entry);
+				fn_map->data = NULL;
+			}
+			break;
+		case FN_RFKILL:
+			shuttle_rfkill_remove(fn_map);
+			break;
+		case FN_STATE:
+			state = fn_map->data;
+			kfree(state->dev_attr);
+			state->dev_attr = NULL;
+			break;
+		}
+		fn_map++;
+	}
+}
+
+static struct device_attribute *new_dev_attr(struct shuttle_fn_map *fn_map)
+{
+	struct device_attribute *dev_attr;
+
+	dev_attr = kzalloc(sizeof(struct device_attribute), GFP_KERNEL);
+	if (!dev_attr)
+		return NULL;
+	dev_attr->attr.name = fn_map->name;
+	return dev_attr;
+}
+
+static int shuttle_fn_init(struct shuttle_wmi *priv)
+{
+	struct shuttle_fn_map *fn_map;
+	struct device_attribute *dev_attr;
+	struct dentry *dbg_entry;
+	struct shuttle_state *state;
+	struct attribute **attr;
+	int nattr = 0;
+	int rc = -ENOMEM;
+
+	fn_map = priv->id->fn_map;
+	while (fn_map->name) {
+		switch (fn_map->type) {
+		case FN_CMD:
+			dev_attr = new_dev_attr(fn_map);
+			if (!dev_attr)
+				goto fn_init_err;
+			dev_attr->attr.mode = 0200;
+			dev_attr->store = store_fn_cmd;
+			fn_map->data = dev_attr;
+			nattr++;
+			break;
+		case FN_CMD_DEBUG:
+			dbg_entry = debugfs_create_file(fn_map->name, 0200,
+							priv->dbg_root, fn_map,
+							&fops_fn_cmd_debug);
+			if (!dbg_entry)
+				goto fn_init_err;
+			fn_map->data = dbg_entry;
+			break;
+		case FN_RFKILL:
+			rc = shuttle_rfkill_init(fn_map, &priv->pdev->dev);
+			if (rc)
+				goto fn_init_err;
+			break;
+		case FN_STATE:
+			dev_attr = new_dev_attr(fn_map);
+			if (!dev_attr)
+				goto fn_init_err;
+			dev_attr->attr.mode = 0644;
+			dev_attr->show = show_fn_state;
+			dev_attr->store = store_fn_state;
+			state = fn_map->data;
+			state->dev_attr = dev_attr;
+			nattr++;
+			break;
+		}
+		fn_map++;
+	}
+
+	/* create array of sysfs attributes (FN_CMD and FN_STATE types) */
+	if (nattr > 0) {
+		priv->attr_group = kzalloc(sizeof(struct attribute_group),
+					   GFP_KERNEL);
+		if (!priv->attr_group)
+			goto fn_init_err;
+		priv->attr_group->attrs = kzalloc(sizeof(struct attribute *) *
+						  (nattr + 1), GFP_KERNEL);
+		if (!priv->attr_group->attrs)
+			goto fn_attrs_err;
+		attr = priv->attr_group->attrs;
+		fn_map = priv->id->fn_map;
+		while (fn_map->name) {
+			if (fn_map->type == FN_CMD) {
+				dev_attr = fn_map->data;
+				*attr = &dev_attr->attr;
+				attr++;
+			} else if (fn_map->type == FN_STATE) {
+				state = fn_map->data;
+				*attr = &state->dev_attr->attr;
+				attr++;
+			}
+			fn_map++;
+		}
+		rc = sysfs_create_group(&priv->pdev->dev.kobj,
+					priv->attr_group);
+		if (rc)
+			goto fn_grp_err;
+	}
+
+	return 0;
+
+fn_grp_err:
+	kfree(priv->attr_group->attrs);
+fn_attrs_err:
+	kfree(priv->attr_group);
+	priv->attr_group = NULL;
+fn_init_err:
+	shuttle_fn_exit(priv);
+	return rc;
+}
+
+static int __devinit shuttle_wmi_probe(struct platform_device *pdev)
+{
+	struct shuttle_wmi *priv;
+	int rc, i;
+	acpi_status status;
+	u32 val;
+
+	priv = kzalloc(sizeof(struct shuttle_wmi), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	priv->pdev = pdev;
+	platform_set_drvdata(pdev, priv);
+
+	for (i = 0; i < ARRAY_SIZE(shuttle_ids); i++) {
+		rc = wmi_ec_cmd(shuttle_ids[i].cmd_id, 0, 0, 0, &val);
+		if (!rc && val == 1) {
+			priv->id = &shuttle_ids[i];
+			break;
+		}
+	}
+	/* If we can't identify the system using a WMI command, try using a DMI
+	 * match, otherwise set id to unknown model */
+	if (i == ARRAY_SIZE(shuttle_ids)) {
+		if (dmi_check_system(shuttle_dmi_ids))
+			priv->id = &shuttle_dmi_id;
+		else
+			priv->id = &id_unknown;
+	}
+
+	/* Process backlight quirks for some models based on DA18IM */
+	if (priv->id->cmd_id == CMD_DA18IM) {
+		if (dmi_check_system(shuttle_quirk_bl_dmi_ids))
+			priv->id->bl_desc = &quirk_bl_desc;
+	}
+
+	priv->dbg_root = debugfs_create_dir(KBUILD_MODNAME, NULL);
+	if (!priv->dbg_root) {
+		rc = -ENOMEM;
+		goto err_debugfs;
+	}
+
+	rc = shuttle_fn_init(priv);
+	if (rc)
+		goto err_fn;
+
+	rc = shuttle_wmi_input_init(priv);
+	if (rc)
+		goto err_input;
+
+	status = wmi_install_notify_handler(SHUTTLE_WMI_EVENT_GUID,
+					    shuttle_wmi_notify, priv);
+	if (ACPI_FAILURE(status)) {
+		rc = -EIO;
+		goto err_notify;
+	}
+
+	if (!acpi_video_backlight_support()) {
+		rc = shuttle_wmi_backlight_init(priv);
+		if (rc)
+			goto err_backlight;
+	}
+	return 0;
+
+err_backlight:
+	wmi_remove_notify_handler(SHUTTLE_WMI_EVENT_GUID);
+err_notify:
+	shuttle_wmi_input_remove(priv);
+err_input:
+	shuttle_fn_exit(priv);
+err_fn:
+	debugfs_remove(priv->dbg_root);
+err_debugfs:
+	kfree(priv);
+	return rc;
+}
+
+static int __devexit shuttle_wmi_remove(struct platform_device *pdev)
+{
+	struct shuttle_wmi *priv = platform_get_drvdata(pdev);
+
+	shuttle_wmi_backlight_exit(priv);
+	wmi_remove_notify_handler(SHUTTLE_WMI_EVENT_GUID);
+	shuttle_wmi_input_remove(priv);
+	shuttle_fn_exit(priv);
+	debugfs_remove(priv->dbg_root);
+	kfree(priv);
+	return 0;
+}
+
+static int shuttle_wmi_resume(struct device *dev)
+{
+	return shuttle_rfkill_resume(dev);
+}
+
+static const struct dev_pm_ops shuttle_wmi_pm_ops = {
+	.restore = shuttle_wmi_resume,
+	.resume = shuttle_wmi_resume,
+};
+
+static struct platform_driver shuttle_wmi_driver = {
+	.driver = {
+		.name = KBUILD_MODNAME,
+		.owner = THIS_MODULE,
+		.pm = &shuttle_wmi_pm_ops,
+	},
+	.probe = shuttle_wmi_probe,
+	.remove = __devexit_p(shuttle_wmi_remove),
+};
+
+static struct platform_device *shuttle_wmi_device;
+
+static ssize_t show_model_name(struct device *dev,
+			       struct device_attribute *attr,
+			       char *buf)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct shuttle_wmi *priv = platform_get_drvdata(pdev);
+
+	return sprintf(buf, "%s\n", priv->id->model_name);
+}
+
+static DEVICE_ATTR(model_name, 0444, show_model_name, NULL);
+
+static struct attribute *shuttle_platform_attributes[] = {
+	&dev_attr_model_name.attr,
+	NULL
+};
+
+static struct attribute_group shuttle_attribute_group = {
+	.attrs = shuttle_platform_attributes
+};
+
+static int __init shuttle_wmi_init(void)
+{
+	int rc;
+	u32 val;
+
+	if (!wmi_has_guid(SHUTTLE_WMI_SETGET_GUID) ||
+	    !wmi_has_guid(SHUTTLE_WMI_EVENT_GUID)) {
+		pr_err("Required WMI GUID not available\n");
+		return -ENODEV;
+	}
+
+	/* Check that we are really on a shuttle BIOS */
+	rc = wmi_ec_cmd(CMD_INT15, 0, 0, 0, &val);
+	if (rc || val != 0x534c) {
+		pr_err("Shuttle WMI device not found or unsupported"
+		       " (val=0x%08x)\n", val);
+		return -ENODEV;
+	}
+
+	rc = platform_driver_register(&shuttle_wmi_driver);
+	if (rc)
+		goto err_driver_register;
+	shuttle_wmi_device = platform_device_alloc(KBUILD_MODNAME, -1);
+	if (!shuttle_wmi_device) {
+		rc = -ENOMEM;
+		goto err_device_alloc;
+	}
+	rc = platform_device_add(shuttle_wmi_device);
+	if (rc)
+		goto err_device_add;
+
+	rc = sysfs_create_group(&shuttle_wmi_device->dev.kobj,
+				&shuttle_attribute_group);
+	if (rc)
+		goto err_sysfs;
+
+	return 0;
+
+err_sysfs:
+	platform_device_del(shuttle_wmi_device);
+err_device_add:
+	platform_device_put(shuttle_wmi_device);
+err_device_alloc:
+	platform_driver_unregister(&shuttle_wmi_driver);
+err_driver_register:
+	return rc;
+}
+
+static void __exit shuttle_wmi_exit(void)
+{
+	sysfs_remove_group(&shuttle_wmi_device->dev.kobj,
+			   &shuttle_attribute_group);
+	platform_device_unregister(shuttle_wmi_device);
+	platform_driver_unregister(&shuttle_wmi_driver);
+}
+
+module_init(shuttle_wmi_init);
+module_exit(shuttle_wmi_exit);