[RFC/RFT][PATCH v2 1/2] Add shuttle-wmi driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This adds a new platform driver for shuttle machines. On some desktop
machines, shuttle is using laptop hardware, but without laptop keyboard.
Thus, for some features like turn on/off webcam, an way is need to
control the hardware. The driver uses the ACPI-WMI interface provided to
export available features through sysfs and other subsystems. The driver
isn't limited to desktop machines, in notebooks with similar firmware,
this driver can be used too (although not necessary).

Signed-off-by: Herton Ronaldo Krzesinski <herton@xxxxxxxxxxxxxxx>
---
 .../ABI/testing/sysfs-platform-shuttle-wmi         |   72 ++
 MAINTAINERS                                        |    6 +
 drivers/platform/x86/Kconfig                       |   15 +
 drivers/platform/x86/Makefile                      |    1 +
 drivers/platform/x86/shuttle-wmi.c                 | 1297 ++++++++++++++++++++
 5 files changed, 1391 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-platform-shuttle-wmi
 create mode 100644 drivers/platform/x86/shuttle-wmi.c

changes from v2:
- address previous reviewing comments.
- create sysfs attributes dynamically, as some firmware may have different
  features or command numbers.
- add support for more shuttle machines.
- lightbar led for now is in debug fs, none of the machines I have report
  any brightness value change in supposed 0x759 address, and they don't have
  the lightbar, so can't test.
- I discovered that wireless presence is now detected using a bit at address
  0x45a, so I'm using that to create or not a rfkill. The machines I have don't
  have 3g/bluetooth support, so I don't know their bits, I left a message
  that's printed once to help discover it if someone can test it on a machine
  where it has these devices.

diff --git a/Documentation/ABI/testing/sysfs-platform-shuttle-wmi b/Documentation/ABI/testing/sysfs-platform-shuttle-wmi
new file mode 100644
index 0000000..06b1d69
--- /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.33
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+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.33
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+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.33
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+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.33
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+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.33
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+Description:
+		Control touchpad state. 1 means off, 0 means on.
+
+What:		/sys/devices/platform/shuttle_wmi/webcam
+Date:		December 2010
+KernelVersion:	2.6.33
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+Description:
+		Control webcam state. 1 means on, 0 means off.
+
+What:		/sys/devices/platform/shuttle_wmi/white_balance
+Date:		December 2010
+KernelVersion:	2.6.33
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+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 d0c63a3..6457198 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5375,6 +5375,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@xxxxxxxxxxxxxxx>
+L:	platform-driver-x86@xxxxxxxxxxxxxxx
+S:	Maintained
+F:	drivers/platform/x86/shuttle-wmi.c
+
 SIMPLE FIRMWARE INTERFACE (SFI)
 M:	Len Brown <lenb@xxxxxxxxxx>
 L:	sfi-devel@xxxxxxxxxxxxxxxxxx
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 4c7f8b9..43ca590 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -651,4 +651,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@xxxxxxxxxxxxxxx>
+ *
+ * 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);
-- 
1.7.3.3

--
To unsubscribe from this list: send the line "unsubscribe platform-driver-x86" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Kernel Development]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux