All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/2] Add shuttle-wmi driver
@ 2011-01-18 18:41 Herton Ronaldo Krzesinski
  2011-01-18 18:41 ` [PATCH 2/2] ACPI video: add initial blacklist to use vendor drivers for backlight Herton Ronaldo Krzesinski
  0 siblings, 1 reply; 9+ messages in thread
From: Herton Ronaldo Krzesinski @ 2011-01-18 18:41 UTC (permalink / raw)
  To: platform-driver-x86
  Cc: linux-acpi, Matthew Garrett, Herton Ronaldo Krzesinski

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@mandriva.com.br>
---
 .../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

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);
-- 
1.7.3.5

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 2/2] ACPI video: add initial blacklist to use vendor drivers for backlight
  2011-01-18 18:41 [PATCH 1/2] Add shuttle-wmi driver Herton Ronaldo Krzesinski
@ 2011-01-18 18:41 ` Herton Ronaldo Krzesinski
  2011-01-18 18:56   ` Matthew Garrett
  2011-04-12  3:40   ` Len Brown
  0 siblings, 2 replies; 9+ messages in thread
From: Herton Ronaldo Krzesinski @ 2011-01-18 18:41 UTC (permalink / raw)
  To: platform-driver-x86
  Cc: linux-acpi, Matthew Garrett, Herton Ronaldo Krzesinski

While developing shuttle-wmi, I discovered some shuttle machines where
its acpi video interface doesn't work properly. Changes to the
brightness value don't reflect actual hardware changes. For these
devices, a quirk is done in shuttle-wmi driver, where brightness values
are then changed properly in the hardware too.

This adds a list for shuttle devices I tested which doesn't have working
backlight support in acpi video, but works with workaround done in
shuttle-wmi driver, so for them, add dmi blacklist to use vendor driver.

Signed-off-by: Herton Ronaldo Krzesinski <herton@mandriva.com.br>
---
 drivers/acpi/video_detect.c |   56 +++++++++++++++++++++++++++++++++++-------
 1 files changed, 46 insertions(+), 10 deletions(-)

diff --git a/drivers/acpi/video_detect.c b/drivers/acpi/video_detect.c
index b836761..39814d2 100644
--- a/drivers/acpi/video_detect.c
+++ b/drivers/acpi/video_detect.c
@@ -132,6 +132,45 @@ find_video(acpi_handle handle, u32 lvl, void *context, void **rv)
 	return AE_OK;
 }
 
+static struct dmi_system_id backlight_vendor_dmi_table[] = {
+	{
+		.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"),
+		},
+	},
+	{
+		.ident = "Positivo SINM10BZ",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Positivo Informatica SA"),
+			DMI_MATCH(DMI_BOARD_NAME, "POS-SINM10BZ"),
+		},
+	},
+	{}
+};
+
 /*
  * Returns the video capabilities of a specific ACPI graphics device
  *
@@ -156,16 +195,13 @@ long acpi_video_get_capabilities(acpi_handle graphics_handle)
 		/* There might be boot param flags set already... */
 		acpi_video_support |= caps;
 		acpi_video_caps_checked = 1;
-		/* Add blacklists here. Be careful to use the right *DMI* bits
-		 * to still be able to override logic via boot params, e.g.:
-		 *
-		 *   if (dmi_name_in_vendors("XY")) {
-		 *	acpi_video_support |=
-		 *		ACPI_VIDEO_OUTPUT_SWITCHING_DMI_VENDOR;
-		 *	acpi_video_support |=
-		 *		ACPI_VIDEO_BACKLIGHT_DMI_VENDOR;
-		 *}
-		 */
+		/* Add blacklists here (systems which don't work properly with
+		 * acpi video and needs a special platform driver) */
+		if (dmi_check_system(backlight_vendor_dmi_table))
+			acpi_video_support |= ACPI_VIDEO_BACKLIGHT_DMI_VENDOR;
+		/*if (dmi_check_system(output_switching_vendor_dmi_table))
+			acpi_video_support |=
+				ACPI_VIDEO_OUTPUT_SWITCHING_DMI_VENDOR;*/
 	} else {
 		status = acpi_bus_get_device(graphics_handle, &tmp_dev);
 		if (ACPI_FAILURE(status)) {
-- 
1.7.3.5

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/2] ACPI video: add initial blacklist to use vendor drivers for backlight
  2011-01-18 18:41 ` [PATCH 2/2] ACPI video: add initial blacklist to use vendor drivers for backlight Herton Ronaldo Krzesinski
@ 2011-01-18 18:56   ` Matthew Garrett
  2011-01-18 19:24     ` Herton Ronaldo Krzesinski
  2011-04-12  3:40   ` Len Brown
  1 sibling, 1 reply; 9+ messages in thread
From: Matthew Garrett @ 2011-01-18 18:56 UTC (permalink / raw)
  To: Herton Ronaldo Krzesinski; +Cc: platform-driver-x86, linux-acpi

On Tue, Jan 18, 2011 at 04:41:54PM -0200, Herton Ronaldo Krzesinski wrote:

> This adds a list for shuttle devices I tested which doesn't have working
> backlight support in acpi video, but works with workaround done in
> shuttle-wmi driver, so for them, add dmi blacklist to use vendor driver.

How about a hook to call from the shuttle-wmi driver to disable it? That 
way we wouldn't need the blacklist in the ACPI driver.

-- 
Matthew Garrett | mjg59@srcf.ucam.org

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/2] ACPI video: add initial blacklist to use vendor drivers for backlight
  2011-01-18 18:56   ` Matthew Garrett
@ 2011-01-18 19:24     ` Herton Ronaldo Krzesinski
  2011-01-18 19:30       ` Matthew Garrett
  0 siblings, 1 reply; 9+ messages in thread
From: Herton Ronaldo Krzesinski @ 2011-01-18 19:24 UTC (permalink / raw)
  To: Matthew Garrett; +Cc: platform-driver-x86, linux-acpi

On Tue, 18 Jan 2011 18:56:59 +0000
Matthew Garrett <mjg59@srcf.ucam.org> wrote:

> On Tue, Jan 18, 2011 at 04:41:54PM -0200, Herton Ronaldo Krzesinski wrote:
> 
> > This adds a list for shuttle devices I tested which doesn't have working
> > backlight support in acpi video, but works with workaround done in
> > shuttle-wmi driver, so for them, add dmi blacklist to use vendor driver.
> 
> How about a hook to call from the shuttle-wmi driver to disable it? That 
> way we wouldn't need the blacklist in the ACPI driver.

The problem is that acpi video driver needs to know the blacklist before it
registers its backlight class, not possible to do at run time (eg., disabling
it after video.ko is loaded). I don't know if there is a good solution to
this, besides adding this blacklist in video module.

> 
> -- 
> Matthew Garrett | mjg59@srcf.ucam.org

--
[]'s
Herton

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/2] ACPI video: add initial blacklist to use vendor drivers for backlight
  2011-01-18 19:24     ` Herton Ronaldo Krzesinski
@ 2011-01-18 19:30       ` Matthew Garrett
  2011-01-18 19:37         ` Herton Ronaldo Krzesinski
  0 siblings, 1 reply; 9+ messages in thread
From: Matthew Garrett @ 2011-01-18 19:30 UTC (permalink / raw)
  To: Herton Ronaldo Krzesinski; +Cc: platform-driver-x86, linux-acpi

Ugh. Ok, how does this work on Windows?

-- 
Matthew Garrett | mjg59@srcf.ucam.org

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/2] ACPI video: add initial blacklist to use vendor drivers for backlight
  2011-01-18 19:30       ` Matthew Garrett
@ 2011-01-18 19:37         ` Herton Ronaldo Krzesinski
  2011-01-18 19:44           ` Matthew Garrett
  0 siblings, 1 reply; 9+ messages in thread
From: Herton Ronaldo Krzesinski @ 2011-01-18 19:37 UTC (permalink / raw)
  To: Matthew Garrett; +Cc: platform-driver-x86, linux-acpi

On Tue, 18 Jan 2011 19:30:51 +0000
Matthew Garrett <mjg59@srcf.ucam.org> wrote:

> Ugh. Ok, how does this work on Windows?

On a provided by vendor windows image, the default brightness slider in windows
didn't worked too... only a vendor supplied utility is able to change the
brightness.

> 
> -- 
> Matthew Garrett | mjg59@srcf.ucam.org

--
[]'s
Herton

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/2] ACPI video: add initial blacklist to use vendor drivers for backlight
  2011-01-18 19:37         ` Herton Ronaldo Krzesinski
@ 2011-01-18 19:44           ` Matthew Garrett
  2011-04-07  8:36             ` Anisse Astier
  0 siblings, 1 reply; 9+ messages in thread
From: Matthew Garrett @ 2011-01-18 19:44 UTC (permalink / raw)
  To: Herton Ronaldo Krzesinski; +Cc: platform-driver-x86, linux-acpi

On Tue, Jan 18, 2011 at 05:37:18PM -0200, Herton Ronaldo Krzesinski wrote:
> On Tue, 18 Jan 2011 19:30:51 +0000
> Matthew Garrett <mjg59@srcf.ucam.org> wrote:
> 
> > Ugh. Ok, how does this work on Windows?
> 
> On a provided by vendor windows image, the default brightness slider in windows
> didn't worked too... only a vendor supplied utility is able to change the
> brightness.

Awesome. Any chance you can provide the acpidump output for one of these 
machines? I'd prefer to find a more elegant solution.

-- 
Matthew Garrett | mjg59@srcf.ucam.org

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/2] ACPI video: add initial blacklist to use vendor drivers for backlight
  2011-01-18 19:44           ` Matthew Garrett
@ 2011-04-07  8:36             ` Anisse Astier
  0 siblings, 0 replies; 9+ messages in thread
From: Anisse Astier @ 2011-04-07  8:36 UTC (permalink / raw)
  To: Matthew Garrett
  Cc: Herton Ronaldo Krzesinski, platform-driver-x86, linux-acpi

[-- Attachment #1: Type: text/plain, Size: 1112 bytes --]

Hi Matthew,

On Tue, 18 Jan 2011 19:44:42 +0000, Matthew Garrett <mjg59@srcf.ucam.org> wrote :

> On Tue, Jan 18, 2011 at 05:37:18PM -0200, Herton Ronaldo Krzesinski wrote:
> > On Tue, 18 Jan 2011 19:30:51 +0000
> > Matthew Garrett <mjg59@srcf.ucam.org> wrote:
> > 
> > > Ugh. Ok, how does this work on Windows?
> > 
> > On a provided by vendor windows image, the default brightness slider in windows
> > didn't worked too... only a vendor supplied utility is able to change the
> > brightness.
> 
> Awesome. Any chance you can provide the acpidump output for one of these 
> machines? I'd prefer to find a more elegant solution.
> 

I just encountered the same problem while debugging a machine using msi-wmi:
 - msi-wmi driver relied on the fact that there wouldn't be an acpi_video
   backlight device to load backlight support, therefore backlight
   buttons don't work by default.
 - On windows, it needs the vendor supplied driver to be installed before
   backlight buttons are supported (installed after first boot on the OEM
   image) 


Please find the DSDT of this computer attached.

Regards,

Anisse

[-- Attachment #2: DSDT-msi-ae2050 --]
[-- Type: application/octet-stream, Size: 30316 bytes --]

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/2] ACPI video: add initial blacklist to use vendor drivers for backlight
  2011-01-18 18:41 ` [PATCH 2/2] ACPI video: add initial blacklist to use vendor drivers for backlight Herton Ronaldo Krzesinski
  2011-01-18 18:56   ` Matthew Garrett
@ 2011-04-12  3:40   ` Len Brown
  1 sibling, 0 replies; 9+ messages in thread
From: Len Brown @ 2011-04-12  3:40 UTC (permalink / raw)
  To: Herton Ronaldo Krzesinski
  Cc: platform-driver-x86, linux-acpi, Matthew Garrett


> +		/*if (dmi_check_system(output_switching_vendor_dmi_table))
> +			acpi_video_support |=
> +				ACPI_VIDEO_OUTPUT_SWITCHING_DMI_VENDOR;*/

please exclude this comment.

thanks,
-Len

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2011-04-12  3:40 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-01-18 18:41 [PATCH 1/2] Add shuttle-wmi driver Herton Ronaldo Krzesinski
2011-01-18 18:41 ` [PATCH 2/2] ACPI video: add initial blacklist to use vendor drivers for backlight Herton Ronaldo Krzesinski
2011-01-18 18:56   ` Matthew Garrett
2011-01-18 19:24     ` Herton Ronaldo Krzesinski
2011-01-18 19:30       ` Matthew Garrett
2011-01-18 19:37         ` Herton Ronaldo Krzesinski
2011-01-18 19:44           ` Matthew Garrett
2011-04-07  8:36             ` Anisse Astier
2011-04-12  3:40   ` Len Brown

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.