new file mode 100644
@@ -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.
@@ -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
@@ -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
@@ -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
new file mode 100644
@@ -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);