[RFC][PATCH] New ACPI-WMI driver for shuttle machines

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

 



Hi,

Recently, I received some shuttle machines that are mostly a notebook "glued"
to a desktop form factor. They are notebook hardware, have a webcam, wireless,
etc., but they don't have a notebook keyboard, and because of this no way to
control the hardware, like webcam on/off, etc. So, a driver is needed.

The machines I have here is similar (not same) to this on shuttle website:
http://us.shuttle.com/X50v2.aspx

What follows is a patch which adds a driver which exposes the controls
available through ACPI-WMI. For now just a RFC to get feedback. Also, on some
shuttle machines, fn+<function> can have different values, and I need to
implement a more flexible way to change command numbers (fn+n) depending on
model of shuttle machine, so it isn't a final version yet. And on Shuttle
DA18IE, the interface for video switch isn't final and could change, which
may affect the driver.

[PATCH] Add shuttle-wmi driver

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, a way is need to
control the hardware. The driver uses ACPI-WMI interface provided to
export available controls through sysfs.

Signed-off-by: Herton Ronaldo Krzesinski <herton@xxxxxxxxxxxxxxx>
---
 .../ABI/testing/sysfs-platform-shuttle-wmi         |  161 ++++
 drivers/platform/x86/Kconfig                       |   16 +
 drivers/platform/x86/Makefile                      |    1 +
 drivers/platform/x86/shuttle-wmi.c                 |  843 ++++++++++++++++++++
 4 files changed, 1021 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..63a8c8b
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-shuttle-wmi
@@ -0,0 +1,161 @@
+What:		/sys/devices/platform/shuttle_wmi/brightness_down
+Date:		November 2010
+KernelVersion:	2.6.37
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+Description:
+		This is a write only option (accepts any single value, eg.
+		"echo 1 > brightness_down") that is equivalent of pressing
+		fn+<brightness down> function on notebooks. This option exists
+		because of shuttle machines that are notebooks in desktop form
+		factor, and which don't have the notebook keyboard, thus no
+		way to use fn+<brightness down>.
+
+What:		/sys/devices/platform/shuttle_wmi/brightness_up
+Date:		November 2010
+KernelVersion:	2.6.37
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+Description:
+		This is a write only option (accepts any single value, eg.
+		"echo 1 > brightness_up") that is equivalent of pressing
+		fn+<brightness up> function on notebooks. This option exists
+		because of shuttle machines that are notebooks in desktop form
+		factor, and which don't have the notebook keyboard, thus no
+		way to use fn+<brightness up>.
+
+What:		/sys/devices/platform/shuttle_wmi/cut_lvds
+Date:		November 2010
+KernelVersion:	2.6.37
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+Description:
+		This is a write only option. Writing any single non-zero value
+		to it enables main screen output, 0 to disable.
+
+What:		/sys/devices/platform/shuttle_wmi/lcd_auto_adjust
+Date:		November 2010
+KernelVersion:	2.6.37
+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. Some shuttle machines have LCD attached to analog
+		VGA connector, so uses/needs auto-adjust.
+
+What:		/sys/devices/platform/shuttle_wmi/lbar_brightness_down
+Date:		November 2010
+KernelVersion:	2.6.37
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+Description:
+		This is a write only option (accepts any single value, eg.
+		"echo 1 > lbar_brightness_down"). Decreases one step of lightbar
+		brightness.
+
+What:		/sys/devices/platform/shuttle_wmi/lbar_brightness_up
+Date:		November 2010
+KernelVersion:	2.6.37
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+Description:
+		This is a write only option (accepts any single value, eg.
+		"echo 1 > lbar_brightness_up"). Increases one step of lightbar
+		brightness.
+
+What:		/sys/devices/platform/shuttle_wmi/model_name
+Date:		November 2010
+KernelVersion:	2.6.37
+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" or "Shuttle DA18IM".
+
+What:		/sys/devices/platform/shuttle_wmi/panel_set_default
+Date:		November 2010
+KernelVersion:	2.6.37
+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.
+
+What:		/sys/devices/platform/shuttle_wmi/powersave
+Date:		November 2010
+KernelVersion:	2.6.37
+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 (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. The function is equal to fn+<cpu> switch on some
+		notebooks.
+
+What:		/sys/devices/platform/shuttle_wmi/sleep
+Date:		November 2010
+KernelVersion:	2.6.37
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+Description:
+		This is a write only option (accepts any single value, eg.
+		"echo 1 > sleep") that is equivalent of pressing fn+<sleep>
+		function on notebooks.
+
+What:		/sys/devices/platform/shuttle_wmi/sound_mute
+Date:		November 2010
+KernelVersion:	2.6.37
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+Description:
+		This is a write only option (accepts any single value, eg.
+		"echo 1 > sound_mute") that is equivalent of pressing fn+<mute>
+		function on notebooks.
+
+What:		/sys/devices/platform/shuttle_wmi/switch_video
+Date:		November 2010
+KernelVersion:	2.6.37
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+Description:
+		This is a write only option (accepts any single value, eg.
+		"echo 1 > switch_video") that is equivalent of pressing
+		fn+<display switch> function on notebooks.
+
+What:		/sys/devices/platform/shuttle_wmi/touchpad
+Date:		November 2010
+KernelVersion:	2.6.37
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+Description:
+		Control touchpad state. 1 means on, 0 means off.
+
+What:		/sys/devices/platform/shuttle_wmi/volume_down
+Date:		November 2010
+KernelVersion:	2.6.37
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+Description:
+		This is a write only option (accepts any single value, eg.
+		"echo 1 > volume_down") that is equivalent of pressing
+		fn+<volume down> function on notebooks.
+
+What:		/sys/devices/platform/shuttle_wmi/volume_up
+Date:		November 2010
+KernelVersion:	2.6.37
+Contact:	"Herton Ronaldo Krzesinski" <herton@xxxxxxxxxxxxxxx>
+Description:
+		This is a write only option (accepts any single value, eg.
+		"echo 1 > volume_up") that is equivalent of pressing
+		fn+<volume up> function on notebooks.
+
+What:		/sys/devices/platform/shuttle_wmi/webcam
+Date:		November 2010
+KernelVersion:	2.6.37
+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:		November 2010
+KernelVersion:	2.6.37
+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.
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index faec777..ef84a4d 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -639,4 +639,20 @@ config XO1_RFKILL
 	  Support for enabling/disabling the WLAN interface on the OLPC XO-1
 	  laptop.
 
+config SHUTTLE_WMI
+	tristate "Shuttle WMI Extras"
+	depends on ACPI
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on RFKILL
+	depends on INPUT
+	select ACPI_WMI
+	select INPUT_SPARSEKMAP
+	---help---
+	  This is a driver for Shuttle machines (mainly for laptops in desktop
+	  form factor). It adds controls for wireless, bluetooth, and 3g control
+	  radios, webcam switch, backlight controls, among others.
+
+	  If you have an 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 9950ccc..6a8fa82 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -33,3 +33,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..389a16d
--- /dev/null
+++ b/drivers/platform/x86/shuttle-wmi.c
@@ -0,0 +1,843 @@
+/*
+ * ACPI-WMI driver for Shuttle DA18IE/DA18IM/MA
+ *
+ * Copyright (c) 2009 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/acpi.h>
+#include <linux/platform_device.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 DA18IE/DA18IM/MA 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       0x47b
+#define ECRAM_ER2       0x758
+
+struct shuttle_id {
+	unsigned char cmd_id;
+	char *model_name;
+	bool step_brightness;
+};
+
+static struct shuttle_id shuttle_ids[] = {
+	{ CMD_MA, "Shuttle MA", false },
+	{ CMD_DA18IE, "Shuttle DA18IE", true },
+	{ CMD_DA18IM, "Shuttle DA18IM", false }
+};
+
+struct shuttle_wmi_priv {
+	struct platform_device *pdev;
+	struct input_dev *inputdev;
+	struct shuttle_id *id;
+	struct backlight_device *bd;
+};
+
+struct shuttle_cmd {
+	u16 param2;
+	u16 param1;
+	u8 arg;
+	u8 cmd;
+	u16 hdr;
+};
+
+static acpi_status 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;
+
+	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 status;
+
+	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__);
+		kfree(obj);
+	} else {
+		if (*res) {
+			pr_warning("No result from WMI method (%s)", __func__);
+			*res = NULL;
+		}
+	}
+	return AE_OK;
+}
+
+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
+	};
+
+	if (ACPI_FAILURE(wmi_setget_mtd(&scmd, &res)))
+		return -1;
+	return (res) ? 0 : 1;
+}
+
+struct shuttle_rfkill {
+	char *name;
+	struct rfkill *rfk;
+	enum rfkill_type type;
+	unsigned short fn;
+	u32 mask;
+	u32 list_on[3];
+	u32 list_off[3];
+};
+
+static struct shuttle_rfkill shuttle_rfk_list[] = {
+	{ "shuttle_wlan", NULL, RFKILL_TYPE_WLAN,
+	  0x04, 0x80, { 0x08 }, { 0x09 } },
+	{ "shuttle_bluetooth", NULL, RFKILL_TYPE_BLUETOOTH,
+	  0x0d, 0x20, { 0x0c, 0x29 }, { 0x0d, 0x2a } },
+	{ "shuttle_3g", NULL, RFKILL_TYPE_WWAN,
+	  0x05, 0x40, { 0x10, 0x29 }, { 0x11, 0x2a } },
+};
+
+static int rfkill_common_set_block(void *data, bool blocked)
+{
+	u32 val;
+	bool sw;
+	struct shuttle_rfkill *srfk = data;
+
+	if (wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER1, &val))
+		return -EIO;
+	sw = val & srfk->mask;
+
+	if ((blocked && sw) || (!blocked && !sw))
+		wmi_ec_cmd(CMD_SCMD, 0, 0, srfk->fn, NULL);
+	else
+		return 0;
+
+	if (wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER1, &val))
+		return -EIO;
+	return ((val & srfk->mask) != blocked) ? 0 : -EIO;
+}
+
+static const struct rfkill_ops rfkill_common_ops = {
+	.set_block = rfkill_common_set_block,
+};
+
+static int common_rfkill_init(struct shuttle_rfkill *srfk, struct device *dev,
+			      u32 init_val)
+{
+	int rc;
+
+	srfk->rfk = rfkill_alloc(srfk->name, dev, srfk->type,
+				 &rfkill_common_ops, srfk);
+	if (!srfk->rfk)
+		return -ENOMEM;
+
+	rfkill_init_sw_state(srfk->rfk, !(init_val & srfk->mask));
+
+	rc = rfkill_register(srfk->rfk);
+	if (rc) {
+		rfkill_destroy(srfk->rfk);
+		srfk->rfk = NULL;
+		return rc;
+	}
+
+	return 0;
+}
+
+static int shuttle_rfkill_init(struct platform_device *pdev)
+{
+	int rc, i;
+	u32 val;
+	struct shuttle_rfkill *srfk;
+
+	if (wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER1, &val))
+		return -EIO;
+
+	for (i = 0; i < ARRAY_SIZE(shuttle_rfk_list); i++) {
+		srfk = &shuttle_rfk_list[i];
+
+		/* check that hardware is available (when missing we can't
+		 * unblock it), to avoid having rfkill switch when not needed;
+		 * after check, reset to initial setting */
+		if (rfkill_common_set_block(srfk, false))
+			continue;
+		if (!(val & srfk->mask))
+			rfkill_common_set_block(srfk, true);
+
+		rc = common_rfkill_init(srfk, &pdev->dev, val);
+		if (rc)
+			goto err_rfk_init;
+	}
+	return 0;
+
+err_rfk_init:
+	for (i--; i >= 0; i--) {
+		srfk = &shuttle_rfk_list[i];
+		if (srfk->rfk) {
+			rfkill_unregister(srfk->rfk);
+			rfkill_destroy(srfk->rfk);
+			srfk->rfk = NULL;
+		}
+	}
+	return rc;
+}
+
+static void shuttle_rfkill_remove(void)
+{
+	int i;
+	struct shuttle_rfkill *srfk;
+
+	for (i = 0; i < ARRAY_SIZE(shuttle_rfk_list); i++) {
+		srfk = &shuttle_rfk_list[i];
+		if (srfk->rfk) {
+			rfkill_unregister(srfk->rfk);
+			rfkill_destroy(srfk->rfk);
+			srfk->rfk = NULL;
+		}
+	}
+}
+
+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(u32 code)
+{
+	int i;
+	struct rfkill *rfk;
+	u32 *rfk_evnt;
+	bool res = false;
+
+	for (i = 0; i < ARRAY_SIZE(shuttle_rfk_list); i++) {
+		rfk = shuttle_rfk_list[i].rfk;
+		if (!rfk)
+			continue;
+
+		rfk_evnt = shuttle_rfk_list[i].list_on;
+		if (set_rfkill_sw(rfk_evnt, code, rfk, false)) {
+			res = true;
+			continue;
+		}
+
+		rfk_evnt = shuttle_rfk_list[i].list_off;
+		if (set_rfkill_sw(rfk_evnt, code, rfk, true))
+			res = true;
+	}
+	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" },
+		{ 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 *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(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);
+
+		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_KEY, 0x14, { KEY_BRIGHTNESSUP } },
+	{ KE_KEY, 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 or that we don't care */
+	{ 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 *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 *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)
+{
+	u32 val;
+
+	if (wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER0, &val))
+		return -EIO;
+	return val & 0x7;
+}
+
+static int shuttle_wmi_update_bl(struct backlight_device *bd)
+{
+	int rc, steps;
+	u32 val;
+	struct shuttle_wmi_priv *priv = bl_get_data(bd);
+
+	if (!priv->id->step_brightness) {
+		rc = ec_write(0x79, 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 */
+		if (wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER0, &val))
+			return -EIO;
+		val &= 0x7;
+		steps = bd->props.brightness - val;
+		while (steps > 0) {
+			wmi_ec_cmd(CMD_SCMD, 0, 0, 0x0c, NULL);
+			steps--;
+		}
+		while (steps < 0) {
+			wmi_ec_cmd(CMD_SCMD, 0, 0, 0x0b, NULL);
+			steps++;
+		}
+	}
+	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 *priv)
+{
+	u32 val;
+	struct backlight_properties props;
+	struct backlight_device *bd;
+
+	if (wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER0, &val))
+		return -EIO;
+	val &= 0x7;
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.max_brightness = 7;
+	props.brightness = val;
+	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 *priv)
+{
+	if (priv->bd)
+		backlight_device_unregister(priv->bd);
+}
+
+static int __devinit shuttle_wmi_probe(struct platform_device *pdev)
+{
+	struct shuttle_wmi_priv *priv;
+	int rc, i;
+	acpi_status status;
+	u32 val;
+	static struct shuttle_id id_unknown = {
+		.model_name = "Unknown",
+	};
+
+	priv = kzalloc(sizeof(struct shuttle_wmi_priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	priv->pdev = pdev;
+	platform_set_drvdata(pdev, priv);
+
+	rc = shuttle_rfkill_init(pdev);
+	if (rc)
+		goto err_rfk;
+
+	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;
+	}
+
+	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 (i == ARRAY_SIZE(shuttle_ids))
+		priv->id = &id_unknown;
+
+	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_rfkill_remove();
+err_rfk:
+	kfree(priv);
+	return rc;
+}
+
+static int __devexit shuttle_wmi_remove(struct platform_device *pdev)
+{
+	struct shuttle_wmi_priv *priv = platform_get_drvdata(pdev);
+
+	shuttle_wmi_backlight_exit(priv);
+	wmi_remove_notify_handler(SHUTTLE_WMI_EVENT_GUID);
+	shuttle_wmi_input_remove(priv);
+	shuttle_rfkill_remove();
+	kfree(priv);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int shuttle_wmi_resume(struct device *dev)
+{
+	u32 val;
+	int i;
+	struct shuttle_rfkill *srfk;
+
+	if (wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER1, &val))
+		return -EIO;
+
+	for (i = 0; i < ARRAY_SIZE(shuttle_rfk_list); i++) {
+		srfk = &shuttle_rfk_list[i];
+		if (srfk->rfk)
+			rfkill_set_sw_state(srfk->rfk, !(val & srfk->mask));
+	}
+	return 0;
+}
+
+static const struct dev_pm_ops shuttle_wmi_pm_ops = {
+	.restore = shuttle_wmi_resume,
+	.resume = shuttle_wmi_resume,
+};
+#endif
+
+static struct platform_driver shuttle_wmi_driver = {
+	.driver = {
+		.name = KBUILD_MODNAME,
+		.owner = THIS_MODULE,
+#ifdef CONFIG_PM
+		.pm = &shuttle_wmi_pm_ops,
+#endif
+	},
+	.probe = shuttle_wmi_probe,
+	.remove = __devexit_p(shuttle_wmi_remove),
+};
+
+static struct platform_device *shuttle_wmi_device;
+
+static ssize_t show_state_common(char *buf, unsigned short ecram, u32 mask)
+{
+	u32 val;
+
+	if (wmi_ec_cmd(CMD_READEC, 0, 0, ecram, &val))
+		return -EIO;
+	return sprintf(buf, "%d\n", (val & mask) ? 1 : 0);
+}
+
+static ssize_t store_state_common(const char *buf, size_t count,
+				  unsigned short ecram, u32 mask,
+				  unsigned short fn)
+{
+	int enable;
+	int rc;
+	u32 val;
+
+	if (sscanf(buf, "%i", &enable) != 1)
+		return -EINVAL;
+
+	if (wmi_ec_cmd(CMD_READEC, 0, 0, ecram, &val))
+		return -EIO;
+	val &= mask;
+	if ((val && !enable) ||
+	    (!val && enable)) {
+		wmi_ec_cmd(CMD_SCMD, 0, 0, fn, NULL);
+		rc = wmi_ec_cmd(CMD_READEC, 0, 0, ecram, &val);
+		val &= mask;
+		if (rc || (!val && enable) ||
+		    (val && !enable))
+			return -EIO;
+	}
+	return count;
+}
+
+#define SHUTTLE_STATE_ATTR(_name, _ecram, _mask, _fn)			\
+	static ssize_t show_##_name(struct device *dev,			\
+				    struct device_attribute *attr,	\
+				    char *buf)				\
+	{								\
+		return show_state_common(buf, _ecram, _mask);		\
+	}								\
+	static ssize_t store_##_name(struct device *dev,		\
+				     struct device_attribute *attr,	\
+				     const char *buf, size_t count)	\
+	{								\
+		return store_state_common(buf, count, _ecram, _mask,	\
+					  _fn);				\
+	}								\
+	static DEVICE_ATTR(_name, 0644, show_##_name, store_##_name);
+
+SHUTTLE_STATE_ATTR(powersave, ECRAM_ER2, 0x10, 0x02)
+SHUTTLE_STATE_ATTR(touchpad, ECRAM_ER1, 0x02, 0x06)
+SHUTTLE_STATE_ATTR(webcam, ECRAM_ER1, 0x10, 0x07)
+
+#define SHUTTLE_CMD_ATTR(_name, _cmd, _arg, _fn)			\
+	static ssize_t store_##_name(struct device *dev,		\
+				     struct device_attribute *attr,	\
+				     const char *buf, size_t count)	\
+	{								\
+		wmi_ec_cmd(_cmd, _arg, 0, _fn, NULL);			\
+		return count;						\
+	}								\
+	static DEVICE_ATTR(_name, 0200, NULL, store_##_name);
+
+SHUTTLE_CMD_ATTR(sleep, CMD_SCMD, 0, 0x01)
+SHUTTLE_CMD_ATTR(switch_video, CMD_SCMD, 0, 0x03)
+SHUTTLE_CMD_ATTR(sound_mute, CMD_SCMD, 0, 0x08)
+SHUTTLE_CMD_ATTR(volume_down, CMD_SCMD, 0, 0x09)
+SHUTTLE_CMD_ATTR(volume_up, CMD_SCMD, 0, 0x0a)
+SHUTTLE_CMD_ATTR(brightness_down, CMD_SCMD, 0, 0x0b)
+SHUTTLE_CMD_ATTR(brightness_up, CMD_SCMD, 0, 0x0c)
+SHUTTLE_CMD_ATTR(lcd_auto_adjust, CMD_SCMD, 0, 0x81)
+SHUTTLE_CMD_ATTR(white_balance, CMD_SCMD, 0, 0x82)
+SHUTTLE_CMD_ATTR(panel_set_default, CMD_SCMD, 0, 0x83)
+SHUTTLE_CMD_ATTR(lbar_brightness_down, CMD_LCTRL, 1, 0)
+SHUTTLE_CMD_ATTR(lbar_brightness_up, CMD_LCTRL, 1, 1)
+
+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 *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 ssize_t store_cut_lvds(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	int cut;
+
+	if (sscanf(buf, "%i", &cut) != 1)
+		return -EINVAL;
+
+	if (cut)
+		wmi_ec_cmd(CMD_CUTLVDS, 0, 0, 1, NULL);
+	else
+		wmi_ec_cmd(CMD_CUTLVDS, 0, 0, 0, NULL);
+
+	return count;
+}
+static DEVICE_ATTR(cut_lvds, 0200, NULL, store_cut_lvds);
+
+static struct attribute *shuttle_platform_attributes[] = {
+	&dev_attr_powersave.attr,
+	&dev_attr_touchpad.attr,
+	&dev_attr_webcam.attr,
+	&dev_attr_sleep.attr,
+	&dev_attr_switch_video.attr,
+	&dev_attr_sound_mute.attr,
+	&dev_attr_volume_down.attr,
+	&dev_attr_volume_up.attr,
+	&dev_attr_brightness_down.attr,
+	&dev_attr_brightness_up.attr,
+	&dev_attr_lcd_auto_adjust.attr,
+	&dev_attr_white_balance.attr,
+	&dev_attr_panel_set_default.attr,
+	&dev_attr_lbar_brightness_down.attr,
+	&dev_attr_lbar_brightness_up.attr,
+	&dev_attr_model_name.attr,
+	&dev_attr_cut_lvds.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.2

-- 
[]'s
Herton
--
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