From patchwork Tue Jan 18 18:41:53 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Herton Ronaldo Krzesinski X-Patchwork-Id: 486771 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter1.kernel.org (8.14.4/8.14.3) with ESMTP id p0IIeQf5011316 for ; Tue, 18 Jan 2011 18:40:29 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752772Ab1ARSk2 (ORCPT ); Tue, 18 Jan 2011 13:40:28 -0500 Received: from perninha.conectiva.com.br ([187.115.55.249]:55211 "EHLO perninha.conectiva.com.br" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752722Ab1ARSk1 (ORCPT ); Tue, 18 Jan 2011 13:40:27 -0500 Received: from localhost (perninha.conectiva.com.br [127.0.0.1]) by perninha.conectiva.com.br (Postfix) with ESMTP id 1B536166F7; Tue, 18 Jan 2011 16:40:10 -0200 (BRST) X-Virus-Scanned: amavisd-new at conectiva.com.br Received: from perninha.conectiva.com.br ([127.0.0.1]) by localhost (perninha.conectiva.com.br [127.0.0.1]) (amavisd-new, port 10025) with LMTP id 5803ATYugQ7h; Tue, 18 Jan 2011 16:40:08 -0200 (BRST) Received: from fox.conectiva (firewall.conectiva [187.115.55.253]) by perninha.conectiva.com.br (Postfix) with ESMTP id 58CAA1627E; Tue, 18 Jan 2011 16:40:07 -0200 (BRST) Received: from gotham.conectiva (gotham.conectiva [10.0.2.23]) by fox.conectiva (Postfix) with ESMTP id D2CFF2F16C; Tue, 18 Jan 2011 16:40:43 -0200 (BRST) From: Herton Ronaldo Krzesinski To: platform-driver-x86@vger.kernel.org Cc: linux-acpi@vger.kernel.org, Matthew Garrett , Herton Ronaldo Krzesinski Subject: [PATCH 1/2] Add shuttle-wmi driver Date: Tue, 18 Jan 2011 16:41:53 -0200 Message-Id: <1295376114-5685-1-git-send-email-herton@mandriva.com.br> X-Mailer: git-send-email 1.7.3.5 Sender: linux-acpi-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-acpi@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.6 (demeter1.kernel.org [140.211.167.41]); Tue, 18 Jan 2011 18:40:29 +0000 (UTC) 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" +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" +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" +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" +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" +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" +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" +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 +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/shuttle-wmi.c + SIMPLE FIRMWARE INTERFACE (SFI) M: Len Brown 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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);