diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-03-29 01:20:23 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-03-29 01:20:23 +0400 |
commit | 61e5191c9d96268746bd57ed55d035678a1a2cf9 (patch) | |
tree | 5ee75dc9aa9eab9cfc41c9fe0042d15f000ef2e6 /drivers/platform | |
parent | d2a2fc18d98d8ee2dec1542efc7f47beec256144 (diff) | |
parent | 86924de2a612b275a45e92ba80d6f47d4e97d620 (diff) | |
download | linux-61e5191c9d96268746bd57ed55d035678a1a2cf9.tar.xz |
Merge branch 'for_linus' of git://cavan.codon.org.uk/platform-drivers-x86
Pull x86 platform driver updates from Matthew Garrett:
"Some significant updates to samsung-laptop, additional hardware
support for Toshibas, misc updates to various hardware and a new
backlight driver for some Apple machines."
Fix up trivial conflicts: geode Geos update happening next to net5501
support, and MSIC thermal platform support added twice.
* 'for_linus' of git://cavan.codon.org.uk/platform-drivers-x86: (77 commits)
acer-wmi: add quirk table for video backlight vendor mode
drivers/platform/x86/amilo-rfkill.c::amilo_rfkill_probe() avoid NULL deref
samsung-laptop: unregister ACPI video module for some well known laptops
acer-wmi: No wifi rfkill on Sony machines
thinkpad-acpi: recognize Lenovo as version string in newer V-series BIOS
asus-wmi: don't update power and brightness when using scalar
eeepc-wmi: split et2012 specific hacks
eeepc-wmi: refine quirks handling
asus-nb-wmi: set panel_power correctly
asus-wmi: move WAPF variable into quirks_entry
asus-wmi: store backlight power status for AIO machine
asus-wmi: add scalar board brightness adj. support
samsung-laptop: cleanup return type: mode_t vs umode_t
drivers, samsung-laptop: fix usage of isalnum
drivers, samsung-laptop: fix initialization of sabi_data in sabi_set_commandb
asus-wmi: on/off bit is not set when reading the value
eeepc-wmi: add extra keymaps for EP121
asus-nb-wmi: ignore useless keys
acer-wmi: support Lenovo ideapad S205 Brazos wifi switch
acer-wmi: fix out of input parameter size when set
...
Diffstat (limited to 'drivers/platform')
24 files changed, 2212 insertions, 2399 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 2dc02c972ce9..2a262f5c5c0c 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -26,6 +26,10 @@ config ACER_WMI depends on RFKILL || RFKILL = n depends on ACPI_WMI select INPUT_SPARSEKMAP + # Acer WMI depends on ACPI_VIDEO when ACPI is enabled + # but for select to work, need to select ACPI_VIDEO's dependencies, ick + select VIDEO_OUTPUT_CONTROL if ACPI + select ACPI_VIDEO if ACPI ---help--- This is a driver for newer Acer (and Wistron) laptops. It adds wireless radio and bluetooth control, and on some laptops, @@ -54,7 +58,6 @@ config ACERHDF config ASUS_LAPTOP tristate "Asus Laptop Extras" depends on ACPI - depends on !ACPI_ASUS select LEDS_CLASS select NEW_LEDS select BACKLIGHT_CLASS_DEVICE @@ -460,10 +463,9 @@ config INTEL_MENLOW If unsure, say N. config EEEPC_LAPTOP - tristate "Eee PC Hotkey Driver (EXPERIMENTAL)" + tristate "Eee PC Hotkey Driver" depends on ACPI depends on INPUT - depends on EXPERIMENTAL depends on RFKILL || RFKILL = n depends on HOTPLUG_PCI select BACKLIGHT_CLASS_DEVICE @@ -482,11 +484,10 @@ config EEEPC_LAPTOP doesn't work on your Eee PC, try eeepc-wmi instead. config ASUS_WMI - tristate "ASUS WMI Driver (EXPERIMENTAL)" + tristate "ASUS WMI Driver" depends on ACPI_WMI depends on INPUT depends on HWMON - depends on EXPERIMENTAL depends on BACKLIGHT_CLASS_DEVICE depends on RFKILL || RFKILL = n depends on HOTPLUG_PCI @@ -501,7 +502,7 @@ config ASUS_WMI be called asus-wmi. config ASUS_NB_WMI - tristate "Asus Notebook WMI Driver (EXPERIMENTAL)" + tristate "Asus Notebook WMI Driver" depends on ASUS_WMI ---help--- This is a driver for newer Asus notebooks. It adds extra features @@ -514,7 +515,7 @@ config ASUS_NB_WMI here. config EEEPC_WMI - tristate "Eee PC WMI Driver (EXPERIMENTAL)" + tristate "Eee PC WMI Driver" depends on ASUS_WMI ---help--- This is a driver for newer Eee PC laptops. It adds extra features @@ -559,38 +560,6 @@ config MSI_WMI To compile this driver as a module, choose M here: the module will be called msi-wmi. -config ACPI_ASUS - tristate "ASUS/Medion Laptop Extras (DEPRECATED)" - depends on ACPI - select BACKLIGHT_CLASS_DEVICE - ---help--- - This driver provides support for extra features of ACPI-compatible - ASUS laptops. As some of Medion laptops are made by ASUS, it may also - support some Medion laptops (such as 9675 for example). It makes all - the extra buttons generate standard ACPI events that go through - /proc/acpi/events, and (on some models) adds support for changing the - display brightness and output, switching the LCD backlight on and off, - and most importantly, allows you to blink those fancy LEDs intended - for reporting mail and wireless status. - - Note: display switching code is currently considered EXPERIMENTAL, - toying with these values may even lock your machine. - - All settings are changed via /proc/acpi/asus directory entries. Owner - and group for these entries can be set with asus_uid and asus_gid - parameters. - - More information and a userspace daemon for handling the extra buttons - at <http://acpi4asus.sf.net>. - - If you have an ACPI-compatible ASUS laptop, say Y or M here. This - driver is still under development, so if your laptop is unsupported or - something works not quite as expected, please use the mailing list - available on the above page (acpi4asus-user@lists.sourceforge.net). - - NOTE: This driver is deprecated and will probably be removed soon, - use asus-laptop instead. - config TOPSTAR_LAPTOP tristate "Topstar Laptop Extras" depends on ACPI @@ -604,6 +573,7 @@ config TOPSTAR_LAPTOP config ACPI_TOSHIBA tristate "Toshiba Laptop Extras" depends on ACPI + depends on ACPI_WMI select LEDS_CLASS select NEW_LEDS depends on BACKLIGHT_CLASS_DEVICE @@ -746,13 +716,18 @@ config XO15_EBOOK config SAMSUNG_LAPTOP tristate "Samsung Laptop driver" - depends on RFKILL && BACKLIGHT_CLASS_DEVICE && X86 + depends on X86 + depends on RFKILL || RFKILL = n + depends on BACKLIGHT_CLASS_DEVICE + select LEDS_CLASS + select NEW_LEDS ---help--- This module implements a driver for a wide range of different Samsung laptops. It offers control over the different - function keys, wireless LED, LCD backlight level, and - sometimes provides a "performance_control" sysfs file to allow - the performance level of the laptop to be changed. + function keys, wireless LED, LCD backlight level. + + It may also provide some sysfs files described in + <file:Documentation/ABI/testing/sysfs-platform-samsung-laptop> To compile this driver as a module, choose M here: the module will be called samsung-laptop. @@ -781,4 +756,14 @@ config SAMSUNG_Q10 This driver provides support for backlight control on Samsung Q10 and related laptops, including Dell Latitude X200. +config APPLE_GMUX + tristate "Apple Gmux Driver" + depends on PNP + select BACKLIGHT_CLASS_DEVICE + ---help--- + This driver provides support for the gmux device found on many + Apple laptops, which controls the display mux for the hybrid + graphics as well as the backlight. Currently only backlight + control is supported by the driver. + endif # X86_PLATFORM_DEVICES diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index bb947657d490..bf7e4f935b17 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -29,9 +29,12 @@ obj-$(CONFIG_PANASONIC_LAPTOP) += panasonic-laptop.o obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o obj-$(CONFIG_ACPI_WMI) += wmi.o obj-$(CONFIG_MSI_WMI) += msi-wmi.o -obj-$(CONFIG_ACPI_ASUS) += asus_acpi.o obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o + +# toshiba_acpi must link after wmi to ensure that wmi devices are found +# before toshiba_acpi initializes obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o + obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o @@ -46,3 +49,4 @@ obj-$(CONFIG_MXM_WMI) += mxm-wmi.o obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o obj-$(CONFIG_SAMSUNG_Q10) += samsung-q10.o +obj-$(CONFIG_APPLE_GMUX) += apple-gmux.o diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index 1e5290b5396d..c1a3fd8e1243 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -43,6 +43,7 @@ #include <linux/input/sparse-keymap.h> #include <acpi/acpi_drivers.h> +#include <acpi/video.h> MODULE_AUTHOR("Carlos Corbacho"); MODULE_DESCRIPTION("Acer Laptop WMI Extras Driver"); @@ -105,13 +106,19 @@ static const struct key_entry acer_wmi_keymap[] = { {KE_KEY, 0x22, {KEY_PROG2} }, /* Arcade */ {KE_KEY, 0x23, {KEY_PROG3} }, /* P_Key */ {KE_KEY, 0x24, {KEY_PROG4} }, /* Social networking_Key */ + {KE_KEY, 0x29, {KEY_PROG3} }, /* P_Key for TM8372 */ {KE_IGNORE, 0x41, {KEY_MUTE} }, {KE_IGNORE, 0x42, {KEY_PREVIOUSSONG} }, + {KE_IGNORE, 0x4d, {KEY_PREVIOUSSONG} }, {KE_IGNORE, 0x43, {KEY_NEXTSONG} }, + {KE_IGNORE, 0x4e, {KEY_NEXTSONG} }, {KE_IGNORE, 0x44, {KEY_PLAYPAUSE} }, + {KE_IGNORE, 0x4f, {KEY_PLAYPAUSE} }, {KE_IGNORE, 0x45, {KEY_STOP} }, + {KE_IGNORE, 0x50, {KEY_STOP} }, {KE_IGNORE, 0x48, {KEY_VOLUMEUP} }, {KE_IGNORE, 0x49, {KEY_VOLUMEDOWN} }, + {KE_IGNORE, 0x4a, {KEY_VOLUMEDOWN} }, {KE_IGNORE, 0x61, {KEY_SWITCHVIDEOMODE} }, {KE_IGNORE, 0x62, {KEY_BRIGHTNESSUP} }, {KE_IGNORE, 0x63, {KEY_BRIGHTNESSDOWN} }, @@ -153,7 +160,14 @@ struct lm_return_value { u16 reserved; } __attribute__((packed)); -struct wmid3_gds_input_param { /* Get Device Status input parameter */ +struct wmid3_gds_set_input_param { /* Set Device Status input parameter */ + u8 function_num; /* Function Number */ + u8 hotkey_number; /* Hotkey Number */ + u16 devices; /* Set Device */ + u8 volume_value; /* Volume Value */ +} __attribute__((packed)); + +struct wmid3_gds_get_input_param { /* Get Device Status input parameter */ u8 function_num; /* Function Number */ u8 hotkey_number; /* Hotkey Number */ u16 devices; /* Get Device */ @@ -171,6 +185,11 @@ struct hotkey_function_type_aa { u8 length; u16 handle; u16 commun_func_bitmap; + u16 application_func_bitmap; + u16 media_func_bitmap; + u16 display_func_bitmap; + u16 others_func_bitmap; + u8 commun_fn_key_number; } __attribute__((packed)); /* @@ -207,6 +226,7 @@ static int force_series; static bool ec_raw_mode; static bool has_type_aa; static u16 commun_func_bitmap; +static u8 commun_fn_key_number; module_param(mailled, int, 0444); module_param(brightness, int, 0444); @@ -468,6 +488,15 @@ static struct dmi_system_id acer_quirks[] = { }, { .callback = dmi_matched, + .ident = "Lenovo Ideapad S205 (Brazos)", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "Brazos"), + }, + .driver_data = &quirk_lenovo_ideapad_s205, + }, + { + .callback = dmi_matched, .ident = "Lenovo 3000 N200", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), @@ -478,6 +507,25 @@ static struct dmi_system_id acer_quirks[] = { {} }; +static int video_set_backlight_video_vendor(const struct dmi_system_id *d) +{ + interface->capability &= ~ACER_CAP_BRIGHTNESS; + pr_info("Brightness must be controlled by generic video driver\n"); + return 0; +} + +static const struct dmi_system_id video_vendor_dmi_table[] = { + { + .callback = video_set_backlight_video_vendor, + .ident = "Acer TravelMate 4750", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 4750"), + }, + }, + {} +}; + /* Find which quirks are needed for a particular vendor/ model pair */ static void find_quirks(void) { @@ -536,8 +584,7 @@ struct acpi_buffer *result) return status; } -static acpi_status AMW0_get_u32(u32 *value, u32 cap, -struct wmi_interface *iface) +static acpi_status AMW0_get_u32(u32 *value, u32 cap) { int err; u8 result; @@ -607,7 +654,7 @@ struct wmi_interface *iface) return AE_OK; } -static acpi_status AMW0_set_u32(u32 value, u32 cap, struct wmi_interface *iface) +static acpi_status AMW0_set_u32(u32 value, u32 cap) { struct wmab_args args; @@ -692,6 +739,7 @@ static const struct acpi_device_id norfkill_ids[] = { { "VPC2004", 0}, { "IBM0068", 0}, { "LEN0068", 0}, + { "SNY5001", 0}, /* sony-laptop in charge */ { "", 0}, }; @@ -827,8 +875,7 @@ WMI_execute_u32(u32 method_id, u32 in, u32 *out) return status; } -static acpi_status WMID_get_u32(u32 *value, u32 cap, -struct wmi_interface *iface) +static acpi_status WMID_get_u32(u32 *value, u32 cap) { acpi_status status; u8 tmp; @@ -864,7 +911,7 @@ struct wmi_interface *iface) return status; } -static acpi_status WMID_set_u32(u32 value, u32 cap, struct wmi_interface *iface) +static acpi_status WMID_set_u32(u32 value, u32 cap) { u32 method_id = 0; char param; @@ -912,13 +959,13 @@ static acpi_status wmid3_get_device_status(u32 *value, u16 device) struct wmid3_gds_return_value return_value; acpi_status status; union acpi_object *obj; - struct wmid3_gds_input_param params = { + struct wmid3_gds_get_input_param params = { .function_num = 0x1, - .hotkey_number = 0x01, + .hotkey_number = commun_fn_key_number, .devices = device, }; struct acpi_buffer input = { - sizeof(struct wmid3_gds_input_param), + sizeof(struct wmid3_gds_get_input_param), ¶ms }; struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -981,19 +1028,28 @@ static acpi_status wmid3_set_device_status(u32 value, u16 device) acpi_status status; union acpi_object *obj; u16 devices; - struct wmid3_gds_input_param params = { + struct wmid3_gds_get_input_param get_params = { .function_num = 0x1, - .hotkey_number = 0x01, + .hotkey_number = commun_fn_key_number, .devices = commun_func_bitmap, }; - struct acpi_buffer input = { - sizeof(struct wmid3_gds_input_param), - ¶ms + struct acpi_buffer get_input = { + sizeof(struct wmid3_gds_get_input_param), + &get_params + }; + struct wmid3_gds_set_input_param set_params = { + .function_num = 0x2, + .hotkey_number = commun_fn_key_number, + .devices = commun_func_bitmap, + }; + struct acpi_buffer set_input = { + sizeof(struct wmid3_gds_set_input_param), + &set_params }; struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; struct acpi_buffer output2 = { ACPI_ALLOCATE_BUFFER, NULL }; - status = wmi_evaluate_method(WMID_GUID3, 0, 0x2, &input, &output); + status = wmi_evaluate_method(WMID_GUID3, 0, 0x2, &get_input, &output); if (ACPI_FAILURE(status)) return status; @@ -1006,7 +1062,7 @@ static acpi_status wmid3_set_device_status(u32 value, u16 device) return AE_ERROR; } if (obj->buffer.length != 8) { - pr_warning("Unknown buffer length %d\n", obj->buffer.length); + pr_warn("Unknown buffer length %d\n", obj->buffer.length); kfree(obj); return AE_ERROR; } @@ -1015,18 +1071,16 @@ static acpi_status wmid3_set_device_status(u32 value, u16 device) kfree(obj); if (return_value.error_code || return_value.ec_return_value) { - pr_warning("Get Current Device Status failed: " - "0x%x - 0x%x\n", return_value.error_code, + pr_warn("Get Current Device Status failed: 0x%x - 0x%x\n", + return_value.error_code, return_value.ec_return_value); return status; } devices = return_value.devices; - params.function_num = 0x2; - params.hotkey_number = 0x01; - params.devices = (value) ? (devices | device) : (devices & ~device); + set_params.devices = (value) ? (devices | device) : (devices & ~device); - status = wmi_evaluate_method(WMID_GUID3, 0, 0x1, &input, &output2); + status = wmi_evaluate_method(WMID_GUID3, 0, 0x1, &set_input, &output2); if (ACPI_FAILURE(status)) return status; @@ -1039,7 +1093,7 @@ static acpi_status wmid3_set_device_status(u32 value, u16 device) return AE_ERROR; } if (obj->buffer.length != 4) { - pr_warning("Unknown buffer length %d\n", obj->buffer.length); + pr_warn("Unknown buffer length %d\n", obj->buffer.length); kfree(obj); return AE_ERROR; } @@ -1048,8 +1102,8 @@ static acpi_status wmid3_set_device_status(u32 value, u16 device) kfree(obj); if (return_value.error_code || return_value.ec_return_value) - pr_warning("Set Device Status failed: " - "0x%x - 0x%x\n", return_value.error_code, + pr_warn("Set Device Status failed: 0x%x - 0x%x\n", + return_value.error_code, return_value.ec_return_value); return status; @@ -1096,6 +1150,8 @@ static void type_aa_dmi_decode(const struct dmi_header *header, void *dummy) interface->capability |= ACER_CAP_THREEG; if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_BLUETOOTH) interface->capability |= ACER_CAP_BLUETOOTH; + + commun_fn_key_number = type_aa->commun_fn_key_number; } static acpi_status WMID_set_capabilities(void) @@ -1154,15 +1210,15 @@ static acpi_status get_u32(u32 *value, u32 cap) switch (interface->type) { case ACER_AMW0: - status = AMW0_get_u32(value, cap, interface); + status = AMW0_get_u32(value, cap); break; case ACER_AMW0_V2: if (cap == ACER_CAP_MAILLED) { - status = AMW0_get_u32(value, cap, interface); + status = AMW0_get_u32(value, cap); break; } case ACER_WMID: - status = WMID_get_u32(value, cap, interface); + status = WMID_get_u32(value, cap); break; case ACER_WMID_v2: if (cap & (ACER_CAP_WIRELESS | @@ -1170,7 +1226,7 @@ static acpi_status get_u32(u32 *value, u32 cap) ACER_CAP_THREEG)) status = wmid_v2_get_u32(value, cap); else if (wmi_has_guid(WMID_GUID2)) - status = WMID_get_u32(value, cap, interface); + status = WMID_get_u32(value, cap); break; } @@ -1184,10 +1240,10 @@ static acpi_status set_u32(u32 value, u32 cap) if (interface->capability & cap) { switch (interface->type) { case ACER_AMW0: - return AMW0_set_u32(value, cap, interface); + return AMW0_set_u32(value, cap); case ACER_AMW0_V2: if (cap == ACER_CAP_MAILLED) - return AMW0_set_u32(value, cap, interface); + return AMW0_set_u32(value, cap); /* * On some models, some WMID methods don't toggle @@ -1197,21 +1253,21 @@ static acpi_status set_u32(u32 value, u32 cap) */ if (cap == ACER_CAP_WIRELESS || cap == ACER_CAP_BLUETOOTH) { - status = WMID_set_u32(value, cap, interface); + status = WMID_set_u32(value, cap); if (ACPI_FAILURE(status)) return status; - return AMW0_set_u32(value, cap, interface); + return AMW0_set_u32(value, cap); } case ACER_WMID: - return WMID_set_u32(value, cap, interface); + return WMID_set_u32(value, cap); case ACER_WMID_v2: if (cap & (ACER_CAP_WIRELESS | ACER_CAP_BLUETOOTH | ACER_CAP_THREEG)) return wmid_v2_set_u32(value, cap); else if (wmi_has_guid(WMID_GUID2)) - return WMID_set_u32(value, cap, interface); + return WMID_set_u32(value, cap); default: return AE_BAD_PARAMETER; } @@ -1488,8 +1544,8 @@ static ssize_t show_bool_threeg(struct device *dev, u32 result; \ acpi_status status; - pr_info("This threeg sysfs will be removed in 2012" - " - used by: %s\n", current->comm); + pr_info("This threeg sysfs will be removed in 2012 - used by: %s\n", + current->comm); status = get_u32(&result, ACER_CAP_THREEG); if (ACPI_SUCCESS(status)) return sprintf(buf, "%u\n", result); @@ -1501,8 +1557,8 @@ static ssize_t set_bool_threeg(struct device *dev, { u32 tmp = simple_strtoul(buf, NULL, 10); acpi_status status = set_u32(tmp, ACER_CAP_THREEG); - pr_info("This threeg sysfs will be removed in 2012" - " - used by: %s\n", current->comm); + pr_info("This threeg sysfs will be removed in 2012 - used by: %s\n", + current->comm); if (ACPI_FAILURE(status)) return -EINVAL; return count; @@ -1513,8 +1569,8 @@ static DEVICE_ATTR(threeg, S_IRUGO | S_IWUSR, show_bool_threeg, static ssize_t show_interface(struct device *dev, struct device_attribute *attr, char *buf) { - pr_info("This interface sysfs will be removed in 2012" - " - used by: %s\n", current->comm); + pr_info("This interface sysfs will be removed in 2012 - used by: %s\n", + current->comm); switch (interface->type) { case ACER_AMW0: return sprintf(buf, "AMW0\n"); @@ -1981,9 +2037,13 @@ static int __init acer_wmi_init(void) set_quirks(); if (acpi_video_backlight_support()) { - interface->capability &= ~ACER_CAP_BRIGHTNESS; - pr_info("Brightness must be controlled by " - "generic video driver\n"); + if (dmi_check_system(video_vendor_dmi_table)) { + acpi_video_unregister(); + } else { + interface->capability &= ~ACER_CAP_BRIGHTNESS; + pr_info("Brightness must be controlled by " + "acpi video driver\n"); + } } if (wmi_has_guid(WMID_GUID3)) { @@ -2008,7 +2068,7 @@ static int __init acer_wmi_init(void) err = platform_driver_register(&acer_platform_driver); if (err) { - pr_err("Unable to register platform driver.\n"); + pr_err("Unable to register platform driver\n"); goto error_platform_register; } diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c index 760c6d7624fe..bc8384c6f3eb 100644 --- a/drivers/platform/x86/acerhdf.c +++ b/drivers/platform/x86/acerhdf.c @@ -244,12 +244,11 @@ static void acerhdf_change_fanstate(int state) unsigned char cmd; if (verbose) - pr_notice("fan %s\n", (state == ACERHDF_FAN_OFF) ? - "OFF" : "ON"); + pr_notice("fan %s\n", state == ACERHDF_FAN_OFF ? "OFF" : "ON"); if ((state != ACERHDF_FAN_OFF) && (state != ACERHDF_FAN_AUTO)) { pr_err("invalid fan state %d requested, setting to auto!\n", - state); + state); state = ACERHDF_FAN_AUTO; } @@ -264,19 +263,18 @@ static void acerhdf_check_param(struct thermal_zone_device *thermal) { if (fanon > ACERHDF_MAX_FANON) { pr_err("fanon temperature too high, set to %d\n", - ACERHDF_MAX_FANON); + ACERHDF_MAX_FANON); fanon = ACERHDF_MAX_FANON; } if (kernelmode && prev_interval != interval) { if (interval > ACERHDF_MAX_INTERVAL) { pr_err("interval too high, set to %d\n", - ACERHDF_MAX_INTERVAL); + ACERHDF_MAX_INTERVAL); interval = ACERHDF_MAX_INTERVAL; } if (verbose) - pr_notice("interval changed to: %d\n", - interval); + pr_notice("interval changed to: %d\n", interval); thermal->polling_delay = interval*1000; prev_interval = interval; } @@ -587,8 +585,8 @@ static int acerhdf_check_hardware(void) } if (!bios_cfg) { - pr_err("unknown (unsupported) BIOS version %s/%s/%s, " - "please report, aborting!\n", vendor, product, version); + pr_err("unknown (unsupported) BIOS version %s/%s/%s, please report, aborting!\n", + vendor, product, version); return -EINVAL; } @@ -598,8 +596,7 @@ static int acerhdf_check_hardware(void) */ if (!kernelmode) { pr_notice("Fan control off, to enable do:\n"); - pr_notice("echo -n \"enabled\" > " - "/sys/class/thermal/thermal_zone0/mode\n"); + pr_notice("echo -n \"enabled\" > /sys/class/thermal/thermal_zone0/mode\n"); } return 0; diff --git a/drivers/platform/x86/amilo-rfkill.c b/drivers/platform/x86/amilo-rfkill.c index 19170bb7700b..a514bf66fdd7 100644 --- a/drivers/platform/x86/amilo-rfkill.c +++ b/drivers/platform/x86/amilo-rfkill.c @@ -97,9 +97,12 @@ static struct rfkill *amilo_rfkill_dev; static int __devinit amilo_rfkill_probe(struct platform_device *device) { + int rc; const struct dmi_system_id *system_id = dmi_first_match(amilo_rfkill_id_table); - int rc; + + if (!system_id) + return -ENXIO; amilo_rfkill_dev = rfkill_alloc(KBUILD_MODNAME, &device->dev, RFKILL_TYPE_WLAN, diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c new file mode 100644 index 000000000000..8a582bdfdc76 --- /dev/null +++ b/drivers/platform/x86/apple-gmux.c @@ -0,0 +1,244 @@ +/* + * Gmux driver for Apple laptops + * + * Copyright (C) Canonical Ltd. <seth.forshee@canonical.com> + * + * 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/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/backlight.h> +#include <linux/acpi.h> +#include <linux/pnp.h> +#include <linux/apple_bl.h> +#include <linux/slab.h> +#include <acpi/video.h> +#include <asm/io.h> + +struct apple_gmux_data { + unsigned long iostart; + unsigned long iolen; + + struct backlight_device *bdev; +}; + +/* + * gmux port offsets. Many of these are not yet used, but may be in the + * future, and it's useful to have them documented here anyhow. + */ +#define GMUX_PORT_VERSION_MAJOR 0x04 +#define GMUX_PORT_VERSION_MINOR 0x05 +#define GMUX_PORT_VERSION_RELEASE 0x06 +#define GMUX_PORT_SWITCH_DISPLAY 0x10 +#define GMUX_PORT_SWITCH_GET_DISPLAY 0x11 +#define GMUX_PORT_INTERRUPT_ENABLE 0x14 +#define GMUX_PORT_INTERRUPT_STATUS 0x16 +#define GMUX_PORT_SWITCH_DDC 0x28 +#define GMUX_PORT_SWITCH_EXTERNAL 0x40 +#define GMUX_PORT_SWITCH_GET_EXTERNAL 0x41 +#define GMUX_PORT_DISCRETE_POWER 0x50 +#define GMUX_PORT_MAX_BRIGHTNESS 0x70 +#define GMUX_PORT_BRIGHTNESS 0x74 + +#define GMUX_MIN_IO_LEN (GMUX_PORT_BRIGHTNESS + 4) + +#define GMUX_INTERRUPT_ENABLE 0xff +#define GMUX_INTERRUPT_DISABLE 0x00 + +#define GMUX_INTERRUPT_STATUS_ACTIVE 0 +#define GMUX_INTERRUPT_STATUS_DISPLAY (1 << 0) +#define GMUX_INTERRUPT_STATUS_POWER (1 << 2) +#define GMUX_INTERRUPT_STATUS_HOTPLUG (1 << 3) + +#define GMUX_BRIGHTNESS_MASK 0x00ffffff +#define GMUX_MAX_BRIGHTNESS GMUX_BRIGHTNESS_MASK + +static inline u8 gmux_read8(struct apple_gmux_data *gmux_data, int port) +{ + return inb(gmux_data->iostart + port); +} + +static inline void gmux_write8(struct apple_gmux_data *gmux_data, int port, + u8 val) +{ + outb(val, gmux_data->iostart + port); +} + +static inline u32 gmux_read32(struct apple_gmux_data *gmux_data, int port) +{ + return inl(gmux_data->iostart + port); +} + +static int gmux_get_brightness(struct backlight_device *bd) +{ + struct apple_gmux_data *gmux_data = bl_get_data(bd); + return gmux_read32(gmux_data, GMUX_PORT_BRIGHTNESS) & + GMUX_BRIGHTNESS_MASK; +} + +static int gmux_update_status(struct backlight_device *bd) +{ + struct apple_gmux_data *gmux_data = bl_get_data(bd); + u32 brightness = bd->props.brightness; + + /* + * Older gmux versions require writing out lower bytes first then + * setting the upper byte to 0 to flush the values. Newer versions + * accept a single u32 write, but the old method also works, so we + * just use the old method for all gmux versions. + */ + gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS, brightness); + gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS + 1, brightness >> 8); + gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS + 2, brightness >> 16); + gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS + 3, 0); + + return 0; +} + +static const struct backlight_ops gmux_bl_ops = { + .get_brightness = gmux_get_brightness, + .update_status = gmux_update_status, +}; + +static int __devinit gmux_probe(struct pnp_dev *pnp, + const struct pnp_device_id *id) +{ + struct apple_gmux_data *gmux_data; + struct resource *res; + struct backlight_properties props; + struct backlight_device *bdev; + u8 ver_major, ver_minor, ver_release; + int ret = -ENXIO; + + gmux_data = kzalloc(sizeof(*gmux_data), GFP_KERNEL); + if (!gmux_data) + return -ENOMEM; + pnp_set_drvdata(pnp, gmux_data); + + res = pnp_get_resource(pnp, IORESOURCE_IO, 0); + if (!res) { + pr_err("Failed to find gmux I/O resource\n"); + goto err_free; + } + + gmux_data->iostart = res->start; + gmux_data->iolen = res->end - res->start; + + if (gmux_data->iolen < GMUX_MIN_IO_LEN) { + pr_err("gmux I/O region too small (%lu < %u)\n", + gmux_data->iolen, GMUX_MIN_IO_LEN); + goto err_free; + } + + if (!request_region(gmux_data->iostart, gmux_data->iolen, + "Apple gmux")) { + pr_err("gmux I/O already in use\n"); + goto err_free; + } + + /* + * On some machines the gmux is in ACPI even thought the machine + * doesn't really have a gmux. Check for invalid version information + * to detect this. + */ + ver_major = gmux_read8(gmux_data, GMUX_PORT_VERSION_MAJOR); + ver_minor = gmux_read8(gmux_data, GMUX_PORT_VERSION_MINOR); + ver_release = gmux_read8(gmux_data, GMUX_PORT_VERSION_RELEASE); + if (ver_major == 0xff && ver_minor == 0xff && ver_release == 0xff) { + pr_info("gmux device not present\n"); + ret = -ENODEV; + goto err_release; + } + + pr_info("Found gmux version %d.%d.%d\n", ver_major, ver_minor, + ver_release); + + memset(&props, 0, sizeof(props)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = gmux_read32(gmux_data, GMUX_PORT_MAX_BRIGHTNESS); + + /* + * Currently it's assumed that the maximum brightness is less than + * 2^24 for compatibility with old gmux versions. Cap the max + * brightness at this value, but print a warning if the hardware + * reports something higher so that it can be fixed. + */ + if (WARN_ON(props.max_brightness > GMUX_MAX_BRIGHTNESS)) + props.max_brightness = GMUX_MAX_BRIGHTNESS; + + bdev = backlight_device_register("gmux_backlight", &pnp->dev, + gmux_data, &gmux_bl_ops, &props); + if (IS_ERR(bdev)) { + ret = PTR_ERR(bdev); + goto err_release; + } + + gmux_data->bdev = bdev; + bdev->props.brightness = gmux_get_brightness(bdev); + backlight_update_status(bdev); + + /* + * The backlight situation on Macs is complicated. If the gmux is + * present it's the best choice, because it always works for + * backlight control and supports more levels than other options. + * Disable the other backlight choices. + */ + acpi_video_unregister(); + apple_bl_unregister(); + + return 0; + +err_release: + release_region(gmux_data->iostart, gmux_data->iolen); +err_free: + kfree(gmux_data); + return ret; +} + +static void __devexit gmux_remove(struct pnp_dev *pnp) +{ + struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp); + + backlight_device_unregister(gmux_data->bdev); + release_region(gmux_data->iostart, gmux_data->iolen); + kfree(gmux_data); + + acpi_video_register(); + apple_bl_register(); +} + +static const struct pnp_device_id gmux_device_ids[] = { + {"APP000B", 0}, + {"", 0} +}; + +static struct pnp_driver gmux_pnp_driver = { + .name = "apple-gmux", + .probe = gmux_probe, + .remove = __devexit_p(gmux_remove), + .id_table = gmux_device_ids, +}; + +static int __init apple_gmux_init(void) +{ + return pnp_register_driver(&gmux_pnp_driver); +} + +static void __exit apple_gmux_exit(void) +{ + pnp_unregister_driver(&gmux_pnp_driver); +} + +module_init(apple_gmux_init); +module_exit(apple_gmux_exit); + +MODULE_AUTHOR("Seth Forshee <seth.forshee@canonical.com>"); +MODULE_DESCRIPTION("Apple Gmux Driver"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(pnp, gmux_device_ids); diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index b7944f903886..e38f91be0b10 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -81,6 +81,19 @@ static uint wapf = 1; module_param(wapf, uint, 0444); MODULE_PARM_DESC(wapf, "WAPF value"); +static char *wled_type = "unknown"; +static char *bled_type = "unknown"; + +module_param(wled_type, charp, 0444); +MODULE_PARM_DESC(wlan_status, "Set the wled type on boot " + "(unknown, led or rfkill). " + "default is unknown"); + +module_param(bled_type, charp, 0444); +MODULE_PARM_DESC(bled_type, "Set the bled type on boot " + "(unknown, led or rfkill). " + "default is unknown"); + static int wlan_status = 1; static int bluetooth_status = 1; static int wimax_status = -1; @@ -137,6 +150,11 @@ MODULE_PARM_DESC(als_status, "Set the ALS status on boot " #define WM_RSTS 0x08 /* internal wimax */ #define WW_RSTS 0x20 /* internal wwan */ +/* WLED and BLED type */ +#define TYPE_UNKNOWN 0 +#define TYPE_LED 1 +#define TYPE_RFKILL 2 + /* LED */ #define METHOD_MLED "MLED" #define METHOD_TLED "TLED" @@ -218,8 +236,9 @@ struct asus_led { /* * Same thing for rfkill */ -struct asus_pega_rfkill { - int control_id; /* type of control. Maps to PEGA_* values */ +struct asus_rfkill { + /* type of control. Maps to PEGA_* values or *_RSTS */ + int control_id; struct rfkill *rfkill; struct asus_laptop *asus; }; @@ -240,6 +259,8 @@ struct asus_laptop { struct key_entry *keymap; struct input_polled_dev *pega_accel_poll; + struct asus_led wled; + struct asus_led bled; struct asus_led mled; struct asus_led tled; struct asus_led rled; @@ -248,6 +269,8 @@ struct asus_laptop { struct asus_led kled; struct workqueue_struct *led_workqueue; + int wled_type; + int bled_type; int wireless_status; bool have_rsts; bool is_pega_lucid; @@ -256,11 +279,11 @@ struct asus_laptop { int pega_acc_y; int pega_acc_z; - struct rfkill *gps_rfkill; - - struct asus_pega_rfkill wlanrfk; - struct asus_pega_rfkill btrfk; - struct asus_pega_rfkill wwanrfk; + struct asus_rfkill wlan; + struct asus_rfkill bluetooth; + struct asus_rfkill wwan; + struct asus_rfkill wimax; + struct asus_rfkill gps; acpi_handle handle; /* the handle of the hotk device */ u32 ledd_status; /* status of the LED display */ @@ -274,6 +297,7 @@ static const struct key_entry asus_keymap[] = { {KE_KEY, 0x02, { KEY_SCREENLOCK } }, {KE_KEY, 0x05, { KEY_WLAN } }, {KE_KEY, 0x08, { KEY_F13 } }, + {KE_KEY, 0x09, { KEY_PROG2 } }, /* Dock */ {KE_KEY, 0x17, { KEY_ZOOM } }, {KE_KEY, 0x1f, { KEY_BATTERY } }, /* End of Lenovo SL Specific keycodes */ @@ -299,6 +323,8 @@ static const struct key_entry asus_keymap[] = { {KE_KEY, 0x62, { KEY_SWITCHVIDEOMODE } }, {KE_KEY, 0x63, { KEY_SWITCHVIDEOMODE } }, {KE_KEY, 0x6B, { KEY_F13 } }, /* Lock Touchpad */ + {KE_KEY, 0x6C, { KEY_SLEEP } }, /* Suspend */ + {KE_KEY, 0x6D, { KEY_SLEEP } }, /* Hibernate */ {KE_KEY, 0x7E, { KEY_BLUETOOTH } }, {KE_KEY, 0x7D, { KEY_BLUETOOTH } }, {KE_KEY, 0x82, { KEY_CAMERA } }, @@ -601,6 +627,10 @@ static enum led_brightness asus_kled_cdev_get(struct led_classdev *led_cdev) static void asus_led_exit(struct asus_laptop *asus) { + if (!IS_ERR_OR_NULL(asus->wled.led.dev)) + led_classdev_unregister(&asus->wled.led); + if (!IS_ERR_OR_NULL(asus->bled.led.dev)) + led_classdev_unregister(&asus->bled.led); if (!IS_ERR_OR_NULL(asus->mled.led.dev)) led_classdev_unregister(&asus->mled.led); if (!IS_ERR_OR_NULL(asus->tled.led.dev)) @@ -642,7 +672,7 @@ static int asus_led_register(struct asus_laptop *asus, static int asus_led_init(struct asus_laptop *asus) { - int r; + int r = 0; /* * The Pegatron Lucid has no physical leds, but all methods are @@ -661,6 +691,16 @@ static int asus_led_init(struct asus_laptop *asus) if (!asus->led_workqueue) return -ENOMEM; + if (asus->wled_type == TYPE_LED) + r = asus_led_register(asus, &asus->wled, "asus::wlan", + METHOD_WLAN); + if (r) + goto error; + if (asus->bled_type == TYPE_LED) + r = asus_led_register(asus, &asus->bled, "asus::bluetooth", + METHOD_BLUETOOTH); + if (r) + goto error; r = asus_led_register(asus, &asus->mled, "asus::mail", METHOD_MLED); if (r) goto error; @@ -963,7 +1003,7 @@ static ssize_t store_wlan(struct device *dev, struct device_attribute *attr, return sysfs_acpi_set(asus, buf, count, METHOD_WLAN); } -/* +/*e * Bluetooth */ static int asus_bluetooth_set(struct asus_laptop *asus, int status) @@ -1228,7 +1268,7 @@ static ssize_t store_gps(struct device *dev, struct device_attribute *attr, ret = asus_gps_switch(asus, !!value); if (ret) return ret; - rfkill_set_sw_state(asus->gps_rfkill, !value); + rfkill_set_sw_state(asus->gps.rfkill, !value); return rv; } @@ -1246,90 +1286,139 @@ static const struct rfkill_ops asus_gps_rfkill_ops = { .set_block = asus_gps_rfkill_set, }; +static int asus_rfkill_set(void *data, bool blocked) +{ + struct asus_rfkill *rfk = data; + struct asus_laptop *asus = rfk->asus; + + if (rfk->control_id == WL_RSTS) + return asus_wlan_set(asus, !blocked); + else if (rfk->control_id == BT_RSTS) + return asus_bluetooth_set(asus, !blocked); + else if (rfk->control_id == WM_RSTS) + return asus_wimax_set(asus, !blocked); + else if (rfk->control_id == WW_RSTS) + return asus_wwan_set(asus, !blocked); + + return -EINVAL; +} + +static const struct rfkill_ops asus_rfkill_ops = { + .set_block = asus_rfkill_set, +}; + +static void asus_rfkill_terminate(struct asus_rfkill *rfk) +{ + if (!rfk->rfkill) + return ; + + rfkill_unregister(rfk->rfkill); + rfkill_destroy(rfk->rfkill); + rfk->rfkill = NULL; +} + static void asus_rfkill_exit(struct asus_laptop *asus) { - if (asus->gps_rfkill) { - rfkill_unregister(asus->gps_rfkill); - rfkill_destroy(asus->gps_rfkill); - asus->gps_rfkill = NULL; - } + asus_rfkill_terminate(&asus->wwan); + asus_rfkill_terminate(&asus->bluetooth); + asus_rfkill_terminate(&asus->wlan); + asus_rfkill_terminate(&asus->gps); } -static int asus_rfkill_init(struct asus_laptop *asus) +static int asus_rfkill_setup(struct asus_laptop *asus, struct asus_rfkill *rfk, + const char *name, int control_id, int type, + const struct rfkill_ops *ops) { int result; - if (acpi_check_handle(asus->handle, METHOD_GPS_ON, NULL) || - acpi_check_handle(asus->handle, METHOD_GPS_OFF, NULL) || - acpi_check_handle(asus->handle, METHOD_GPS_STATUS, NULL)) - return 0; - - asus->gps_rfkill = rfkill_alloc("asus-gps", &asus->platform_device->dev, - RFKILL_TYPE_GPS, - &asus_gps_rfkill_ops, asus); - if (!asus->gps_rfkill) + rfk->control_id = control_id; + rfk->asus = asus; + rfk->rfkill = rfkill_alloc(name, &asus->platform_device->dev, + type, ops, rfk); + if (!rfk->rfkill) return -EINVAL; - result = rfkill_register(asus->gps_rfkill); + result = rfkill_register(rfk->rfkill); if (result) { - rfkill_destroy(asus->gps_rfkill); - asus->gps_rfkill = NULL; + rfkill_destroy(rfk->rfkill); + rfk->rfkill = NULL; } return result; } -static int pega_rfkill_set(void *data, bool blocked) +static int asus_rfkill_init(struct asus_laptop *asus) { - struct asus_pega_rfkill *pega_rfk = data; + int result = 0; - int ret = asus_pega_lucid_set(pega_rfk->asus, pega_rfk->control_id, !blocked); - pr_warn("Setting rfkill %d, to %d; returned %d\n", pega_rfk->control_id, !blocked, ret); + if (asus->is_pega_lucid) + return -ENODEV; - return ret; -} + if (!acpi_check_handle(asus->handle, METHOD_GPS_ON, NULL) && + !acpi_check_handle(asus->handle, METHOD_GPS_OFF, NULL) && + !acpi_check_handle(asus->handle, METHOD_GPS_STATUS, NULL)) + result = asus_rfkill_setup(asus, &asus->gps, "asus-gps", + -1, RFKILL_TYPE_GPS, + &asus_gps_rfkill_ops); + if (result) + goto exit; -static const struct rfkill_ops pega_rfkill_ops = { - .set_block = pega_rfkill_set, -}; -static void pega_rfkill_terminate(struct asus_pega_rfkill *pega_rfk) -{ - pr_warn("Terminating %d\n", pega_rfk->control_id); - if (pega_rfk->rfkill) { - rfkill_unregister(pega_rfk->rfkill); - rfkill_destroy(pega_rfk->rfkill); - pega_rfk->rfkill = NULL; - } -} + if (!acpi_check_handle(asus->handle, METHOD_WLAN, NULL) && + asus->wled_type == TYPE_RFKILL) + result = asus_rfkill_setup(asus, &asus->wlan, "asus-wlan", + WL_RSTS, RFKILL_TYPE_WLAN, + &asus_rfkill_ops); + if (result) + goto exit; -static void pega_rfkill_exit(struct asus_laptop *asus) -{ - pega_rfkill_terminate(&asus->wwanrfk); - pega_rfkill_terminate(&asus->btrfk); - pega_rfkill_terminate(&asus->wlanrfk); + if (!acpi_check_handle(asus->handle, METHOD_BLUETOOTH, NULL) && + asus->bled_type == TYPE_RFKILL) + result = asus_rfkill_setup(asus, &asus->bluetooth, + "asus-bluetooth", BT_RSTS, + RFKILL_TYPE_BLUETOOTH, + &asus_rfkill_ops); + if (result) + goto exit; + + if (!acpi_check_handle(asus->handle, METHOD_WWAN, NULL)) + result = asus_rfkill_setup(asus, &asus->wwan, "asus-wwan", + WW_RSTS, RFKILL_TYPE_WWAN, + &asus_rfkill_ops); + if (result) + goto exit; + + if (!acpi_check_handle(asus->handle, METHOD_WIMAX, NULL)) + result = asus_rfkill_setup(asus, &asus->wimax, "asus-wimax", + WM_RSTS, RFKILL_TYPE_WIMAX, + &asus_rfkill_ops); + if (result) + goto exit; + +exit: + if (result) + asus_rfkill_exit(asus); + + return result; } -static int pega_rfkill_setup(struct asus_laptop *asus, struct asus_pega_rfkill *pega_rfk, - const char *name, int controlid, int rfkill_type) +static int pega_rfkill_set(void *data, bool blocked) { - int result; + struct asus_rfkill *rfk = data; - pr_warn("Setting up rfk %s, control %d, type %d\n", name, controlid, rfkill_type); - pega_rfk->control_id = controlid; - pega_rfk->asus = asus; - pega_rfk->rfkill = rfkill_alloc(name, &asus->platform_device->dev, - rfkill_type, &pega_rfkill_ops, pega_rfk); - if (!pega_rfk->rfkill) - return -EINVAL; + int ret = asus_pega_lucid_set(rfk->asus, rfk->control_id, !blocked); + return ret; +} - result = rfkill_register(pega_rfk->rfkill); - if (result) { - rfkill_destroy(pega_rfk->rfkill); - pega_rfk->rfkill = NULL; - } +static const struct rfkill_ops pega_rfkill_ops = { + .set_block = pega_rfkill_set, +}; - return result; +static int pega_rfkill_setup(struct asus_laptop *asus, struct asus_rfkill *rfk, + const char *name, int controlid, int rfkill_type) +{ + return asus_rfkill_setup(asus, rfk, name, controlid, rfkill_type, + &pega_rfkill_ops); } static int pega_rfkill_init(struct asus_laptop *asus) @@ -1339,22 +1428,22 @@ static int pega_rfkill_init(struct asus_laptop *asus) if(!asus->is_pega_lucid) return -ENODEV; - ret = pega_rfkill_setup(asus, &asus->wlanrfk, "pega-wlan", PEGA_WLAN, RFKILL_TYPE_WLAN); - if(ret) - return ret; - ret = pega_rfkill_setup(asus, &asus->btrfk, "pega-bt", PEGA_BLUETOOTH, RFKILL_TYPE_BLUETOOTH); + ret = pega_rfkill_setup(asus, &asus->wlan, "pega-wlan", + PEGA_WLAN, RFKILL_TYPE_WLAN); if(ret) - goto err_btrfk; - ret = pega_rfkill_setup(asus, &asus->wwanrfk, "pega-wwan", PEGA_WWAN, RFKILL_TYPE_WWAN); + goto exit; + + ret = pega_rfkill_setup(asus, &asus->bluetooth, "pega-bt", + PEGA_BLUETOOTH, RFKILL_TYPE_BLUETOOTH); if(ret) - goto err_wwanrfk; + goto exit; - pr_warn("Pega rfkill init succeeded\n"); - return 0; -err_wwanrfk: - pega_rfkill_terminate(&asus->btrfk); -err_btrfk: - pega_rfkill_terminate(&asus->wlanrfk); + ret = pega_rfkill_setup(asus, &asus->wwan, "pega-wwan", + PEGA_WWAN, RFKILL_TYPE_WWAN); + +exit: + if (ret) + asus_rfkill_exit(asus); return ret; } @@ -1364,8 +1453,10 @@ err_btrfk: */ static void asus_input_notify(struct asus_laptop *asus, int event) { - if (asus->inputdev) - sparse_keymap_report_event(asus->inputdev, event, 1, true); + if (!asus->inputdev) + return ; + if (!sparse_keymap_report_event(asus->inputdev, event, 1, true)) + pr_info("Unknown key %x pressed\n", event); } static int asus_input_init(struct asus_laptop *asus) @@ -1375,7 +1466,7 @@ static int asus_input_init(struct asus_laptop *asus) input = input_allocate_device(); if (!input) { - pr_info("Unable to allocate input device\n"); + pr_warn("Unable to allocate input device\n"); return -ENOMEM; } input->name = "Asus Laptop extra buttons"; @@ -1390,7 +1481,7 @@ static int asus_input_init(struct asus_laptop *asus) } error = input_register_device(input); if (error) { - pr_info("Unable to register input device\n"); + pr_warn("Unable to register input device\n"); goto err_free_keymap; } @@ -1688,7 +1779,16 @@ static int __devinit asus_acpi_init(struct asus_laptop *asus) if (result) return result; - /* WLED and BLED are on by default */ + if (!strcmp(bled_type, "led")) + asus->bled_type = TYPE_LED; + else if (!strcmp(bled_type, "rfkill")) + asus->bled_type = TYPE_RFKILL; + + if (!strcmp(wled_type, "led")) + asus->wled_type = TYPE_LED; + else if (!strcmp(wled_type, "rfkill")) + asus->wled_type = TYPE_RFKILL; + if (bluetooth_status >= 0) asus_bluetooth_set(asus, !!bluetooth_status); @@ -1786,7 +1886,7 @@ static int __devinit asus_acpi_add(struct acpi_device *device) goto fail_led; result = asus_rfkill_init(asus); - if (result) + if (result && result != -ENODEV) goto fail_rfkill; result = pega_accel_init(asus); @@ -1828,7 +1928,6 @@ static int asus_acpi_remove(struct acpi_device *device, int type) asus_led_exit(asus); asus_input_exit(asus); pega_accel_exit(asus); - pega_rfkill_exit(asus); asus_platform_exit(asus); kfree(asus->name); diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index b0859d4183e8..99a30b513137 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -25,6 +25,7 @@ #include <linux/init.h> #include <linux/input.h> #include <linux/input/sparse-keymap.h> +#include <linux/fb.h> #include "asus-wmi.h" @@ -51,9 +52,14 @@ static uint wapf; module_param(wapf, uint, 0444); MODULE_PARM_DESC(wapf, "WAPF value"); +static struct quirk_entry quirk_asus_unknown = { +}; + static void asus_nb_wmi_quirks(struct asus_wmi_driver *driver) { - driver->wapf = wapf; + driver->quirks = &quirk_asus_unknown; + driver->quirks->wapf = wapf; + driver->panel_power = FB_BLANK_UNBLANK; } static const struct key_entry asus_nb_wmi_keymap[] = { @@ -70,6 +76,8 @@ static const struct key_entry asus_nb_wmi_keymap[] = { { KE_KEY, 0x50, { KEY_EMAIL } }, { KE_KEY, 0x51, { KEY_WWW } }, { KE_KEY, 0x55, { KEY_CALC } }, + { KE_IGNORE, 0x57, }, /* Battery mode */ + { KE_IGNORE, 0x58, }, /* AC mode */ { KE_KEY, 0x5C, { KEY_F15 } }, /* Power Gear key */ { KE_KEY, 0x5D, { KEY_WLAN } }, /* Wireless console Toggle */ { KE_KEY, 0x5E, { KEY_WLAN } }, /* Wireless console Enable */ @@ -99,7 +107,7 @@ static struct asus_wmi_driver asus_nb_wmi_driver = { .keymap = asus_nb_wmi_keymap, .input_name = "Asus WMI hotkeys", .input_phys = ASUS_NB_WMI_FILE "/input0", - .quirks = asus_nb_wmi_quirks, + .detect_quirks = asus_nb_wmi_quirks, }; diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 9929246895de..77aadde5281c 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -411,7 +411,7 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env) if (retval >= 0) { if (level) - *level = retval & 0x80 ? retval & 0x7F : 0; + *level = retval & 0x7F; if (env) *env = (retval >> 8) & 0x7F; retval = 0; @@ -784,7 +784,8 @@ static int asus_new_rfkill(struct asus_wmi *asus, arfkill->dev_id = dev_id; arfkill->asus = asus; - if (dev_id == ASUS_WMI_DEVID_WLAN && asus->driver->hotplug_wireless) + if (dev_id == ASUS_WMI_DEVID_WLAN && + asus->driver->quirks->hotplug_wireless) *rfkill = rfkill_alloc(name, &asus->platform_device->dev, type, &asus_rfkill_wlan_ops, arfkill); else @@ -895,7 +896,7 @@ static int asus_wmi_rfkill_init(struct asus_wmi *asus) if (result && result != -ENODEV) goto exit; - if (!asus->driver->hotplug_wireless) + if (!asus->driver->quirks->hotplug_wireless) goto exit; result = asus_setup_pci_hotplug(asus); @@ -1075,7 +1076,12 @@ static int asus_wmi_hwmon_init(struct asus_wmi *asus) */ static int read_backlight_power(struct asus_wmi *asus) { - int ret = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_BACKLIGHT); + int ret; + if (asus->driver->quirks->store_backlight_power) + ret = !asus->driver->panel_power; + else + ret = asus_wmi_get_devstate_simple(asus, + ASUS_WMI_DEVID_BACKLIGHT); if (ret < 0) return ret; @@ -1116,26 +1122,51 @@ static int read_brightness(struct backlight_device *bd) return retval & ASUS_WMI_DSTS_BRIGHTNESS_MASK; } -static int update_bl_status(struct backlight_device *bd) +static u32 get_scalar_command(struct backlight_device *bd) { struct asus_wmi *asus = bl_get_data(bd); - u32 ctrl_param; - int power, err; + u32 ctrl_param = 0; - ctrl_param = bd->props.brightness; + if ((asus->driver->brightness < bd->props.brightness) || + bd->props.brightness == bd->props.max_brightness) + ctrl_param = 0x00008001; + else if ((asus->driver->brightness > bd->props.brightness) || + bd->props.brightness == 0) + ctrl_param = 0x00008000; - err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BRIGHTNESS, - ctrl_param, NULL); + asus->driver->brightness = bd->props.brightness; - if (err < 0) - return err; + return ctrl_param; +} + +static int update_bl_status(struct backlight_device *bd) +{ + struct asus_wmi *asus = bl_get_data(bd); + u32 ctrl_param; + int power, err = 0; power = read_backlight_power(asus); if (power != -ENODEV && bd->props.power != power) { ctrl_param = !!(bd->props.power == FB_BLANK_UNBLANK); err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BACKLIGHT, ctrl_param, NULL); + if (asus->driver->quirks->store_backlight_power) + asus->driver->panel_power = bd->props.power; + + /* When using scalar brightness, updating the brightness + * will mess with the backlight power */ + if (asus->driver->quirks->scalar_panel_brightness) + return err; } + + if (asus->driver->quirks->scalar_panel_brightness) + ctrl_param = get_scalar_command(bd); + else + ctrl_param = bd->props.brightness; + + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BRIGHTNESS, + ctrl_param, NULL); + return err; } @@ -1196,10 +1227,15 @@ static int asus_wmi_backlight_init(struct asus_wmi *asus) asus->backlight_device = bd; + if (asus->driver->quirks->store_backlight_power) + asus->driver->panel_power = power; + bd->props.brightness = read_brightness(bd); bd->props.power = power; backlight_update_status(bd); + asus->driver->brightness = bd->props.brightness; + return 0; } @@ -1441,9 +1477,9 @@ static int asus_wmi_platform_init(struct asus_wmi *asus) /* CWAP allow to define the behavior of the Fn+F2 key, * this method doesn't seems to be present on Eee PCs */ - if (asus->driver->wapf >= 0) + if (asus->driver->quirks->wapf >= 0) asus_wmi_set_devstate(ASUS_WMI_DEVID_CWAP, - asus->driver->wapf, NULL); + asus->driver->quirks->wapf, NULL); return asus_wmi_sysfs_init(asus->platform_device); } @@ -1622,8 +1658,8 @@ static int asus_wmi_add(struct platform_device *pdev) wdrv->platform_device = pdev; platform_set_drvdata(asus->platform_device, asus); - if (wdrv->quirks) - wdrv->quirks(asus->driver); + if (wdrv->detect_quirks) + wdrv->detect_quirks(asus->driver); err = asus_wmi_platform_init(asus); if (err) diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h index 8147c10161cc..d43b66742004 100644 --- a/drivers/platform/x86/asus-wmi.h +++ b/drivers/platform/x86/asus-wmi.h @@ -35,9 +35,16 @@ struct module; struct key_entry; struct asus_wmi; +struct quirk_entry { + bool hotplug_wireless; + bool scalar_panel_brightness; + bool store_backlight_power; + int wapf; +}; + struct asus_wmi_driver { - bool hotplug_wireless; - int wapf; + int brightness; + int panel_power; const char *name; struct module *owner; @@ -47,13 +54,14 @@ struct asus_wmi_driver { const struct key_entry *keymap; const char *input_name; const char *input_phys; + struct quirk_entry *quirks; /* Returns new code, value, and autorelease values in arguments. * Return ASUS_WMI_KEY_IGNORE in code if event should be ignored. */ void (*key_filter) (struct asus_wmi_driver *driver, int *code, unsigned int *value, bool *autorelease); int (*probe) (struct platform_device *device); - void (*quirks) (struct asus_wmi_driver *driver); + void (*detect_quirks) (struct asus_wmi_driver *driver); struct platform_driver platform_driver; struct platform_device *platform_device; diff --git a/drivers/platform/x86/asus_acpi.c b/drivers/platform/x86/asus_acpi.c deleted file mode 100644 index 6f966d6c062b..000000000000 --- a/drivers/platform/x86/asus_acpi.c +++ /dev/null @@ -1,1513 +0,0 @@ -/* - * asus_acpi.c - Asus Laptop ACPI Extras - * - * - * Copyright (C) 2002-2005 Julien Lerouge, 2003-2006 Karol Kozimor - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * - * The development page for this driver is located at - * http://sourceforge.net/projects/acpi4asus/ - * - * Credits: - * Pontus Fuchs - Helper functions, cleanup - * Johann Wiesner - Small compile fixes - * John Belmonte - ACPI code for Toshiba laptop was a good starting point. - * �ic Burghard - LED display support for W1N - * - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/slab.h> -#include <linux/init.h> -#include <linux/types.h> -#include <linux/proc_fs.h> -#include <linux/seq_file.h> -#include <linux/backlight.h> -#include <acpi/acpi_drivers.h> -#include <acpi/acpi_bus.h> -#include <asm/uaccess.h> - -#define ASUS_ACPI_VERSION "0.30" - -#define PROC_ASUS "asus" /* The directory */ -#define PROC_MLED "mled" -#define PROC_WLED "wled" -#define PROC_TLED "tled" -#define PROC_BT "bluetooth" -#define PROC_LEDD "ledd" -#define PROC_INFO "info" -#define PROC_LCD "lcd" -#define PROC_BRN "brn" -#define PROC_DISP "disp" - -#define ACPI_HOTK_NAME "Asus Laptop ACPI Extras Driver" -#define ACPI_HOTK_CLASS "hotkey" -#define ACPI_HOTK_DEVICE_NAME "Hotkey" - -/* - * Some events we use, same for all Asus - */ -#define BR_UP 0x10 -#define BR_DOWN 0x20 - -/* - * Flags for hotk status - */ -#define MLED_ON 0x01 /* Mail LED */ -#define WLED_ON 0x02 /* Wireless LED */ -#define TLED_ON 0x04 /* Touchpad LED */ -#define BT_ON 0x08 /* Internal Bluetooth */ - -MODULE_AUTHOR("Julien Lerouge, Karol Kozimor"); -MODULE_DESCRIPTION(ACPI_HOTK_NAME); -MODULE_LICENSE("GPL"); - -static uid_t asus_uid; -static gid_t asus_gid; -module_param(asus_uid, uint, 0); -MODULE_PARM_DESC(asus_uid, "UID for entries in /proc/acpi/asus"); -module_param(asus_gid, uint, 0); -MODULE_PARM_DESC(asus_gid, "GID for entries in /proc/acpi/asus"); - -/* For each model, all features implemented, - * those marked with R are relative to HOTK, A for absolute */ -struct model_data { - char *name; /* name of the laptop________________A */ - char *mt_mled; /* method to handle mled_____________R */ - char *mled_status; /* node to handle mled reading_______A */ - char *mt_wled; /* method to handle wled_____________R */ - char *wled_status; /* node to handle wled reading_______A */ - char *mt_tled; /* method to handle tled_____________R */ - char *tled_status; /* node to handle tled reading_______A */ - char *mt_ledd; /* method to handle LED display______R */ - char *mt_bt_switch; /* method to switch Bluetooth on/off_R */ - char *bt_status; /* no model currently supports this__? */ - char *mt_lcd_switch; /* method to turn LCD on/off_________A */ - char *lcd_status; /* node to read LCD panel state______A */ - char *brightness_up; /* method to set brightness up_______A */ - char *brightness_down; /* method to set brightness down ____A */ - char *brightness_set; /* method to set absolute brightness_R */ - char *brightness_get; /* method to get absolute brightness_R */ - char *brightness_status;/* node to get brightness____________A */ - char *display_set; /* method to set video output________R */ - char *display_get; /* method to get video output________R */ -}; - -/* - * This is the main structure, we can use it to store anything interesting - * about the hotk device - */ -struct asus_hotk { - struct acpi_device *device; /* the device we are in */ - acpi_handle handle; /* the handle of the hotk device */ - char status; /* status of the hotk, for LEDs */ - u32 ledd_status; /* status of the LED display */ - struct model_data *methods; /* methods available on the laptop */ - u8 brightness; /* brightness level */ - enum { - A1x = 0, /* A1340D, A1300F */ - A2x, /* A2500H */ - A4G, /* A4700G */ - D1x, /* D1 */ - L2D, /* L2000D */ - L3C, /* L3800C */ - L3D, /* L3400D */ - L3H, /* L3H, L2000E, L5D */ - L4R, /* L4500R */ - L5x, /* L5800C */ - L8L, /* L8400L */ - M1A, /* M1300A */ - M2E, /* M2400E, L4400L */ - M6N, /* M6800N, W3400N */ - M6R, /* M6700R, A3000G */ - P30, /* Samsung P30 */ - S1x, /* S1300A, but also L1400B and M2400A (L84F) */ - S2x, /* S200 (J1 reported), Victor MP-XP7210 */ - W1N, /* W1000N */ - W5A, /* W5A */ - W3V, /* W3030V */ - xxN, /* M2400N, M3700N, M5200N, M6800N, - S1300N, S5200N*/ - A4S, /* Z81sp */ - F3Sa, /* (Centrino) */ - R1F, - END_MODEL - } model; /* Models currently supported */ - u16 event_count[128]; /* Count for each event TODO make this better */ -}; - -/* Here we go */ -#define A1x_PREFIX "\\_SB.PCI0.ISA.EC0." -#define L3C_PREFIX "\\_SB.PCI0.PX40.ECD0." -#define M1A_PREFIX "\\_SB.PCI0.PX40.EC0." -#define P30_PREFIX "\\_SB.PCI0.LPCB.EC0." -#define S1x_PREFIX "\\_SB.PCI0.PX40." -#define S2x_PREFIX A1x_PREFIX -#define xxN_PREFIX "\\_SB.PCI0.SBRG.EC0." - -static struct model_data model_conf[END_MODEL] = { - /* - * TODO I have seen a SWBX and AIBX method on some models, like L1400B, - * it seems to be a kind of switch, but what for ? - */ - - { - .name = "A1x", - .mt_mled = "MLED", - .mled_status = "\\MAIL", - .mt_lcd_switch = A1x_PREFIX "_Q10", - .lcd_status = "\\BKLI", - .brightness_up = A1x_PREFIX "_Q0E", - .brightness_down = A1x_PREFIX "_Q0F"}, - - { - .name = "A2x", - .mt_mled = "MLED", - .mt_wled = "WLED", - .wled_status = "\\SG66", - .mt_lcd_switch = "\\Q10", - .lcd_status = "\\BAOF", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "A4G", - .mt_mled = "MLED", -/* WLED present, but not controlled by ACPI */ - .mt_lcd_switch = xxN_PREFIX "_Q10", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\ADVG"}, - - { - .name = "D1x", - .mt_mled = "MLED", - .mt_lcd_switch = "\\Q0D", - .lcd_status = "\\GP11", - .brightness_up = "\\Q0C", - .brightness_down = "\\Q0B", - .brightness_status = "\\BLVL", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "L2D", - .mt_mled = "MLED", - .mled_status = "\\SGP6", - .mt_wled = "WLED", - .wled_status = "\\RCP3", - .mt_lcd_switch = "\\Q10", - .lcd_status = "\\SGP0", - .brightness_up = "\\Q0E", - .brightness_down = "\\Q0F", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "L3C", - .mt_mled = "MLED", - .mt_wled = "WLED", - .mt_lcd_switch = L3C_PREFIX "_Q10", - .lcd_status = "\\GL32", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\_SB.PCI0.PCI1.VGAC.NMAP"}, - - { - .name = "L3D", - .mt_mled = "MLED", - .mled_status = "\\MALD", - .mt_wled = "WLED", - .mt_lcd_switch = "\\Q10", - .lcd_status = "\\BKLG", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "L3H", - .mt_mled = "MLED", - .mt_wled = "WLED", - .mt_lcd_switch = "EHK", - .lcd_status = "\\_SB.PCI0.PM.PBC", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "L4R", - .mt_mled = "MLED", - .mt_wled = "WLED", - .wled_status = "\\_SB.PCI0.SBRG.SG13", - .mt_lcd_switch = xxN_PREFIX "_Q10", - .lcd_status = "\\_SB.PCI0.SBSM.SEO4", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\_SB.PCI0.P0P1.VGA.GETD"}, - - { - .name = "L5x", - .mt_mled = "MLED", -/* WLED present, but not controlled by ACPI */ - .mt_tled = "TLED", - .mt_lcd_switch = "\\Q0D", - .lcd_status = "\\BAOF", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "L8L" -/* No features, but at least support the hotkeys */ - }, - - { - .name = "M1A", - .mt_mled = "MLED", - .mt_lcd_switch = M1A_PREFIX "Q10", - .lcd_status = "\\PNOF", - .brightness_up = M1A_PREFIX "Q0E", - .brightness_down = M1A_PREFIX "Q0F", - .brightness_status = "\\BRIT", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "M2E", - .mt_mled = "MLED", - .mt_wled = "WLED", - .mt_lcd_switch = "\\Q10", - .lcd_status = "\\GP06", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "M6N", - .mt_mled = "MLED", - .mt_wled = "WLED", - .wled_status = "\\_SB.PCI0.SBRG.SG13", - .mt_lcd_switch = xxN_PREFIX "_Q10", - .lcd_status = "\\_SB.BKLT", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\SSTE"}, - - { - .name = "M6R", - .mt_mled = "MLED", - .mt_wled = "WLED", - .mt_lcd_switch = xxN_PREFIX "_Q10", - .lcd_status = "\\_SB.PCI0.SBSM.SEO4", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\_SB.PCI0.P0P1.VGA.GETD"}, - - { - .name = "P30", - .mt_wled = "WLED", - .mt_lcd_switch = P30_PREFIX "_Q0E", - .lcd_status = "\\BKLT", - .brightness_up = P30_PREFIX "_Q68", - .brightness_down = P30_PREFIX "_Q69", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\DNXT"}, - - { - .name = "S1x", - .mt_mled = "MLED", - .mled_status = "\\EMLE", - .mt_wled = "WLED", - .mt_lcd_switch = S1x_PREFIX "Q10", - .lcd_status = "\\PNOF", - .brightness_set = "SPLV", - .brightness_get = "GPLV"}, - - { - .name = "S2x", - .mt_mled = "MLED", - .mled_status = "\\MAIL", - .mt_lcd_switch = S2x_PREFIX "_Q10", - .lcd_status = "\\BKLI", - .brightness_up = S2x_PREFIX "_Q0B", - .brightness_down = S2x_PREFIX "_Q0A"}, - - { - .name = "W1N", - .mt_mled = "MLED", - .mt_wled = "WLED", - .mt_ledd = "SLCM", - .mt_lcd_switch = xxN_PREFIX "_Q10", - .lcd_status = "\\BKLT", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\ADVG"}, - - { - .name = "W5A", - .mt_bt_switch = "BLED", - .mt_wled = "WLED", - .mt_lcd_switch = xxN_PREFIX "_Q10", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\ADVG"}, - - { - .name = "W3V", - .mt_mled = "MLED", - .mt_wled = "WLED", - .mt_lcd_switch = xxN_PREFIX "_Q10", - .lcd_status = "\\BKLT", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "xxN", - .mt_mled = "MLED", -/* WLED present, but not controlled by ACPI */ - .mt_lcd_switch = xxN_PREFIX "_Q10", - .lcd_status = "\\BKLT", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\ADVG"}, - - { - .name = "A4S", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .mt_bt_switch = "BLED", - .mt_wled = "WLED" - }, - - { - .name = "F3Sa", - .mt_bt_switch = "BLED", - .mt_wled = "WLED", - .mt_mled = "MLED", - .brightness_get = "GPLV", - .brightness_set = "SPLV", - .mt_lcd_switch = "\\_SB.PCI0.SBRG.EC0._Q10", - .lcd_status = "\\_SB.PCI0.SBRG.EC0.RPIN", - .display_get = "\\ADVG", - .display_set = "SDSP", - }, - { - .name = "R1F", - .mt_bt_switch = "BLED", - .mt_mled = "MLED", - .mt_wled = "WLED", - .mt_lcd_switch = "\\Q10", - .lcd_status = "\\GP06", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\INFB" - } -}; - -/* procdir we use */ -static struct proc_dir_entry *asus_proc_dir; - -static struct backlight_device *asus_backlight_device; - -/* - * This header is made available to allow proper configuration given model, - * revision number , ... this info cannot go in struct asus_hotk because it is - * available before the hotk - */ -static struct acpi_table_header *asus_info; - -/* The actual device the driver binds to */ -static struct asus_hotk *hotk; - -/* - * The hotkey driver and autoloading declaration - */ -static int asus_hotk_add(struct acpi_device *device); -static int asus_hotk_remove(struct acpi_device *device, int type); -static void asus_hotk_notify(struct acpi_device *device, u32 event); - -static const struct acpi_device_id asus_device_ids[] = { - {"ATK0100", 0}, - {"", 0}, -}; -MODULE_DEVICE_TABLE(acpi, asus_device_ids); - -static struct acpi_driver asus_hotk_driver = { - .name = "asus_acpi", - .class = ACPI_HOTK_CLASS, - .owner = THIS_MODULE, - .ids = asus_device_ids, - .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, - .ops = { - .add = asus_hotk_add, - .remove = asus_hotk_remove, - .notify = asus_hotk_notify, - }, -}; - -/* - * This function evaluates an ACPI method, given an int as parameter, the - * method is searched within the scope of the handle, can be NULL. The output - * of the method is written is output, which can also be NULL - * - * returns 1 if write is successful, 0 else. - */ -static int write_acpi_int(acpi_handle handle, const char *method, int val, - struct acpi_buffer *output) -{ - struct acpi_object_list params; /* list of input parameters (int) */ - union acpi_object in_obj; /* the only param we use */ - acpi_status status; - - params.count = 1; - params.pointer = &in_obj; - in_obj.type = ACPI_TYPE_INTEGER; - in_obj.integer.value = val; - - status = acpi_evaluate_object(handle, (char *)method, ¶ms, output); - return (status == AE_OK); -} - -static int read_acpi_int(acpi_handle handle, const char *method, int *val) -{ - struct acpi_buffer output; - union acpi_object out_obj; - acpi_status status; - - output.length = sizeof(out_obj); - output.pointer = &out_obj; - - status = acpi_evaluate_object(handle, (char *)method, NULL, &output); - *val = out_obj.integer.value; - return (status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER); -} - -static int asus_info_proc_show(struct seq_file *m, void *v) -{ - int temp; - - seq_printf(m, ACPI_HOTK_NAME " " ASUS_ACPI_VERSION "\n"); - seq_printf(m, "Model reference : %s\n", hotk->methods->name); - /* - * The SFUN method probably allows the original driver to get the list - * of features supported by a given model. For now, 0x0100 or 0x0800 - * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card. - * The significance of others is yet to be found. - */ - if (read_acpi_int(hotk->handle, "SFUN", &temp)) - seq_printf(m, "SFUN value : 0x%04x\n", temp); - /* - * Another value for userspace: the ASYM method returns 0x02 for - * battery low and 0x04 for battery critical, its readings tend to be - * more accurate than those provided by _BST. - * Note: since not all the laptops provide this method, errors are - * silently ignored. - */ - if (read_acpi_int(hotk->handle, "ASYM", &temp)) - seq_printf(m, "ASYM value : 0x%04x\n", temp); - if (asus_info) { - seq_printf(m, "DSDT length : %d\n", asus_info->length); - seq_printf(m, "DSDT checksum : %d\n", asus_info->checksum); - seq_printf(m, "DSDT revision : %d\n", asus_info->revision); - seq_printf(m, "OEM id : %.*s\n", ACPI_OEM_ID_SIZE, asus_info->oem_id); - seq_printf(m, "OEM table id : %.*s\n", ACPI_OEM_TABLE_ID_SIZE, asus_info->oem_table_id); - seq_printf(m, "OEM revision : 0x%x\n", asus_info->oem_revision); - seq_printf(m, "ASL comp vendor id : %.*s\n", ACPI_NAME_SIZE, asus_info->asl_compiler_id); - seq_printf(m, "ASL comp revision : 0x%x\n", asus_info->asl_compiler_revision); - } - - return 0; -} - -static int asus_info_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, asus_info_proc_show, NULL); -} - -static const struct file_operations asus_info_proc_fops = { - .owner = THIS_MODULE, - .open = asus_info_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -/* - * /proc handlers - * We write our info in page, we begin at offset off and cannot write more - * than count bytes. We set eof to 1 if we handle those 2 values. We return the - * number of bytes written in page - */ - -/* Generic LED functions */ -static int read_led(const char *ledname, int ledmask) -{ - if (ledname) { - int led_status; - - if (read_acpi_int(NULL, ledname, &led_status)) - return led_status; - else - pr_warn("Error reading LED status\n"); - } - return (hotk->status & ledmask) ? 1 : 0; -} - -static int parse_arg(const char __user *buf, unsigned long count, int *val) -{ - char s[32]; - if (!count) - return 0; - if (count > 31) - return -EINVAL; - if (copy_from_user(s, buf, count)) - return -EFAULT; - s[count] = 0; - if (sscanf(s, "%i", val) != 1) - return -EINVAL; - return count; -} - -/* FIXME: kill extraneous args so it can be called independently */ -static int -write_led(const char __user *buffer, unsigned long count, - char *ledname, int ledmask, int invert) -{ - int rv, value; - int led_out = 0; - - rv = parse_arg(buffer, count, &value); - if (rv > 0) - led_out = value ? 1 : 0; - - hotk->status = - (led_out) ? (hotk->status | ledmask) : (hotk->status & ~ledmask); - - if (invert) /* invert target value */ - led_out = !led_out; - - if (!write_acpi_int(hotk->handle, ledname, led_out, NULL)) - pr_warn("LED (%s) write failed\n", ledname); - - return rv; -} - -/* - * Proc handlers for MLED - */ -static int mled_proc_show(struct seq_file *m, void *v) -{ - seq_printf(m, "%d\n", read_led(hotk->methods->mled_status, MLED_ON)); - return 0; -} - -static int mled_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, mled_proc_show, NULL); -} - -static ssize_t mled_proc_write(struct file *file, const char __user *buffer, - size_t count, loff_t *pos) -{ - return write_led(buffer, count, hotk->methods->mt_mled, MLED_ON, 1); -} - -static const struct file_operations mled_proc_fops = { - .owner = THIS_MODULE, - .open = mled_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .write = mled_proc_write, -}; - -/* - * Proc handlers for LED display - */ -static int ledd_proc_show(struct seq_file *m, void *v) -{ - seq_printf(m, "0x%08x\n", hotk->ledd_status); - return 0; -} - -static int ledd_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, ledd_proc_show, NULL); -} - -static ssize_t ledd_proc_write(struct file *file, const char __user *buffer, - size_t count, loff_t *pos) -{ - int rv, value; - - rv = parse_arg(buffer, count, &value); - if (rv > 0) { - if (!write_acpi_int - (hotk->handle, hotk->methods->mt_ledd, value, NULL)) - pr_warn("LED display write failed\n"); - else - hotk->ledd_status = (u32) value; - } - return rv; -} - -static const struct file_operations ledd_proc_fops = { - .owner = THIS_MODULE, - .open = ledd_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .write = ledd_proc_write, -}; - -/* - * Proc handlers for WLED - */ -static int wled_proc_show(struct seq_file *m, void *v) -{ - seq_printf(m, "%d\n", read_led(hotk->methods->wled_status, WLED_ON)); - return 0; -} - -static int wled_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, wled_proc_show, NULL); -} - -static ssize_t wled_proc_write(struct file *file, const char __user *buffer, - size_t count, loff_t *pos) -{ - return write_led(buffer, count, hotk->methods->mt_wled, WLED_ON, 0); -} - -static const struct file_operations wled_proc_fops = { - .owner = THIS_MODULE, - .open = wled_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .write = wled_proc_write, -}; - -/* - * Proc handlers for Bluetooth - */ -static int bluetooth_proc_show(struct seq_file *m, void *v) -{ - seq_printf(m, "%d\n", read_led(hotk->methods->bt_status, BT_ON)); - return 0; -} - -static int bluetooth_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, bluetooth_proc_show, NULL); -} - -static ssize_t bluetooth_proc_write(struct file *file, - const char __user *buffer, size_t count, loff_t *pos) -{ - /* Note: mt_bt_switch controls both internal Bluetooth adapter's - presence and its LED */ - return write_led(buffer, count, hotk->methods->mt_bt_switch, BT_ON, 0); -} - -static const struct file_operations bluetooth_proc_fops = { - .owner = THIS_MODULE, - .open = bluetooth_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .write = bluetooth_proc_write, -}; - -/* - * Proc handlers for TLED - */ -static int tled_proc_show(struct seq_file *m, void *v) -{ - seq_printf(m, "%d\n", read_led(hotk->methods->tled_status, TLED_ON)); - return 0; -} - -static int tled_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, tled_proc_show, NULL); -} - -static ssize_t tled_proc_write(struct file *file, const char __user *buffer, - size_t count, loff_t *pos) -{ - return write_led(buffer, count, hotk->methods->mt_tled, TLED_ON, 0); -} - -static const struct file_operations tled_proc_fops = { - .owner = THIS_MODULE, - .open = tled_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .write = tled_proc_write, -}; - -static int get_lcd_state(void) -{ - int lcd = 0; - - if (hotk->model == L3H) { - /* L3H and the like have to be handled differently */ - acpi_status status = 0; - struct acpi_object_list input; - union acpi_object mt_params[2]; - struct acpi_buffer output; - union acpi_object out_obj; - - input.count = 2; - input.pointer = mt_params; - /* Note: the following values are partly guessed up, but - otherwise they seem to work */ - mt_params[0].type = ACPI_TYPE_INTEGER; - mt_params[0].integer.value = 0x02; - mt_params[1].type = ACPI_TYPE_INTEGER; - mt_params[1].integer.value = 0x02; - - output.length = sizeof(out_obj); - output.pointer = &out_obj; - - status = - acpi_evaluate_object(NULL, hotk->methods->lcd_status, - &input, &output); - if (status != AE_OK) - return -1; - if (out_obj.type == ACPI_TYPE_INTEGER) - /* That's what the AML code does */ - lcd = out_obj.integer.value >> 8; - } else if (hotk->model == F3Sa) { - unsigned long long tmp; - union acpi_object param; - struct acpi_object_list input; - acpi_status status; - - /* Read pin 11 */ - param.type = ACPI_TYPE_INTEGER; - param.integer.value = 0x11; - input.count = 1; - input.pointer = ¶m; - - status = acpi_evaluate_integer(NULL, hotk->methods->lcd_status, - &input, &tmp); - if (status != AE_OK) - return -1; - - lcd = tmp; - } else { - /* We don't have to check anything if we are here */ - if (!read_acpi_int(NULL, hotk->methods->lcd_status, &lcd)) - pr_warn("Error reading LCD status\n"); - - if (hotk->model == L2D) - lcd = ~lcd; - } - - return (lcd & 1); -} - -static int set_lcd_state(int value) -{ - int lcd = 0; - acpi_status status = 0; - - lcd = value ? 1 : 0; - if (lcd != get_lcd_state()) { - /* switch */ - if (hotk->model != L3H) { - status = - acpi_evaluate_object(NULL, - hotk->methods->mt_lcd_switch, - NULL, NULL); - } else { - /* L3H and the like must be handled differently */ - if (!write_acpi_int - (hotk->handle, hotk->methods->mt_lcd_switch, 0x07, - NULL)) - status = AE_ERROR; - /* L3H's AML executes EHK (0x07) upon Fn+F7 keypress, - the exact behaviour is simulated here */ - } - if (ACPI_FAILURE(status)) - pr_warn("Error switching LCD\n"); - } - return 0; - -} - -static int lcd_proc_show(struct seq_file *m, void *v) -{ - seq_printf(m, "%d\n", get_lcd_state()); - return 0; -} - -static int lcd_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, lcd_proc_show, NULL); -} - -static ssize_t lcd_proc_write(struct file *file, const char __user *buffer, - size_t count, loff_t *pos) -{ - int rv, value; - - rv = parse_arg(buffer, count, &value); - if (rv > 0) - set_lcd_state(value); - return rv; -} - -static const struct file_operations lcd_proc_fops = { - .owner = THIS_MODULE, - .open = lcd_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .write = lcd_proc_write, -}; - -static int read_brightness(struct backlight_device *bd) -{ - int value; - - if (hotk->methods->brightness_get) { /* SPLV/GPLV laptop */ - if (!read_acpi_int(hotk->handle, hotk->methods->brightness_get, - &value)) - pr_warn("Error reading brightness\n"); - } else if (hotk->methods->brightness_status) { /* For D1 for example */ - if (!read_acpi_int(NULL, hotk->methods->brightness_status, - &value)) - pr_warn("Error reading brightness\n"); - } else /* No GPLV method */ - value = hotk->brightness; - return value; -} - -/* - * Change the brightness level - */ -static int set_brightness(int value) -{ - acpi_status status = 0; - int ret = 0; - - /* SPLV laptop */ - if (hotk->methods->brightness_set) { - if (!write_acpi_int(hotk->handle, hotk->methods->brightness_set, - value, NULL)) { - pr_warn("Error changing brightness\n"); - ret = -EIO; - } - goto out; - } - - /* No SPLV method if we are here, act as appropriate */ - value -= read_brightness(NULL); - while (value != 0) { - status = acpi_evaluate_object(NULL, (value > 0) ? - hotk->methods->brightness_up : - hotk->methods->brightness_down, - NULL, NULL); - (value > 0) ? value-- : value++; - if (ACPI_FAILURE(status)) { - pr_warn("Error changing brightness\n"); - ret = -EIO; - } - } -out: - return ret; -} - -static int set_brightness_status(struct backlight_device *bd) -{ - return set_brightness(bd->props.brightness); -} - -static int brn_proc_show(struct seq_file *m, void *v) -{ - seq_printf(m, "%d\n", read_brightness(NULL)); - return 0; -} - -static int brn_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, brn_proc_show, NULL); -} - -static ssize_t brn_proc_write(struct file *file, const char __user *buffer, - size_t count, loff_t *pos) -{ - int rv, value; - - rv = parse_arg(buffer, count, &value); - if (rv > 0) { - value = (0 < value) ? ((15 < value) ? 15 : value) : 0; - /* 0 <= value <= 15 */ - set_brightness(value); - } - return rv; -} - -static const struct file_operations brn_proc_fops = { - .owner = THIS_MODULE, - .open = brn_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .write = brn_proc_write, -}; - -static void set_display(int value) -{ - /* no sanity check needed for now */ - if (!write_acpi_int(hotk->handle, hotk->methods->display_set, - value, NULL)) - pr_warn("Error setting display\n"); - return; -} - -/* - * Now, *this* one could be more user-friendly, but so far, no-one has - * complained. The significance of bits is the same as in proc_write_disp() - */ -static int disp_proc_show(struct seq_file *m, void *v) -{ - int value = 0; - - if (!read_acpi_int(hotk->handle, hotk->methods->display_get, &value)) - pr_warn("Error reading display status\n"); - value &= 0x07; /* needed for some models, shouldn't hurt others */ - seq_printf(m, "%d\n", value); - return 0; -} - -static int disp_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, disp_proc_show, NULL); -} - -/* - * Experimental support for display switching. As of now: 1 should activate - * the LCD output, 2 should do for CRT, and 4 for TV-Out. Any combination - * (bitwise) of these will suffice. I never actually tested 3 displays hooked - * up simultaneously, so be warned. See the acpi4asus README for more info. - */ -static ssize_t disp_proc_write(struct file *file, const char __user *buffer, - size_t count, loff_t *pos) -{ - int rv, value; - - rv = parse_arg(buffer, count, &value); - if (rv > 0) - set_display(value); - return rv; -} - -static const struct file_operations disp_proc_fops = { - .owner = THIS_MODULE, - .open = disp_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .write = disp_proc_write, -}; - -static int -asus_proc_add(char *name, const struct file_operations *proc_fops, umode_t mode, - struct acpi_device *device) -{ - struct proc_dir_entry *proc; - - proc = proc_create_data(name, mode, acpi_device_dir(device), - proc_fops, acpi_driver_data(device)); - if (!proc) { - pr_warn(" Unable to create %s fs entry\n", name); - return -1; - } - proc->uid = asus_uid; - proc->gid = asus_gid; - return 0; -} - -static int asus_hotk_add_fs(struct acpi_device *device) -{ - struct proc_dir_entry *proc; - umode_t mode; - - if ((asus_uid == 0) && (asus_gid == 0)) { - mode = S_IFREG | S_IRUGO | S_IWUSR | S_IWGRP; - } else { - mode = S_IFREG | S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP; - pr_warn(" asus_uid and asus_gid parameters are " - "deprecated, use chown and chmod instead!\n"); - } - - acpi_device_dir(device) = asus_proc_dir; - if (!acpi_device_dir(device)) - return -ENODEV; - - proc = proc_create(PROC_INFO, mode, acpi_device_dir(device), - &asus_info_proc_fops); - if (proc) { - proc->uid = asus_uid; - proc->gid = asus_gid; - } else { - pr_warn(" Unable to create " PROC_INFO " fs entry\n"); - } - - if (hotk->methods->mt_wled) { - asus_proc_add(PROC_WLED, &wled_proc_fops, mode, device); - } - - if (hotk->methods->mt_ledd) { - asus_proc_add(PROC_LEDD, &ledd_proc_fops, mode, device); - } - - if (hotk->methods->mt_mled) { - asus_proc_add(PROC_MLED, &mled_proc_fops, mode, device); - } - - if (hotk->methods->mt_tled) { - asus_proc_add(PROC_TLED, &tled_proc_fops, mode, device); - } - - if (hotk->methods->mt_bt_switch) { - asus_proc_add(PROC_BT, &bluetooth_proc_fops, mode, device); - } - - /* - * We need both read node and write method as LCD switch is also - * accessible from the keyboard - */ - if (hotk->methods->mt_lcd_switch && hotk->methods->lcd_status) { - asus_proc_add(PROC_LCD, &lcd_proc_fops, mode, device); - } - - if ((hotk->methods->brightness_up && hotk->methods->brightness_down) || - (hotk->methods->brightness_get && hotk->methods->brightness_set)) { - asus_proc_add(PROC_BRN, &brn_proc_fops, mode, device); - } - - if (hotk->methods->display_set) { - asus_proc_add(PROC_DISP, &disp_proc_fops, mode, device); - } - - return 0; -} - -static int asus_hotk_remove_fs(struct acpi_device *device) -{ - if (acpi_device_dir(device)) { - remove_proc_entry(PROC_INFO, acpi_device_dir(device)); - if (hotk->methods->mt_wled) - remove_proc_entry(PROC_WLED, acpi_device_dir(device)); - if (hotk->methods->mt_mled) - remove_proc_entry(PROC_MLED, acpi_device_dir(device)); - if (hotk->methods->mt_tled) - remove_proc_entry(PROC_TLED, acpi_device_dir(device)); - if (hotk->methods->mt_ledd) - remove_proc_entry(PROC_LEDD, acpi_device_dir(device)); - if (hotk->methods->mt_bt_switch) - remove_proc_entry(PROC_BT, acpi_device_dir(device)); - if (hotk->methods->mt_lcd_switch && hotk->methods->lcd_status) - remove_proc_entry(PROC_LCD, acpi_device_dir(device)); - if ((hotk->methods->brightness_up - && hotk->methods->brightness_down) - || (hotk->methods->brightness_get - && hotk->methods->brightness_set)) - remove_proc_entry(PROC_BRN, acpi_device_dir(device)); - if (hotk->methods->display_set) - remove_proc_entry(PROC_DISP, acpi_device_dir(device)); - } - return 0; -} - -static void asus_hotk_notify(struct acpi_device *device, u32 event) -{ - /* TODO Find a better way to handle events count. */ - if (!hotk) - return; - - /* - * The BIOS *should* be sending us device events, but apparently - * Asus uses system events instead, so just ignore any device - * events we get. - */ - if (event > ACPI_MAX_SYS_NOTIFY) - return; - - if ((event & ~((u32) BR_UP)) < 16) - hotk->brightness = (event & ~((u32) BR_UP)); - else if ((event & ~((u32) BR_DOWN)) < 16) - hotk->brightness = (event & ~((u32) BR_DOWN)); - - acpi_bus_generate_proc_event(hotk->device, event, - hotk->event_count[event % 128]++); - - return; -} - -/* - * Match the model string to the list of supported models. Return END_MODEL if - * no match or model is NULL. - */ -static int asus_model_match(char *model) -{ - if (model == NULL) - return END_MODEL; - - if (strncmp(model, "L3D", 3) == 0) - return L3D; - else if (strncmp(model, "L2E", 3) == 0 || - strncmp(model, "L3H", 3) == 0 || strncmp(model, "L5D", 3) == 0) - return L3H; - else if (strncmp(model, "L3", 2) == 0 || strncmp(model, "L2B", 3) == 0) - return L3C; - else if (strncmp(model, "L8L", 3) == 0) - return L8L; - else if (strncmp(model, "L4R", 3) == 0) - return L4R; - else if (strncmp(model, "M6N", 3) == 0 || strncmp(model, "W3N", 3) == 0) - return M6N; - else if (strncmp(model, "M6R", 3) == 0 || strncmp(model, "A3G", 3) == 0) - return M6R; - else if (strncmp(model, "M2N", 3) == 0 || - strncmp(model, "M3N", 3) == 0 || - strncmp(model, "M5N", 3) == 0 || - strncmp(model, "S1N", 3) == 0 || - strncmp(model, "S5N", 3) == 0) - return xxN; - else if (strncmp(model, "M1", 2) == 0) - return M1A; - else if (strncmp(model, "M2", 2) == 0 || strncmp(model, "L4E", 3) == 0) - return M2E; - else if (strncmp(model, "L2", 2) == 0) - return L2D; - else if (strncmp(model, "L8", 2) == 0) - return S1x; - else if (strncmp(model, "D1", 2) == 0) - return D1x; - else if (strncmp(model, "A1", 2) == 0) - return A1x; - else if (strncmp(model, "A2", 2) == 0) - return A2x; - else if (strncmp(model, "J1", 2) == 0) - return S2x; - else if (strncmp(model, "L5", 2) == 0) - return L5x; - else if (strncmp(model, "A4G", 3) == 0) - return A4G; - else if (strncmp(model, "W1N", 3) == 0) - return W1N; - else if (strncmp(model, "W3V", 3) == 0) - return W3V; - else if (strncmp(model, "W5A", 3) == 0) - return W5A; - else if (strncmp(model, "R1F", 3) == 0) - return R1F; - else if (strncmp(model, "A4S", 3) == 0) - return A4S; - else if (strncmp(model, "F3Sa", 4) == 0) - return F3Sa; - else - return END_MODEL; -} - -/* - * This function is used to initialize the hotk with right values. In this - * method, we can make all the detection we want, and modify the hotk struct - */ -static int asus_hotk_get_info(void) -{ - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *model = NULL; - int bsts_result; - char *string = NULL; - acpi_status status; - - /* - * Get DSDT headers early enough to allow for differentiating between - * models, but late enough to allow acpi_bus_register_driver() to fail - * before doing anything ACPI-specific. Should we encounter a machine, - * which needs special handling (i.e. its hotkey device has a different - * HID), this bit will be moved. A global variable asus_info contains - * the DSDT header. - */ - status = acpi_get_table(ACPI_SIG_DSDT, 1, &asus_info); - if (ACPI_FAILURE(status)) - pr_warn(" Couldn't get the DSDT table header\n"); - - /* We have to write 0 on init this far for all ASUS models */ - if (!write_acpi_int(hotk->handle, "INIT", 0, &buffer)) { - pr_err(" Hotkey initialization failed\n"); - return -ENODEV; - } - - /* This needs to be called for some laptops to init properly */ - if (!read_acpi_int(hotk->handle, "BSTS", &bsts_result)) - pr_warn(" Error calling BSTS\n"); - else if (bsts_result) - pr_notice(" BSTS called, 0x%02x returned\n", bsts_result); - - /* - * Try to match the object returned by INIT to the specific model. - * Handle every possible object (or the lack of thereof) the DSDT - * writers might throw at us. When in trouble, we pass NULL to - * asus_model_match() and try something completely different. - */ - if (buffer.pointer) { - model = buffer.pointer; - switch (model->type) { - case ACPI_TYPE_STRING: - string = model->string.pointer; - break; - case ACPI_TYPE_BUFFER: - string = model->buffer.pointer; - break; - default: - kfree(model); - model = NULL; - break; - } - } - hotk->model = asus_model_match(string); - if (hotk->model == END_MODEL) { /* match failed */ - if (asus_info && - strncmp(asus_info->oem_table_id, "ODEM", 4) == 0) { - hotk->model = P30; - pr_notice(" Samsung P30 detected, supported\n"); - hotk->methods = &model_conf[hotk->model]; - kfree(model); - return 0; - } else { - hotk->model = M2E; - pr_notice(" unsupported model %s, trying default values\n", - string); - pr_notice(" send /proc/acpi/dsdt to the developers\n"); - kfree(model); - return -ENODEV; - } - } - hotk->methods = &model_conf[hotk->model]; - pr_notice(" %s model detected, supported\n", string); - - /* Sort of per-model blacklist */ - if (strncmp(string, "L2B", 3) == 0) - hotk->methods->lcd_status = NULL; - /* L2B is similar enough to L3C to use its settings, with this only - exception */ - else if (strncmp(string, "A3G", 3) == 0) - hotk->methods->lcd_status = "\\BLFG"; - /* A3G is like M6R */ - else if (strncmp(string, "S5N", 3) == 0 || - strncmp(string, "M5N", 3) == 0 || - strncmp(string, "W3N", 3) == 0) - hotk->methods->mt_mled = NULL; - /* S5N, M5N and W3N have no MLED */ - else if (strncmp(string, "L5D", 3) == 0) - hotk->methods->mt_wled = NULL; - /* L5D's WLED is not controlled by ACPI */ - else if (strncmp(string, "M2N", 3) == 0 || - strncmp(string, "W3V", 3) == 0 || - strncmp(string, "S1N", 3) == 0) - hotk->methods->mt_wled = "WLED"; - /* M2N, S1N and W3V have a usable WLED */ - else if (asus_info) { - if (strncmp(asus_info->oem_table_id, "L1", 2) == 0) - hotk->methods->mled_status = NULL; - /* S1300A reports L84F, but L1400B too, account for that */ - } - - kfree(model); - - return 0; -} - -static int asus_hotk_check(void) -{ - int result = 0; - - result = acpi_bus_get_status(hotk->device); - if (result) - return result; - - if (hotk->device->status.present) { - result = asus_hotk_get_info(); - } else { - pr_err(" Hotkey device not present, aborting\n"); - return -EINVAL; - } - - return result; -} - -static int asus_hotk_found; - -static int asus_hotk_add(struct acpi_device *device) -{ - acpi_status status = AE_OK; - int result; - - pr_notice("Asus Laptop ACPI Extras version %s\n", ASUS_ACPI_VERSION); - - hotk = kzalloc(sizeof(struct asus_hotk), GFP_KERNEL); - if (!hotk) - return -ENOMEM; - - hotk->handle = device->handle; - strcpy(acpi_device_name(device), ACPI_HOTK_DEVICE_NAME); - strcpy(acpi_device_class(device), ACPI_HOTK_CLASS); - device->driver_data = hotk; - hotk->device = device; - - result = asus_hotk_check(); - if (result) - goto end; - - result = asus_hotk_add_fs(device); - if (result) - goto end; - - /* For laptops without GPLV: init the hotk->brightness value */ - if ((!hotk->methods->brightness_get) - && (!hotk->methods->brightness_status) - && (hotk->methods->brightness_up && hotk->methods->brightness_down)) { - status = - acpi_evaluate_object(NULL, hotk->methods->brightness_down, - NULL, NULL); - if (ACPI_FAILURE(status)) - pr_warn(" Error changing brightness\n"); - else { - status = - acpi_evaluate_object(NULL, - hotk->methods->brightness_up, - NULL, NULL); - if (ACPI_FAILURE(status)) - pr_warn(" Strange, error changing brightness\n"); - } - } - - asus_hotk_found = 1; - - /* LED display is off by default */ - hotk->ledd_status = 0xFFF; - -end: - if (result) - kfree(hotk); - - return result; -} - -static int asus_hotk_remove(struct acpi_device *device, int type) -{ - asus_hotk_remove_fs(device); - - kfree(hotk); - - return 0; -} - -static const struct backlight_ops asus_backlight_data = { - .get_brightness = read_brightness, - .update_status = set_brightness_status, -}; - -static void asus_acpi_exit(void) -{ - if (asus_backlight_device) - backlight_device_unregister(asus_backlight_device); - - acpi_bus_unregister_driver(&asus_hotk_driver); - remove_proc_entry(PROC_ASUS, acpi_root_dir); - - return; -} - -static int __init asus_acpi_init(void) -{ - struct backlight_properties props; - int result; - - result = acpi_bus_register_driver(&asus_hotk_driver); - if (result < 0) - return result; - - asus_proc_dir = proc_mkdir(PROC_ASUS, acpi_root_dir); - if (!asus_proc_dir) { - pr_err("Unable to create /proc entry\n"); - acpi_bus_unregister_driver(&asus_hotk_driver); - return -ENODEV; - } - - /* - * This is a bit of a kludge. We only want this module loaded - * for ASUS systems, but there's currently no way to probe the - * ACPI namespace for ASUS HIDs. So we just return failure if - * we didn't find one, which will cause the module to be - * unloaded. - */ - if (!asus_hotk_found) { - acpi_bus_unregister_driver(&asus_hotk_driver); - remove_proc_entry(PROC_ASUS, acpi_root_dir); - return -ENODEV; - } - - memset(&props, 0, sizeof(struct backlight_properties)); - props.type = BACKLIGHT_PLATFORM; - props.max_brightness = 15; - asus_backlight_device = backlight_device_register("asus", NULL, NULL, - &asus_backlight_data, - &props); - if (IS_ERR(asus_backlight_device)) { - pr_err("Could not register asus backlight device\n"); - asus_backlight_device = NULL; - asus_acpi_exit(); - return -ENODEV; - } - - return 0; -} - -module_init(asus_acpi_init); -module_exit(asus_acpi_exit); diff --git a/drivers/platform/x86/compal-laptop.c b/drivers/platform/x86/compal-laptop.c index d96734478324..1887e2f166a4 100644 --- a/drivers/platform/x86/compal-laptop.c +++ b/drivers/platform/x86/compal-laptop.c @@ -882,6 +882,7 @@ static struct dmi_system_id __initdata compal_dmi_table[] = { }, { } }; +MODULE_DEVICE_TABLE(dmi, compal_dmi_table); static void initialize_power_supply_data(struct compal_data *data) { @@ -1097,16 +1098,3 @@ MODULE_AUTHOR("Roald Frederickx (roald.frederickx@gmail.com)"); MODULE_DESCRIPTION("Compal Laptop Support"); MODULE_VERSION(DRIVER_VERSION); MODULE_LICENSE("GPL"); - -MODULE_ALIAS("dmi:*:rnIFL90:rvrIFT00:*"); -MODULE_ALIAS("dmi:*:rnIFL90:rvrREFERENCE:*"); -MODULE_ALIAS("dmi:*:rnIFL91:rvrIFT00:*"); -MODULE_ALIAS("dmi:*:rnJFL92:rvrIFT00:*"); -MODULE_ALIAS("dmi:*:rnIFT00:rvrIFT00:*"); -MODULE_ALIAS("dmi:*:rnJHL90:rvrREFERENCE:*"); -MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron910:*"); -MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron1010:*"); -MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron1011:*"); -MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron1012:*"); -MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron1110:*"); -MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron1210:*"); diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index d93e962f2610..a05fc9c955d8 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c @@ -117,6 +117,7 @@ static const struct dmi_system_id __initdata dell_device_table[] = { }, { } }; +MODULE_DEVICE_TABLE(dmi, dell_device_table); static struct dmi_system_id __devinitdata dell_blacklist[] = { /* Supported by compal-laptop */ @@ -184,6 +185,33 @@ static struct dmi_system_id __devinitdata dell_quirks[] = { }, .driver_data = &quirk_dell_vostro_v130, }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3555", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3555"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron N311z", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron N311z"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron M5110", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron M5110"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, }; static struct calling_interface_buffer *buffer; @@ -236,9 +264,7 @@ static void __init find_tokens(const struct dmi_header *dm, void *dummy) { switch (dm->type) { case 0xd4: /* Indexed IO */ - break; case 0xd5: /* Protected Area Type 1 */ - break; case 0xd6: /* Protected Area Type 2 */ break; case 0xda: /* Calling interface */ @@ -615,6 +641,7 @@ static void touchpad_led_set(struct led_classdev *led_cdev, static struct led_classdev touchpad_led = { .name = "dell-laptop::touchpad", .brightness_set = touchpad_led_set, + .flags = LED_CORE_SUSPENDRESUME, }; static int __devinit touchpad_led_init(struct device *dev) @@ -794,6 +821,3 @@ module_exit(dell_exit); MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); MODULE_DESCRIPTION("Dell laptop driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS("dmi:*svnDellInc.:*:ct8:*"); -MODULE_ALIAS("dmi:*svnDellInc.:*:ct9:*"); -MODULE_ALIAS("dmi:*svnDellComputerCorporation.:*:ct8:*"); diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index d9a9e2bedb30..dab91b48d22c 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -1251,6 +1251,14 @@ static void eeepc_input_exit(struct eeepc_laptop *eeepc) /* * ACPI driver */ +static void eeepc_input_notify(struct eeepc_laptop *eeepc, int event) +{ + if (!eeepc->inputdev) + return ; + if (!sparse_keymap_report_event(eeepc->inputdev, event, 1, true)) + pr_info("Unknown key %x pressed\n", event); +} + static void eeepc_acpi_notify(struct acpi_device *device, u32 event) { struct eeepc_laptop *eeepc = acpi_driver_data(device); @@ -1287,12 +1295,11 @@ static void eeepc_acpi_notify(struct acpi_device *device, u32 event) * event will be desired value (or else ignored) */ } - sparse_keymap_report_event(eeepc->inputdev, event, - 1, true); + eeepc_input_notify(eeepc, event); } } else { /* Everything else is a bona-fide keypress event */ - sparse_keymap_report_event(eeepc->inputdev, event, 1, true); + eeepc_input_notify(eeepc, event); } } diff --git a/drivers/platform/x86/eeepc-wmi.c b/drivers/platform/x86/eeepc-wmi.c index 9f6e64302b45..656761380342 100644 --- a/drivers/platform/x86/eeepc-wmi.c +++ b/drivers/platform/x86/eeepc-wmi.c @@ -32,6 +32,7 @@ #include <linux/input.h> #include <linux/input/sparse-keymap.h> #include <linux/dmi.h> +#include <linux/fb.h> #include <acpi/acpi_bus.h> #include "asus-wmi.h" @@ -84,9 +85,81 @@ static const struct key_entry eeepc_wmi_keymap[] = { { KE_KEY, 0xed, { KEY_CAMERA_DOWN } }, { KE_KEY, 0xee, { KEY_CAMERA_LEFT } }, { KE_KEY, 0xef, { KEY_CAMERA_RIGHT } }, + { KE_KEY, 0xf3, { KEY_MENU } }, + { KE_KEY, 0xf5, { KEY_HOMEPAGE } }, + { KE_KEY, 0xf6, { KEY_ESC } }, { KE_END, 0}, }; +static struct quirk_entry quirk_asus_unknown = { +}; + +static struct quirk_entry quirk_asus_1000h = { + .hotplug_wireless = true, +}; + +static struct quirk_entry quirk_asus_et2012_type1 = { + .store_backlight_power = true, +}; + +static struct quirk_entry quirk_asus_et2012_type3 = { + .scalar_panel_brightness = true, + .store_backlight_power = true, +}; + +static struct quirk_entry *quirks; + +static void et2012_quirks(void) +{ + const struct dmi_device *dev = NULL; + char oemstring[30]; + + while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) { + if (sscanf(dev->name, "AEMS%24c", oemstring) == 1) { + if (oemstring[18] == '1') + quirks = &quirk_asus_et2012_type1; + else if (oemstring[18] == '3') + quirks = &quirk_asus_et2012_type3; + break; + } + } +} + +static int dmi_matched(const struct dmi_system_id *dmi) +{ + char *model; + + quirks = dmi->driver_data; + + model = (char *)dmi->matches[1].substr; + if (unlikely(strncmp(model, "ET2012", 6) == 0)) + et2012_quirks(); + + return 1; +} + +static struct dmi_system_id asus_quirks[] = { + { + .callback = dmi_matched, + .ident = "ASUSTeK Computer INC. 1000H", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "1000H"), + }, + .driver_data = &quirk_asus_1000h, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK Computer INC. ET2012E/I", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "ET2012"), + }, + .driver_data = &quirk_asus_unknown, + }, + {}, +}; + static void eeepc_wmi_key_filter(struct asus_wmi_driver *asus_wmi, int *code, unsigned int *value, bool *autorelease) { @@ -141,33 +214,16 @@ static int eeepc_wmi_probe(struct platform_device *pdev) return 0; } -static void eeepc_dmi_check(struct asus_wmi_driver *driver) -{ - const char *model; - - model = dmi_get_system_info(DMI_PRODUCT_NAME); - if (!model) - return; - - /* - * Whitelist for wlan hotplug - * - * Asus 1000H needs the current hotplug code to handle - * Fn+F2 correctly. We may add other Asus here later, but - * it seems that most of the laptops supported by asus-wmi - * don't need to be on this list - */ - if (strcmp(model, "1000H") == 0) { - driver->hotplug_wireless = true; - pr_info("wlan hotplug enabled\n"); - } -} - static void eeepc_wmi_quirks(struct asus_wmi_driver *driver) { - driver->hotplug_wireless = hotplug_wireless; - driver->wapf = -1; - eeepc_dmi_check(driver); + quirks = &quirk_asus_unknown; + quirks->hotplug_wireless = hotplug_wireless; + + dmi_check_system(asus_quirks); + + driver->quirks = quirks; + driver->quirks->wapf = -1; + driver->panel_power = FB_BLANK_UNBLANK; } static struct asus_wmi_driver asus_wmi_driver = { @@ -179,7 +235,7 @@ static struct asus_wmi_driver asus_wmi_driver = { .input_phys = EEEPC_WMI_FILE "/input0", .key_filter = eeepc_wmi_key_filter, .probe = eeepc_wmi_probe, - .quirks = eeepc_wmi_quirks, + .detect_quirks = eeepc_wmi_quirks, }; diff --git a/drivers/platform/x86/hdaps.c b/drivers/platform/x86/hdaps.c index ba68d4e7a779..7387f97a2941 100644 --- a/drivers/platform/x86/hdaps.c +++ b/drivers/platform/x86/hdaps.c @@ -375,7 +375,7 @@ static ssize_t hdaps_variance_show(struct device *dev, static ssize_t hdaps_temp1_show(struct device *dev, struct device_attribute *attr, char *buf) { - u8 temp; + u8 uninitialized_var(temp); int ret; ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp); @@ -388,7 +388,7 @@ static ssize_t hdaps_temp1_show(struct device *dev, static ssize_t hdaps_temp2_show(struct device *dev, struct device_attribute *attr, char *buf) { - u8 temp; + u8 uninitialized_var(temp); int ret; ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp); diff --git a/drivers/platform/x86/intel_mid_powerbtn.c b/drivers/platform/x86/intel_mid_powerbtn.c index 0903a883e9f4..0a3594c7e912 100644 --- a/drivers/platform/x86/intel_mid_powerbtn.c +++ b/drivers/platform/x86/intel_mid_powerbtn.c @@ -142,17 +142,7 @@ static struct platform_driver mfld_pb_driver = { .remove = __devexit_p(mfld_pb_remove), }; -static int __init mfld_pb_init(void) -{ - return platform_driver_register(&mfld_pb_driver); -} -module_init(mfld_pb_init); - -static void __exit mfld_pb_exit(void) -{ - platform_driver_unregister(&mfld_pb_driver); -} -module_exit(mfld_pb_exit); +module_platform_driver(mfld_pb_driver); MODULE_AUTHOR("Hong Liu <hong.liu@intel.com>"); MODULE_DESCRIPTION("Intel Medfield Power Button Driver"); diff --git a/drivers/platform/x86/intel_mid_thermal.c b/drivers/platform/x86/intel_mid_thermal.c index 2ee9766737ea..5ae9cd9c7e6e 100644 --- a/drivers/platform/x86/intel_mid_thermal.c +++ b/drivers/platform/x86/intel_mid_thermal.c @@ -549,6 +549,7 @@ static int mid_thermal_remove(struct platform_device *pdev) static const struct platform_device_id therm_id_table[] = { { DRIVER_NAME, 1 }, + { "msic_thermal", 1 }, { } }; @@ -564,18 +565,7 @@ static struct platform_driver mid_thermal_driver = { .id_table = therm_id_table, }; -static int __init mid_thermal_module_init(void) -{ - return platform_driver_register(&mid_thermal_driver); -} - -static void __exit mid_thermal_module_exit(void) -{ - platform_driver_unregister(&mid_thermal_driver); -} - -module_init(mid_thermal_module_init); -module_exit(mid_thermal_module_exit); +module_platform_driver(mid_thermal_driver); MODULE_AUTHOR("Durgadoss R <durgadoss.r@intel.com>"); MODULE_DESCRIPTION("Intel Medfield Platform Thermal Driver"); diff --git a/drivers/platform/x86/intel_oaktrail.c b/drivers/platform/x86/intel_oaktrail.c index 6ee0b5c90933..79a0c2f6be53 100644 --- a/drivers/platform/x86/intel_oaktrail.c +++ b/drivers/platform/x86/intel_oaktrail.c @@ -313,6 +313,7 @@ static struct dmi_system_id __initdata oaktrail_dmi_table[] = { }, { } }; +MODULE_DEVICE_TABLE(dmi, oaktrail_dmi_table); static int __init oaktrail_init(void) { @@ -394,4 +395,3 @@ MODULE_AUTHOR("Yin Kangkai (kangkai.yin@intel.com)"); MODULE_DESCRIPTION("Intel Oaktrail Platform ACPI Extras"); MODULE_VERSION(DRIVER_VERSION); MODULE_LICENSE("GPL"); -MODULE_ALIAS("dmi:*:svnIntelCorporation:pnOakTrailplatform:*"); diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c index fd73ea89b857..e2a34b42ddc1 100644 --- a/drivers/platform/x86/samsung-laptop.c +++ b/drivers/platform/x86/samsung-laptop.c @@ -17,10 +17,18 @@ #include <linux/delay.h> #include <linux/pci.h> #include <linux/backlight.h> +#include <linux/leds.h> #include <linux/fb.h> #include <linux/dmi.h> #include <linux/platform_device.h> #include <linux/rfkill.h> +#include <linux/acpi.h> +#include <linux/seq_file.h> +#include <linux/debugfs.h> +#include <linux/ctype.h> +#if (defined CONFIG_ACPI_VIDEO || defined CONFIG_ACPI_VIDEO_MODULE) +#include <acpi/video.h> +#endif /* * This driver is needed because a number of Samsung laptops do not hook @@ -41,9 +49,20 @@ #define SABI_IFACE_COMPLETE 0x04 #define SABI_IFACE_DATA 0x05 -/* Structure to get data back to the calling function */ -struct sabi_retval { - u8 retval[20]; +#define WL_STATUS_WLAN 0x0 +#define WL_STATUS_BT 0x2 + +/* Structure get/set data using sabi */ +struct sabi_data { + union { + struct { + u32 d0; + u32 d1; + u16 d2; + u8 d3; + }; + u8 data[11]; + }; }; struct sabi_header_offsets { @@ -60,8 +79,8 @@ struct sabi_commands { * Brightness is 0 - 8, as described above. * Value 0 is for the BIOS to use */ - u8 get_brightness; - u8 set_brightness; + u16 get_brightness; + u16 set_brightness; /* * first byte: @@ -72,40 +91,56 @@ struct sabi_commands { * 0x03 - 3G is on * TODO, verify 3G is correct, that doesn't seem right... */ - u8 get_wireless_button; - u8 set_wireless_button; + u16 get_wireless_button; + u16 set_wireless_button; /* 0 is off, 1 is on */ - u8 get_backlight; - u8 set_backlight; + u16 get_backlight; + u16 set_backlight; /* * 0x80 or 0x00 - no action * 0x81 - recovery key pressed */ - u8 get_recovery_mode; - u8 set_recovery_mode; + u16 get_recovery_mode; + u16 set_recovery_mode; /* * on seclinux: 0 is low, 1 is high, * on swsmi: 0 is normal, 1 is silent, 2 is turbo */ - u8 get_performance_level; - u8 set_performance_level; + u16 get_performance_level; + u16 set_performance_level; + + /* 0x80 is off, 0x81 is on */ + u16 get_battery_life_extender; + u16 set_battery_life_extender; + + /* 0x80 is off, 0x81 is on */ + u16 get_usb_charge; + u16 set_usb_charge; + + /* the first byte is for bluetooth and the third one is for wlan */ + u16 get_wireless_status; + u16 set_wireless_status; + + /* 0x81 to read, (0x82 | level << 8) to set, 0xaabb to enable */ + u16 kbd_backlight; /* * Tell the BIOS that Linux is running on this machine. * 81 is on, 80 is off */ - u8 set_linux; + u16 set_linux; }; struct sabi_performance_level { const char *name; - u8 value; + u16 value; }; struct sabi_config { + int sabi_version; const char *test_string; u16 main_function; const struct sabi_header_offsets header_offsets; @@ -117,6 +152,10 @@ struct sabi_config { static const struct sabi_config sabi_configs[] = { { + /* I don't know if it is really 2, but it it is + * less than 3 anyway */ + .sabi_version = 2, + .test_string = "SECLINUX", .main_function = 0x4c49, @@ -146,6 +185,17 @@ static const struct sabi_config sabi_configs[] = { .get_performance_level = 0x08, .set_performance_level = 0x09, + .get_battery_life_extender = 0xFFFF, + .set_battery_life_extender = 0xFFFF, + + .get_usb_charge = 0xFFFF, + .set_usb_charge = 0xFFFF, + + .get_wireless_status = 0xFFFF, + .set_wireless_status = 0xFFFF, + + .kbd_backlight = 0xFFFF, + .set_linux = 0x0a, }, @@ -164,6 +214,8 @@ static const struct sabi_config sabi_configs[] = { .max_brightness = 8, }, { + .sabi_version = 3, + .test_string = "SwSmi@", .main_function = 0x5843, @@ -193,6 +245,17 @@ static const struct sabi_config sabi_configs[] = { .get_performance_level = 0x31, .set_performance_level = 0x32, + .get_battery_life_extender = 0x65, + .set_battery_life_extender = 0x66, + + .get_usb_charge = 0x67, + .set_usb_charge = 0x68, + + .get_wireless_status = 0x69, + .set_wireless_status = 0x6a, + + .kbd_backlight = 0x78, + .set_linux = 0xff, }, @@ -217,16 +280,82 @@ static const struct sabi_config sabi_configs[] = { { }, }; -static const struct sabi_config *sabi_config; +/* + * samsung-laptop/ - debugfs root directory + * f0000_segment - dump f0000 segment + * command - current command + * data - current data + * d0, d1, d2, d3 - data fields + * call - call SABI using command and data + * + * This allow to call arbitrary sabi commands wihout + * modifying the driver at all. + * For example, setting the keyboard backlight brightness to 5 + * + * echo 0x78 > command + * echo 0x0582 > d0 + * echo 0 > d1 + * echo 0 > d2 + * echo 0 > d3 + * cat call + */ + +struct samsung_laptop_debug { + struct dentry *root; + struct sabi_data data; + u16 command; + + struct debugfs_blob_wrapper f0000_wrapper; + struct debugfs_blob_wrapper data_wrapper; + struct debugfs_blob_wrapper sdiag_wrapper; +}; + +struct samsung_laptop; + +struct samsung_rfkill { + struct samsung_laptop *samsung; + struct rfkill *rfkill; + enum rfkill_type type; +}; + +struct samsung_laptop { + const struct sabi_config *config; + + void __iomem *sabi; + void __iomem *sabi_iface; + void __iomem *f0000_segment; + + struct mutex sabi_mutex; + + struct platform_device *platform_device; + struct backlight_device *backlight_device; + + struct samsung_rfkill wlan; + struct samsung_rfkill bluetooth; + + struct led_classdev kbd_led; + int kbd_led_wk; + struct workqueue_struct *led_workqueue; + struct work_struct kbd_led_work; + + struct samsung_laptop_debug debug; + struct samsung_quirks *quirks; + + bool handle_backlight; + bool has_stepping_quirk; + + char sdiag[64]; +}; + +struct samsung_quirks { + bool broken_acpi_video; +}; + +static struct samsung_quirks samsung_unknown = {}; -static void __iomem *sabi; -static void __iomem *sabi_iface; -static void __iomem *f0000_segment; -static struct backlight_device *backlight_device; -static struct mutex sabi_mutex; -static struct platform_device *sdev; -static struct rfkill *rfk; -static bool has_stepping_quirk; +static struct samsung_quirks samsung_broken_acpi_video = { + .broken_acpi_video = true, +}; static bool force; module_param(force, bool, 0); @@ -237,176 +366,143 @@ static bool debug; module_param(debug, bool, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(debug, "Debug enabled or not"); -static int sabi_get_command(u8 command, struct sabi_retval *sretval) +static int sabi_command(struct samsung_laptop *samsung, u16 command, + struct sabi_data *in, + struct sabi_data *out) { - int retval = 0; - u16 port = readw(sabi + sabi_config->header_offsets.port); + const struct sabi_config *config = samsung->config; + int ret = 0; + u16 port = readw(samsung->sabi + config->header_offsets.port); u8 complete, iface_data; - mutex_lock(&sabi_mutex); - - /* enable memory to be able to write to it */ - outb(readb(sabi + sabi_config->header_offsets.en_mem), port); - - /* write out the command */ - writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN); - writew(command, sabi_iface + SABI_IFACE_SUB); - writeb(0, sabi_iface + SABI_IFACE_COMPLETE); - outb(readb(sabi + sabi_config->header_offsets.iface_func), port); - - /* write protect memory to make it safe */ - outb(readb(sabi + sabi_config->header_offsets.re_mem), port); + mutex_lock(&samsung->sabi_mutex); - /* see if the command actually succeeded */ - complete = readb(sabi_iface + SABI_IFACE_COMPLETE); - iface_data = readb(sabi_iface + SABI_IFACE_DATA); - if (complete != 0xaa || iface_data == 0xff) { - pr_warn("SABI get command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n", - command, complete, iface_data); - retval = -EINVAL; - goto exit; + if (debug) { + if (in) + pr_info("SABI command:0x%04x " + "data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}", + command, in->d0, in->d1, in->d2, in->d3); + else + pr_info("SABI command:0x%04x", command); } - /* - * Save off the data into a structure so the caller use it. - * Right now we only want the first 4 bytes, - * There are commands that need more, but not for the ones we - * currently care about. - */ - sretval->retval[0] = readb(sabi_iface + SABI_IFACE_DATA); - sretval->retval[1] = readb(sabi_iface + SABI_IFACE_DATA + 1); - sretval->retval[2] = readb(sabi_iface + SABI_IFACE_DATA + 2); - sretval->retval[3] = readb(sabi_iface + SABI_IFACE_DATA + 3); - -exit: - mutex_unlock(&sabi_mutex); - return retval; - -} - -static int sabi_set_command(u8 command, u8 data) -{ - int retval = 0; - u16 port = readw(sabi + sabi_config->header_offsets.port); - u8 complete, iface_data; - - mutex_lock(&sabi_mutex); /* enable memory to be able to write to it */ - outb(readb(sabi + sabi_config->header_offsets.en_mem), port); + outb(readb(samsung->sabi + config->header_offsets.en_mem), port); /* write out the command */ - writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN); - writew(command, sabi_iface + SABI_IFACE_SUB); - writeb(0, sabi_iface + SABI_IFACE_COMPLETE); - writeb(data, sabi_iface + SABI_IFACE_DATA); - outb(readb(sabi + sabi_config->header_offsets.iface_func), port); + writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN); + writew(command, samsung->sabi_iface + SABI_IFACE_SUB); + writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE); + if (in) { + writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA); + writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4); + writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8); + writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10); + } + outb(readb(samsung->sabi + config->header_offsets.iface_func), port); /* write protect memory to make it safe */ - outb(readb(sabi + sabi_config->header_offsets.re_mem), port); + outb(readb(samsung->sabi + config->header_offsets.re_mem), port); /* see if the command actually succeeded */ - complete = readb(sabi_iface + SABI_IFACE_COMPLETE); - iface_data = readb(sabi_iface + SABI_IFACE_DATA); + complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE); + iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA); + + /* iface_data = 0xFF happens when a command is not known + * so we only add a warning in debug mode since we will + * probably issue some unknown command at startup to find + * out which features are supported */ + if (complete != 0xaa || (iface_data == 0xff && debug)) + pr_warn("SABI command 0x%04x failed with" + " completion flag 0x%02x and interface data 0x%02x", + command, complete, iface_data); + if (complete != 0xaa || iface_data == 0xff) { - pr_warn("SABI set command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n", - command, complete, iface_data); - retval = -EINVAL; + ret = -EINVAL; + goto exit; } - mutex_unlock(&sabi_mutex); - return retval; -} - -static void test_backlight(void) -{ - struct sabi_retval sretval; - - sabi_get_command(sabi_config->commands.get_backlight, &sretval); - printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); - - sabi_set_command(sabi_config->commands.set_backlight, 0); - printk(KERN_DEBUG "backlight should be off\n"); - - sabi_get_command(sabi_config->commands.get_backlight, &sretval); - printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); - - msleep(1000); + if (out) { + out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA); + out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4); + out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2); + out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1); + } - sabi_set_command(sabi_config->commands.set_backlight, 1); - printk(KERN_DEBUG "backlight should be on\n"); + if (debug && out) { + pr_info("SABI return data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}", + out->d0, out->d1, out->d2, out->d3); + } - sabi_get_command(sabi_config->commands.get_backlight, &sretval); - printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); +exit: + mutex_unlock(&samsung->sabi_mutex); + return ret; } -static void test_wireless(void) +/* simple wrappers usable with most commands */ +static int sabi_set_commandb(struct samsung_laptop *samsung, + u16 command, u8 data) { - struct sabi_retval sretval; - - sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); - printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); - - sabi_set_command(sabi_config->commands.set_wireless_button, 0); - printk(KERN_DEBUG "wireless led should be off\n"); - - sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); - printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); + struct sabi_data in = { { { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 } } }; - msleep(1000); - - sabi_set_command(sabi_config->commands.set_wireless_button, 1); - printk(KERN_DEBUG "wireless led should be on\n"); - - sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); - printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); + in.data[0] = data; + return sabi_command(samsung, command, &in, NULL); } -static u8 read_brightness(void) +static int read_brightness(struct samsung_laptop *samsung) { - struct sabi_retval sretval; + const struct sabi_config *config = samsung->config; + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data sretval; int user_brightness = 0; int retval; - retval = sabi_get_command(sabi_config->commands.get_brightness, - &sretval); - if (!retval) { - user_brightness = sretval.retval[0]; - if (user_brightness > sabi_config->min_brightness) - user_brightness -= sabi_config->min_brightness; - else - user_brightness = 0; - } + retval = sabi_command(samsung, commands->get_brightness, + NULL, &sretval); + if (retval) + return retval; + + user_brightness = sretval.data[0]; + if (user_brightness > config->min_brightness) + user_brightness -= config->min_brightness; + else + user_brightness = 0; + return user_brightness; } -static void set_brightness(u8 user_brightness) +static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness) { - u8 user_level = user_brightness + sabi_config->min_brightness; + const struct sabi_config *config = samsung->config; + const struct sabi_commands *commands = &samsung->config->commands; + u8 user_level = user_brightness + config->min_brightness; - if (has_stepping_quirk && user_level != 0) { + if (samsung->has_stepping_quirk && user_level != 0) { /* * short circuit if the specified level is what's already set * to prevent the screen from flickering needlessly */ - if (user_brightness == read_brightness()) + if (user_brightness == read_brightness(samsung)) return; - sabi_set_command(sabi_config->commands.set_brightness, 0); + sabi_set_commandb(samsung, commands->set_brightness, 0); } - sabi_set_command(sabi_config->commands.set_brightness, user_level); + sabi_set_commandb(samsung, commands->set_brightness, user_level); } static int get_brightness(struct backlight_device *bd) { - return (int)read_brightness(); + struct samsung_laptop *samsung = bl_get_data(bd); + + return read_brightness(samsung); } -static void check_for_stepping_quirk(void) +static void check_for_stepping_quirk(struct samsung_laptop *samsung) { - u8 initial_level; - u8 check_level; - u8 orig_level = read_brightness(); + int initial_level; + int check_level; + int orig_level = read_brightness(samsung); /* * Some laptops exhibit the strange behaviour of stepping toward @@ -416,34 +512,38 @@ static void check_for_stepping_quirk(void) */ if (orig_level == 0) - set_brightness(1); + set_brightness(samsung, 1); - initial_level = read_brightness(); + initial_level = read_brightness(samsung); if (initial_level <= 2) check_level = initial_level + 2; else check_level = initial_level - 2; - has_stepping_quirk = false; - set_brightness(check_level); + samsung->has_stepping_quirk = false; + set_brightness(samsung, check_level); - if (read_brightness() != check_level) { - has_stepping_quirk = true; + if (read_brightness(samsung) != check_level) { + samsung->has_stepping_quirk = true; pr_info("enabled workaround for brightness stepping quirk\n"); } - set_brightness(orig_level); + set_brightness(samsung, orig_level); } static int update_status(struct backlight_device *bd) { - set_brightness(bd->props.brightness); + struct samsung_laptop *samsung = bl_get_data(bd); + const struct sabi_commands *commands = &samsung->config->commands; + + set_brightness(samsung, bd->props.brightness); if (bd->props.power == FB_BLANK_UNBLANK) - sabi_set_command(sabi_config->commands.set_backlight, 1); + sabi_set_commandb(samsung, commands->set_backlight, 1); else - sabi_set_command(sabi_config->commands.set_backlight, 0); + sabi_set_commandb(samsung, commands->set_backlight, 0); + return 0; } @@ -452,66 +552,101 @@ static const struct backlight_ops backlight_ops = { .update_status = update_status, }; -static int rfkill_set(void *data, bool blocked) +static int seclinux_rfkill_set(void *data, bool blocked) { - /* Do something with blocked...*/ - /* - * blocked == false is on - * blocked == true is off - */ - if (blocked) - sabi_set_command(sabi_config->commands.set_wireless_button, 0); - else - sabi_set_command(sabi_config->commands.set_wireless_button, 1); + struct samsung_rfkill *srfkill = data; + struct samsung_laptop *samsung = srfkill->samsung; + const struct sabi_commands *commands = &samsung->config->commands; - return 0; + return sabi_set_commandb(samsung, commands->set_wireless_button, + !blocked); } -static struct rfkill_ops rfkill_ops = { - .set_block = rfkill_set, +static struct rfkill_ops seclinux_rfkill_ops = { + .set_block = seclinux_rfkill_set, }; -static int init_wireless(struct platform_device *sdev) +static int swsmi_wireless_status(struct samsung_laptop *samsung, + struct sabi_data *data) { - int retval; + const struct sabi_commands *commands = &samsung->config->commands; - rfk = rfkill_alloc("samsung-wifi", &sdev->dev, RFKILL_TYPE_WLAN, - &rfkill_ops, NULL); - if (!rfk) - return -ENOMEM; - - retval = rfkill_register(rfk); - if (retval) { - rfkill_destroy(rfk); - return -ENODEV; - } + return sabi_command(samsung, commands->get_wireless_status, + NULL, data); +} - return 0; +static int swsmi_rfkill_set(void *priv, bool blocked) +{ + struct samsung_rfkill *srfkill = priv; + struct samsung_laptop *samsung = srfkill->samsung; + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; + int ret, i; + + ret = swsmi_wireless_status(samsung, &data); + if (ret) + return ret; + + /* Don't set the state for non-present devices */ + for (i = 0; i < 4; i++) + if (data.data[i] == 0x02) + data.data[1] = 0; + + if (srfkill->type == RFKILL_TYPE_WLAN) + data.data[WL_STATUS_WLAN] = !blocked; + else if (srfkill->type == RFKILL_TYPE_BLUETOOTH) + data.data[WL_STATUS_BT] = !blocked; + + return sabi_command(samsung, commands->set_wireless_status, + &data, &data); } -static void destroy_wireless(void) +static void swsmi_rfkill_query(struct rfkill *rfkill, void *priv) { - rfkill_unregister(rfk); - rfkill_destroy(rfk); + struct samsung_rfkill *srfkill = priv; + struct samsung_laptop *samsung = srfkill->samsung; + struct sabi_data data; + int ret; + + ret = swsmi_wireless_status(samsung, &data); + if (ret) + return ; + + if (srfkill->type == RFKILL_TYPE_WLAN) + ret = data.data[WL_STATUS_WLAN]; + else if (srfkill->type == RFKILL_TYPE_BLUETOOTH) + ret = data.data[WL_STATUS_BT]; + else + return ; + + rfkill_set_sw_state(rfkill, !ret); } +static struct rfkill_ops swsmi_rfkill_ops = { + .set_block = swsmi_rfkill_set, + .query = swsmi_rfkill_query, +}; + static ssize_t get_performance_level(struct device *dev, struct device_attribute *attr, char *buf) { - struct sabi_retval sretval; + struct samsung_laptop *samsung = dev_get_drvdata(dev); + const struct sabi_config *config = samsung->config; + const struct sabi_commands *commands = &config->commands; + struct sabi_data sretval; int retval; int i; /* Read the state */ - retval = sabi_get_command(sabi_config->commands.get_performance_level, - &sretval); + retval = sabi_command(samsung, commands->get_performance_level, + NULL, &sretval); if (retval) return retval; /* The logic is backwards, yeah, lots of fun... */ - for (i = 0; sabi_config->performance_levels[i].name; ++i) { - if (sretval.retval[0] == sabi_config->performance_levels[i].value) - return sprintf(buf, "%s\n", sabi_config->performance_levels[i].name); + for (i = 0; config->performance_levels[i].name; ++i) { + if (sretval.data[0] == config->performance_levels[i].value) + return sprintf(buf, "%s\n", config->performance_levels[i].name); } return sprintf(buf, "%s\n", "unknown"); } @@ -520,269 +655,178 @@ static ssize_t set_performance_level(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - if (count >= 1) { - int i; - for (i = 0; sabi_config->performance_levels[i].name; ++i) { - const struct sabi_performance_level *level = - &sabi_config->performance_levels[i]; - if (!strncasecmp(level->name, buf, strlen(level->name))) { - sabi_set_command(sabi_config->commands.set_performance_level, - level->value); - break; - } + struct samsung_laptop *samsung = dev_get_drvdata(dev); + const struct sabi_config *config = samsung->config; + const struct sabi_commands *commands = &config->commands; + int i; + + if (count < 1) + return count; + + for (i = 0; config->performance_levels[i].name; ++i) { + const struct sabi_performance_level *level = + &config->performance_levels[i]; + if (!strncasecmp(level->name, buf, strlen(level->name))) { + sabi_set_commandb(samsung, + commands->set_performance_level, + level->value); + break; } - if (!sabi_config->performance_levels[i].name) - return -EINVAL; } + + if (!config->performance_levels[i].name) + return -EINVAL; + return count; } + static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO, get_performance_level, set_performance_level); +static int read_battery_life_extender(struct samsung_laptop *samsung) +{ + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; + int retval; + + if (commands->get_battery_life_extender == 0xFFFF) + return -ENODEV; + + memset(&data, 0, sizeof(data)); + data.data[0] = 0x80; + retval = sabi_command(samsung, commands->get_battery_life_extender, + &data, &data); -static int __init dmi_check_cb(const struct dmi_system_id *id) + if (retval) + return retval; + + if (data.data[0] != 0 && data.data[0] != 1) + return -ENODEV; + + return data.data[0]; +} + +static int write_battery_life_extender(struct samsung_laptop *samsung, + int enabled) { - pr_info("found laptop model '%s'\n", - id->ident); - return 1; + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; + + memset(&data, 0, sizeof(data)); + data.data[0] = 0x80 | enabled; + return sabi_command(samsung, commands->set_battery_life_extender, + &data, NULL); } -static struct dmi_system_id __initdata samsung_dmi_table[] = { - { - .ident = "N128", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, - "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "N128"), - DMI_MATCH(DMI_BOARD_NAME, "N128"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "N130", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, - "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "N130"), - DMI_MATCH(DMI_BOARD_NAME, "N130"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "N510", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, - "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "N510"), - DMI_MATCH(DMI_BOARD_NAME, "N510"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "X125", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, - "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "X125"), - DMI_MATCH(DMI_BOARD_NAME, "X125"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "X120/X170", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, - "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"), - DMI_MATCH(DMI_BOARD_NAME, "X120/X170"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "NC10", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, - "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "NC10"), - DMI_MATCH(DMI_BOARD_NAME, "NC10"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "NP-Q45", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, - "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"), - DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "X360", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, - "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "X360"), - DMI_MATCH(DMI_BOARD_NAME, "X360"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "R410 Plus", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, - "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "R410P"), - DMI_MATCH(DMI_BOARD_NAME, "R460"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "R518", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, - "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "R518"), - DMI_MATCH(DMI_BOARD_NAME, "R518"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "R519/R719", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, - "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"), - DMI_MATCH(DMI_BOARD_NAME, "R519/R719"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "N150/N210/N220", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, - "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"), - DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "N220", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, - "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "N220"), - DMI_MATCH(DMI_BOARD_NAME, "N220"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "N150/N210/N220/N230", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, - "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"), - DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "N150P/N210P/N220P", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, - "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"), - DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "R700", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "SR700"), - DMI_MATCH(DMI_BOARD_NAME, "SR700"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "R530/R730", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"), - DMI_MATCH(DMI_BOARD_NAME, "R530/R730"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "NF110/NF210/NF310", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"), - DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "N145P/N250P/N260P", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"), - DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "R70/R71", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, - "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"), - DMI_MATCH(DMI_BOARD_NAME, "R70/R71"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "P460", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "P460"), - DMI_MATCH(DMI_BOARD_NAME, "P460"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "R528/R728", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "R528/R728"), - DMI_MATCH(DMI_BOARD_NAME, "R528/R728"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "NC210/NC110", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"), - DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"), - }, - .callback = dmi_check_cb, - }, - { - .ident = "X520", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), - DMI_MATCH(DMI_PRODUCT_NAME, "X520"), - DMI_MATCH(DMI_BOARD_NAME, "X520"), - }, - .callback = dmi_check_cb, - }, - { }, +static ssize_t get_battery_life_extender(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct samsung_laptop *samsung = dev_get_drvdata(dev); + int ret; + + ret = read_battery_life_extender(samsung); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", ret); +} + +static ssize_t set_battery_life_extender(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct samsung_laptop *samsung = dev_get_drvdata(dev); + int ret, value; + + if (!count || sscanf(buf, "%i", &value) != 1) + return -EINVAL; + + ret = write_battery_life_extender(samsung, !!value); + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO, + get_battery_life_extender, set_battery_life_extender); + +static int read_usb_charge(struct samsung_laptop *samsung) +{ + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; + int retval; + + if (commands->get_usb_charge == 0xFFFF) + return -ENODEV; + + memset(&data, 0, sizeof(data)); + data.data[0] = 0x80; + retval = sabi_command(samsung, commands->get_usb_charge, + &data, &data); + + if (retval) + return retval; + + if (data.data[0] != 0 && data.data[0] != 1) + return -ENODEV; + + return data.data[0]; +} + +static int write_usb_charge(struct samsung_laptop *samsung, + int enabled) +{ + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; + + memset(&data, 0, sizeof(data)); + data.data[0] = 0x80 | enabled; + return sabi_command(samsung, commands->set_usb_charge, + &data, NULL); +} + +static ssize_t get_usb_charge(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct samsung_laptop *samsung = dev_get_drvdata(dev); + int ret; + + ret = read_usb_charge(samsung); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", ret); +} + +static ssize_t set_usb_charge(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct samsung_laptop *samsung = dev_get_drvdata(dev); + int ret, value; + + if (!count || sscanf(buf, "%i", &value) != 1) + return -EINVAL; + + ret = write_usb_charge(samsung, !!value); + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO, + get_usb_charge, set_usb_charge); + +static struct attribute *platform_attributes[] = { + &dev_attr_performance_level.attr, + &dev_attr_battery_life_extender.attr, + &dev_attr_usb_charge.attr, + NULL }; -MODULE_DEVICE_TABLE(dmi, samsung_dmi_table); static int find_signature(void __iomem *memcheck, const char *testStr) { @@ -803,153 +847,772 @@ static int find_signature(void __iomem *memcheck, const char *testStr) return loca; } -static int __init samsung_init(void) +static void samsung_rfkill_exit(struct samsung_laptop *samsung) { - struct backlight_properties props; - struct sabi_retval sretval; - unsigned int ifaceP; - int i; - int loca; + if (samsung->wlan.rfkill) { + rfkill_unregister(samsung->wlan.rfkill); + rfkill_destroy(samsung->wlan.rfkill); + samsung->wlan.rfkill = NULL; + } + if (samsung->bluetooth.rfkill) { + rfkill_unregister(samsung->bluetooth.rfkill); + rfkill_destroy(samsung->bluetooth.rfkill); + samsung->bluetooth.rfkill = NULL; + } +} + +static int samsung_new_rfkill(struct samsung_laptop *samsung, + struct samsung_rfkill *arfkill, + const char *name, enum rfkill_type type, + const struct rfkill_ops *ops, + int blocked) +{ + struct rfkill **rfkill = &arfkill->rfkill; + int ret; + + arfkill->type = type; + arfkill->samsung = samsung; + + *rfkill = rfkill_alloc(name, &samsung->platform_device->dev, + type, ops, arfkill); + + if (!*rfkill) + return -EINVAL; + + if (blocked != -1) + rfkill_init_sw_state(*rfkill, blocked); + + ret = rfkill_register(*rfkill); + if (ret) { + rfkill_destroy(*rfkill); + *rfkill = NULL; + return ret; + } + return 0; +} + +static int __init samsung_rfkill_init_seclinux(struct samsung_laptop *samsung) +{ + return samsung_new_rfkill(samsung, &samsung->wlan, "samsung-wlan", + RFKILL_TYPE_WLAN, &seclinux_rfkill_ops, -1); +} + +static int __init samsung_rfkill_init_swsmi(struct samsung_laptop *samsung) +{ + struct sabi_data data; + int ret; + + ret = swsmi_wireless_status(samsung, &data); + if (ret) { + /* Some swsmi laptops use the old seclinux way to control + * wireless devices */ + if (ret == -EINVAL) + ret = samsung_rfkill_init_seclinux(samsung); + return ret; + } + + /* 0x02 seems to mean that the device is no present/available */ + + if (data.data[WL_STATUS_WLAN] != 0x02) + ret = samsung_new_rfkill(samsung, &samsung->wlan, + "samsung-wlan", + RFKILL_TYPE_WLAN, + &swsmi_rfkill_ops, + !data.data[WL_STATUS_WLAN]); + if (ret) + goto exit; + + if (data.data[WL_STATUS_BT] != 0x02) + ret = samsung_new_rfkill(samsung, &samsung->bluetooth, + "samsung-bluetooth", + RFKILL_TYPE_BLUETOOTH, + &swsmi_rfkill_ops, + !data.data[WL_STATUS_BT]); + if (ret) + goto exit; + +exit: + if (ret) + samsung_rfkill_exit(samsung); + + return ret; +} + +static int __init samsung_rfkill_init(struct samsung_laptop *samsung) +{ + if (samsung->config->sabi_version == 2) + return samsung_rfkill_init_seclinux(samsung); + if (samsung->config->sabi_version == 3) + return samsung_rfkill_init_swsmi(samsung); + return 0; +} + +static int kbd_backlight_enable(struct samsung_laptop *samsung) +{ + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; int retval; - mutex_init(&sabi_mutex); + if (commands->kbd_backlight == 0xFFFF) + return -ENODEV; + + memset(&data, 0, sizeof(data)); + data.d0 = 0xaabb; + retval = sabi_command(samsung, commands->kbd_backlight, + &data, &data); - if (!force && !dmi_check_system(samsung_dmi_table)) + if (retval) + return retval; + + if (data.d0 != 0xccdd) return -ENODEV; + return 0; +} - f0000_segment = ioremap_nocache(0xf0000, 0xffff); - if (!f0000_segment) { - pr_err("Can't map the segment at 0xf0000\n"); - return -EINVAL; +static int kbd_backlight_read(struct samsung_laptop *samsung) +{ + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; + int retval; + + memset(&data, 0, sizeof(data)); + data.data[0] = 0x81; + retval = sabi_command(samsung, commands->kbd_backlight, + &data, &data); + + if (retval) + return retval; + + return data.data[0]; +} + +static int kbd_backlight_write(struct samsung_laptop *samsung, int brightness) +{ + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; + + memset(&data, 0, sizeof(data)); + data.d0 = 0x82 | ((brightness & 0xFF) << 8); + return sabi_command(samsung, commands->kbd_backlight, + &data, NULL); +} + +static void kbd_led_update(struct work_struct *work) +{ + struct samsung_laptop *samsung; + + samsung = container_of(work, struct samsung_laptop, kbd_led_work); + kbd_backlight_write(samsung, samsung->kbd_led_wk); +} + +static void kbd_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct samsung_laptop *samsung; + + samsung = container_of(led_cdev, struct samsung_laptop, kbd_led); + + if (value > samsung->kbd_led.max_brightness) + value = samsung->kbd_led.max_brightness; + else if (value < 0) + value = 0; + + samsung->kbd_led_wk = value; + queue_work(samsung->led_workqueue, &samsung->kbd_led_work); +} + +static enum led_brightness kbd_led_get(struct led_classdev *led_cdev) +{ + struct samsung_laptop *samsung; + + samsung = container_of(led_cdev, struct samsung_laptop, kbd_led); + return kbd_backlight_read(samsung); +} + +static void samsung_leds_exit(struct samsung_laptop *samsung) +{ + if (!IS_ERR_OR_NULL(samsung->kbd_led.dev)) + led_classdev_unregister(&samsung->kbd_led); + if (samsung->led_workqueue) + destroy_workqueue(samsung->led_workqueue); +} + +static int __init samsung_leds_init(struct samsung_laptop *samsung) +{ + int ret = 0; + + samsung->led_workqueue = create_singlethread_workqueue("led_workqueue"); + if (!samsung->led_workqueue) + return -ENOMEM; + + if (kbd_backlight_enable(samsung) >= 0) { + INIT_WORK(&samsung->kbd_led_work, kbd_led_update); + + samsung->kbd_led.name = "samsung::kbd_backlight"; + samsung->kbd_led.brightness_set = kbd_led_set; + samsung->kbd_led.brightness_get = kbd_led_get; + samsung->kbd_led.max_brightness = 8; + + ret = led_classdev_register(&samsung->platform_device->dev, + &samsung->kbd_led); + } + + if (ret) + samsung_leds_exit(samsung); + + return ret; +} + +static void samsung_backlight_exit(struct samsung_laptop *samsung) +{ + if (samsung->backlight_device) { + backlight_device_unregister(samsung->backlight_device); + samsung->backlight_device = NULL; + } +} + +static int __init samsung_backlight_init(struct samsung_laptop *samsung) +{ + struct backlight_device *bd; + struct backlight_properties props; + + if (!samsung->handle_backlight) + return 0; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = samsung->config->max_brightness - + samsung->config->min_brightness; + + bd = backlight_device_register("samsung", + &samsung->platform_device->dev, + samsung, &backlight_ops, + &props); + if (IS_ERR(bd)) + return PTR_ERR(bd); + + samsung->backlight_device = bd; + samsung->backlight_device->props.brightness = read_brightness(samsung); + samsung->backlight_device->props.power = FB_BLANK_UNBLANK; + backlight_update_status(samsung->backlight_device); + + return 0; +} + +static umode_t samsung_sysfs_is_visible(struct kobject *kobj, + struct attribute *attr, int idx) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct platform_device *pdev = to_platform_device(dev); + struct samsung_laptop *samsung = platform_get_drvdata(pdev); + bool ok = true; + + if (attr == &dev_attr_performance_level.attr) + ok = !!samsung->config->performance_levels[0].name; + if (attr == &dev_attr_battery_life_extender.attr) + ok = !!(read_battery_life_extender(samsung) >= 0); + if (attr == &dev_attr_usb_charge.attr) + ok = !!(read_usb_charge(samsung) >= 0); + + return ok ? attr->mode : 0; +} + +static struct attribute_group platform_attribute_group = { + .is_visible = samsung_sysfs_is_visible, + .attrs = platform_attributes +}; + +static void samsung_sysfs_exit(struct samsung_laptop *samsung) +{ + struct platform_device *device = samsung->platform_device; + + sysfs_remove_group(&device->dev.kobj, &platform_attribute_group); +} + +static int __init samsung_sysfs_init(struct samsung_laptop *samsung) +{ + struct platform_device *device = samsung->platform_device; + + return sysfs_create_group(&device->dev.kobj, &platform_attribute_group); + +} + +static int show_call(struct seq_file *m, void *data) +{ + struct samsung_laptop *samsung = m->private; + struct sabi_data *sdata = &samsung->debug.data; + int ret; + + seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n", + samsung->debug.command, + sdata->d0, sdata->d1, sdata->d2, sdata->d3); + + ret = sabi_command(samsung, samsung->debug.command, sdata, sdata); + + if (ret) { + seq_printf(m, "SABI command 0x%04x failed\n", + samsung->debug.command); + return ret; + } + + seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n", + sdata->d0, sdata->d1, sdata->d2, sdata->d3); + return 0; +} + +static int samsung_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, show_call, inode->i_private); +} + +static const struct file_operations samsung_laptop_call_io_ops = { + .owner = THIS_MODULE, + .open = samsung_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void samsung_debugfs_exit(struct samsung_laptop *samsung) +{ + debugfs_remove_recursive(samsung->debug.root); +} + +static int samsung_debugfs_init(struct samsung_laptop *samsung) +{ + struct dentry *dent; + + samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL); + if (!samsung->debug.root) { + pr_err("failed to create debugfs directory"); + goto error_debugfs; + } + + samsung->debug.f0000_wrapper.data = samsung->f0000_segment; + samsung->debug.f0000_wrapper.size = 0xffff; + + samsung->debug.data_wrapper.data = &samsung->debug.data; + samsung->debug.data_wrapper.size = sizeof(samsung->debug.data); + + samsung->debug.sdiag_wrapper.data = samsung->sdiag; + samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag); + + dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR, + samsung->debug.root, &samsung->debug.command); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root, + &samsung->debug.data.d0); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root, + &samsung->debug.data.d1); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root, + &samsung->debug.data.d2); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root, + &samsung->debug.data.d3); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR, + samsung->debug.root, + &samsung->debug.data_wrapper); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR, + samsung->debug.root, + &samsung->debug.f0000_wrapper); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_file("call", S_IFREG | S_IRUGO, + samsung->debug.root, samsung, + &samsung_laptop_call_io_ops); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR, + samsung->debug.root, + &samsung->debug.sdiag_wrapper); + if (!dent) + goto error_debugfs; + + return 0; + +error_debugfs: + samsung_debugfs_exit(samsung); + return -ENOMEM; +} + +static void samsung_sabi_exit(struct samsung_laptop *samsung) +{ + const struct sabi_config *config = samsung->config; + + /* Turn off "Linux" mode in the BIOS */ + if (config && config->commands.set_linux != 0xff) + sabi_set_commandb(samsung, config->commands.set_linux, 0x80); + + if (samsung->sabi_iface) { + iounmap(samsung->sabi_iface); + samsung->sabi_iface = NULL; + } + if (samsung->f0000_segment) { + iounmap(samsung->f0000_segment); + samsung->f0000_segment = NULL; + } + + samsung->config = NULL; +} + +static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca, + unsigned int ifaceP) +{ + const struct sabi_config *config = samsung->config; + + printk(KERN_DEBUG "This computer supports SABI==%x\n", + loca + 0xf0000 - 6); + + printk(KERN_DEBUG "SABI header:\n"); + printk(KERN_DEBUG " SMI Port Number = 0x%04x\n", + readw(samsung->sabi + config->header_offsets.port)); + printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n", + readb(samsung->sabi + config->header_offsets.iface_func)); + printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n", + readb(samsung->sabi + config->header_offsets.en_mem)); + printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n", + readb(samsung->sabi + config->header_offsets.re_mem)); + printk(KERN_DEBUG " SABI data offset = 0x%04x\n", + readw(samsung->sabi + config->header_offsets.data_offset)); + printk(KERN_DEBUG " SABI data segment = 0x%04x\n", + readw(samsung->sabi + config->header_offsets.data_segment)); + + printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP); +} + +static void __init samsung_sabi_diag(struct samsung_laptop *samsung) +{ + int loca = find_signature(samsung->f0000_segment, "SDiaG@"); + int i; + + if (loca == 0xffff) + return ; + + /* Example: + * Ident: @SDiaG@686XX-N90X3A/966-SEC-07HL-S90X3A + * + * Product name: 90X3A + * BIOS Version: 07HL + */ + loca += 1; + for (i = 0; loca < 0xffff && i < sizeof(samsung->sdiag) - 1; loca++) { + char temp = readb(samsung->f0000_segment + loca); + + if (isalnum(temp) || temp == '/' || temp == '-') + samsung->sdiag[i++] = temp; + else + break ; } + if (debug && samsung->sdiag[0]) + pr_info("sdiag: %s", samsung->sdiag); +} + +static int __init samsung_sabi_init(struct samsung_laptop *samsung) +{ + const struct sabi_config *config = NULL; + const struct sabi_commands *commands; + unsigned int ifaceP; + int ret = 0; + int i; + int loca; + + samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff); + if (!samsung->f0000_segment) { + if (debug || force) + pr_err("Can't map the segment at 0xf0000\n"); + ret = -EINVAL; + goto exit; + } + + samsung_sabi_diag(samsung); + /* Try to find one of the signatures in memory to find the header */ for (i = 0; sabi_configs[i].test_string != 0; ++i) { - sabi_config = &sabi_configs[i]; - loca = find_signature(f0000_segment, sabi_config->test_string); + samsung->config = &sabi_configs[i]; + loca = find_signature(samsung->f0000_segment, + samsung->config->test_string); if (loca != 0xffff) break; } if (loca == 0xffff) { - pr_err("This computer does not support SABI\n"); - goto error_no_signature; + if (debug || force) + pr_err("This computer does not support SABI\n"); + ret = -ENODEV; + goto exit; } + config = samsung->config; + commands = &config->commands; + /* point to the SMI port Number */ loca += 1; - sabi = (f0000_segment + loca); - - if (debug) { - printk(KERN_DEBUG "This computer supports SABI==%x\n", - loca + 0xf0000 - 6); - printk(KERN_DEBUG "SABI header:\n"); - printk(KERN_DEBUG " SMI Port Number = 0x%04x\n", - readw(sabi + sabi_config->header_offsets.port)); - printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n", - readb(sabi + sabi_config->header_offsets.iface_func)); - printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n", - readb(sabi + sabi_config->header_offsets.en_mem)); - printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n", - readb(sabi + sabi_config->header_offsets.re_mem)); - printk(KERN_DEBUG " SABI data offset = 0x%04x\n", - readw(sabi + sabi_config->header_offsets.data_offset)); - printk(KERN_DEBUG " SABI data segment = 0x%04x\n", - readw(sabi + sabi_config->header_offsets.data_segment)); - } + samsung->sabi = (samsung->f0000_segment + loca); /* Get a pointer to the SABI Interface */ - ifaceP = (readw(sabi + sabi_config->header_offsets.data_segment) & 0x0ffff) << 4; - ifaceP += readw(sabi + sabi_config->header_offsets.data_offset) & 0x0ffff; - sabi_iface = ioremap_nocache(ifaceP, 16); - if (!sabi_iface) { - pr_err("Can't remap %x\n", ifaceP); - goto error_no_signature; - } - if (debug) { - printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP); - printk(KERN_DEBUG "sabi_iface = %p\n", sabi_iface); + ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4; + ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff; - test_backlight(); - test_wireless(); + if (debug) + samsung_sabi_infos(samsung, loca, ifaceP); - retval = sabi_get_command(sabi_config->commands.get_brightness, - &sretval); - printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]); + samsung->sabi_iface = ioremap_nocache(ifaceP, 16); + if (!samsung->sabi_iface) { + pr_err("Can't remap %x\n", ifaceP); + ret = -EINVAL; + goto exit; } /* Turn on "Linux" mode in the BIOS */ - if (sabi_config->commands.set_linux != 0xff) { - retval = sabi_set_command(sabi_config->commands.set_linux, - 0x81); + if (commands->set_linux != 0xff) { + int retval = sabi_set_commandb(samsung, + commands->set_linux, 0x81); if (retval) { pr_warn("Linux mode was not set!\n"); - goto error_no_platform; + ret = -ENODEV; + goto exit; } } /* Check for stepping quirk */ - check_for_stepping_quirk(); + if (samsung->handle_backlight) + check_for_stepping_quirk(samsung); - /* knock up a platform device to hang stuff off of */ - sdev = platform_device_register_simple("samsung", -1, NULL, 0); - if (IS_ERR(sdev)) - goto error_no_platform; + pr_info("detected SABI interface: %s\n", + samsung->config->test_string); - /* create a backlight device to talk to this one */ - memset(&props, 0, sizeof(struct backlight_properties)); - props.type = BACKLIGHT_PLATFORM; - props.max_brightness = sabi_config->max_brightness - - sabi_config->min_brightness; - backlight_device = backlight_device_register("samsung", &sdev->dev, - NULL, &backlight_ops, - &props); - if (IS_ERR(backlight_device)) - goto error_no_backlight; - - backlight_device->props.brightness = read_brightness(); - backlight_device->props.power = FB_BLANK_UNBLANK; - backlight_update_status(backlight_device); - - retval = init_wireless(sdev); - if (retval) - goto error_no_rfk; +exit: + if (ret) + samsung_sabi_exit(samsung); - retval = device_create_file(&sdev->dev, &dev_attr_performance_level); - if (retval) - goto error_file_create; + return ret; +} + +static void samsung_platform_exit(struct samsung_laptop *samsung) +{ + if (samsung->platform_device) { + platform_device_unregister(samsung->platform_device); + samsung->platform_device = NULL; + } +} + +static int __init samsung_platform_init(struct samsung_laptop *samsung) +{ + struct platform_device *pdev; + + pdev = platform_device_register_simple("samsung", -1, NULL, 0); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + samsung->platform_device = pdev; + platform_set_drvdata(samsung->platform_device, samsung); return 0; +} + +static struct samsung_quirks *quirks; + +static int __init samsung_dmi_matched(const struct dmi_system_id *d) +{ + quirks = d->driver_data; + return 0; +} + +static struct dmi_system_id __initdata samsung_dmi_table[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, + "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, + "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /* Laptop */ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, + "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /* Notebook */ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, + "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_CHASSIS_TYPE, "14"), /* Sub-Notebook */ + }, + }, + /* Specific DMI ids for laptop with quirks */ + { + .callback = samsung_dmi_matched, + .ident = "N150P", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "N150P"), + DMI_MATCH(DMI_BOARD_NAME, "N150P"), + }, + .driver_data = &samsung_broken_acpi_video, + }, + { + .callback = samsung_dmi_matched, + .ident = "N145P/N250P/N260P", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"), + DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"), + }, + .driver_data = &samsung_broken_acpi_video, + }, + { + .callback = samsung_dmi_matched, + .ident = "N150/N210/N220", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"), + DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"), + }, + .driver_data = &samsung_broken_acpi_video, + }, + { + .callback = samsung_dmi_matched, + .ident = "NF110/NF210/NF310", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"), + DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"), + }, + .driver_data = &samsung_broken_acpi_video, + }, + { }, +}; +MODULE_DEVICE_TABLE(dmi, samsung_dmi_table); -error_file_create: - destroy_wireless(); +static struct platform_device *samsung_platform_device; -error_no_rfk: - backlight_device_unregister(backlight_device); +static int __init samsung_init(void) +{ + struct samsung_laptop *samsung; + int ret; -error_no_backlight: - platform_device_unregister(sdev); + quirks = &samsung_unknown; + if (!force && !dmi_check_system(samsung_dmi_table)) + return -ENODEV; + + samsung = kzalloc(sizeof(*samsung), GFP_KERNEL); + if (!samsung) + return -ENOMEM; -error_no_platform: - iounmap(sabi_iface); + mutex_init(&samsung->sabi_mutex); + samsung->handle_backlight = true; + samsung->quirks = quirks; -error_no_signature: - iounmap(f0000_segment); - return -EINVAL; + +#if (defined CONFIG_ACPI_VIDEO || defined CONFIG_ACPI_VIDEO_MODULE) + /* Don't handle backlight here if the acpi video already handle it */ + if (acpi_video_backlight_support()) { + if (samsung->quirks->broken_acpi_video) { + pr_info("Disabling ACPI video driver\n"); + acpi_video_unregister(); + } else { + samsung->handle_backlight = false; + } + } +#endif + + ret = samsung_platform_init(samsung); + if (ret) + goto error_platform; + + ret = samsung_sabi_init(samsung); + if (ret) + goto error_sabi; + +#ifdef CONFIG_ACPI + /* Only log that if we are really on a sabi platform */ + if (acpi_video_backlight_support() && + !samsung->quirks->broken_acpi_video) + pr_info("Backlight controlled by ACPI video driver\n"); +#endif + + ret = samsung_sysfs_init(samsung); + if (ret) + goto error_sysfs; + + ret = samsung_backlight_init(samsung); + if (ret) + goto error_backlight; + + ret = samsung_rfkill_init(samsung); + if (ret) + goto error_rfkill; + + ret = samsung_leds_init(samsung); + if (ret) + goto error_leds; + + ret = samsung_debugfs_init(samsung); + if (ret) + goto error_debugfs; + + samsung_platform_device = samsung->platform_device; + return ret; + +error_debugfs: + samsung_leds_exit(samsung); +error_leds: + samsung_rfkill_exit(samsung); +error_rfkill: + samsung_backlight_exit(samsung); +error_backlight: + samsung_sysfs_exit(samsung); +error_sysfs: + samsung_sabi_exit(samsung); +error_sabi: + samsung_platform_exit(samsung); +error_platform: + kfree(samsung); + return ret; } static void __exit samsung_exit(void) { - /* Turn off "Linux" mode in the BIOS */ - if (sabi_config->commands.set_linux != 0xff) - sabi_set_command(sabi_config->commands.set_linux, 0x80); - - device_remove_file(&sdev->dev, &dev_attr_performance_level); - backlight_device_unregister(backlight_device); - destroy_wireless(); - iounmap(sabi_iface); - iounmap(f0000_segment); - platform_device_unregister(sdev); + struct samsung_laptop *samsung; + + samsung = platform_get_drvdata(samsung_platform_device); + + samsung_debugfs_exit(samsung); + samsung_leds_exit(samsung); + samsung_rfkill_exit(samsung); + samsung_backlight_exit(samsung); + samsung_sysfs_exit(samsung); + samsung_sabi_exit(samsung); + samsung_platform_exit(samsung); + + kfree(samsung); + samsung_platform_device = NULL; } module_init(samsung_init); diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index c006dee5ebfe..8a51795aa02a 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -127,7 +127,7 @@ MODULE_PARM_DESC(minor, "default is -1 (automatic)"); #endif -static int kbd_backlight; /* = 1 */ +static int kbd_backlight = 1; module_param(kbd_backlight, int, 0444); MODULE_PARM_DESC(kbd_backlight, "set this to 0 to disable keyboard backlight, " @@ -347,6 +347,7 @@ static void sony_laptop_report_input_event(u8 event) struct input_dev *jog_dev = sony_laptop_input.jog_dev; struct input_dev *key_dev = sony_laptop_input.key_dev; struct sony_laptop_keypress kp = { NULL }; + int scancode = -1; if (event == SONYPI_EVENT_FNKEY_RELEASED || event == SONYPI_EVENT_ANYBUTTON_RELEASED) { @@ -380,8 +381,8 @@ static void sony_laptop_report_input_event(u8 event) dprintk("sony_laptop_report_input_event, event not known: %d\n", event); break; } - if (sony_laptop_input_index[event] != -1) { - kp.key = sony_laptop_input_keycode_map[sony_laptop_input_index[event]]; + if ((scancode = sony_laptop_input_index[event]) != -1) { + kp.key = sony_laptop_input_keycode_map[scancode]; if (kp.key != KEY_UNKNOWN) kp.dev = key_dev; } @@ -389,9 +390,11 @@ static void sony_laptop_report_input_event(u8 event) } if (kp.dev) { + /* if we have a scancode we emit it so we can always + remap the key */ + if (scancode != -1) + input_event(kp.dev, EV_MSC, MSC_SCAN, scancode); input_report_key(kp.dev, kp.key, 1); - /* we emit the scancode so we can always remap the key */ - input_event(kp.dev, EV_MSC, MSC_SCAN, event); input_sync(kp.dev); /* schedule key release */ @@ -466,7 +469,7 @@ static int sony_laptop_setup_input(struct acpi_device *acpi_device) jog_dev->name = "Sony Vaio Jogdial"; jog_dev->id.bustype = BUS_ISA; jog_dev->id.vendor = PCI_VENDOR_ID_SONY; - key_dev->dev.parent = &acpi_device->dev; + jog_dev->dev.parent = &acpi_device->dev; input_set_capability(jog_dev, EV_KEY, BTN_MIDDLE); input_set_capability(jog_dev, EV_REL, REL_WHEEL); diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index ea0c6075b720..d68c0002f4a2 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -8658,7 +8658,7 @@ static int __must_check __init get_thinkpad_model_data( } s = dmi_get_system_info(DMI_PRODUCT_VERSION); - if (s && !strnicmp(s, "ThinkPad", 8)) { + if (s && !(strnicmp(s, "ThinkPad", 8) && strnicmp(s, "Lenovo", 6))) { tp->model_str = kstrdup(s, GFP_KERNEL); if (!tp->model_str) return -ENOMEM; diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index dcdc1f4a4624..ee79ce64d9df 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -52,6 +52,8 @@ #include <linux/input/sparse-keymap.h> #include <linux/leds.h> #include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/i8042.h> #include <asm/uaccess.h> @@ -61,6 +63,11 @@ MODULE_AUTHOR("John Belmonte"); MODULE_DESCRIPTION("Toshiba Laptop ACPI Extras Driver"); MODULE_LICENSE("GPL"); +#define TOSHIBA_WMI_EVENT_GUID "59142400-C6A3-40FA-BADB-8A2652834100" + +/* Scan code for Fn key on TOS1900 models */ +#define TOS1900_FN_SCAN 0x6e + /* Toshiba ACPI method paths */ #define METHOD_VIDEO_OUT "\\_SB_.VALX.DSSX" @@ -95,6 +102,8 @@ MODULE_LICENSE("GPL"); #define HCI_WIRELESS 0x0056 /* field definitions */ +#define HCI_HOTKEY_DISABLE 0x0b +#define HCI_HOTKEY_ENABLE 0x09 #define HCI_LCD_BRIGHTNESS_BITS 3 #define HCI_LCD_BRIGHTNESS_SHIFT (16-HCI_LCD_BRIGHTNESS_BITS) #define HCI_LCD_BRIGHTNESS_LEVELS (1 << HCI_LCD_BRIGHTNESS_BITS) @@ -111,6 +120,7 @@ struct toshiba_acpi_dev { const char *method_hci; struct rfkill *bt_rfk; struct input_dev *hotkey_dev; + struct work_struct hotkey_work; struct backlight_device *backlight_dev; struct led_classdev led_dev; @@ -118,14 +128,18 @@ struct toshiba_acpi_dev { int last_key_event; int key_event_valid; - int illumination_supported:1; - int video_supported:1; - int fan_supported:1; - int system_event_supported:1; + unsigned int illumination_supported:1; + unsigned int video_supported:1; + unsigned int fan_supported:1; + unsigned int system_event_supported:1; + unsigned int ntfy_supported:1; + unsigned int info_supported:1; struct mutex mutex; }; +static struct toshiba_acpi_dev *toshiba_acpi; + static const struct acpi_device_id toshiba_device_ids[] = { {"TOS6200", 0}, {"TOS6208", 0}, @@ -138,6 +152,8 @@ static const struct key_entry toshiba_acpi_keymap[] __devinitconst = { { KE_KEY, 0x101, { KEY_MUTE } }, { KE_KEY, 0x102, { KEY_ZOOMOUT } }, { KE_KEY, 0x103, { KEY_ZOOMIN } }, + { KE_KEY, 0x12c, { KEY_KBDILLUMTOGGLE } }, + { KE_KEY, 0x139, { KEY_ZOOMRESET } }, { KE_KEY, 0x13b, { KEY_COFFEE } }, { KE_KEY, 0x13c, { KEY_BATTERY } }, { KE_KEY, 0x13d, { KEY_SLEEP } }, @@ -146,7 +162,7 @@ static const struct key_entry toshiba_acpi_keymap[] __devinitconst = { { KE_KEY, 0x140, { KEY_BRIGHTNESSDOWN } }, { KE_KEY, 0x141, { KEY_BRIGHTNESSUP } }, { KE_KEY, 0x142, { KEY_WLAN } }, - { KE_KEY, 0x143, { KEY_PROG1 } }, + { KE_KEY, 0x143, { KEY_TOUCHPAD_TOGGLE } }, { KE_KEY, 0x17f, { KEY_FN } }, { KE_KEY, 0xb05, { KEY_PROG2 } }, { KE_KEY, 0xb06, { KEY_WWW } }, @@ -156,6 +172,7 @@ static const struct key_entry toshiba_acpi_keymap[] __devinitconst = { { KE_KEY, 0xb32, { KEY_NEXTSONG } }, { KE_KEY, 0xb33, { KEY_PLAYPAUSE } }, { KE_KEY, 0xb5a, { KEY_MEDIA } }, + { KE_IGNORE, 0x1430, { KEY_RESERVED } }, { KE_END, 0 }, }; @@ -847,10 +864,78 @@ static const struct backlight_ops toshiba_backlight_data = { .update_status = set_lcd_status, }; +static bool toshiba_acpi_i8042_filter(unsigned char data, unsigned char str, + struct serio *port) +{ + if (str & 0x20) + return false; + + if (unlikely(data == 0xe0)) + return false; + + if ((data & 0x7f) == TOS1900_FN_SCAN) { + schedule_work(&toshiba_acpi->hotkey_work); + return true; + } + + return false; +} + +static void toshiba_acpi_hotkey_work(struct work_struct *work) +{ + acpi_handle ec_handle = ec_get_handle(); + acpi_status status; + + if (!ec_handle) + return; + + status = acpi_evaluate_object(ec_handle, "NTFY", NULL, NULL); + if (ACPI_FAILURE(status)) + pr_err("ACPI NTFY method execution failed\n"); +} + +/* + * Returns hotkey scancode, or < 0 on failure. + */ +static int toshiba_acpi_query_hotkey(struct toshiba_acpi_dev *dev) +{ + struct acpi_buffer buf; + union acpi_object out_obj; + acpi_status status; + + buf.pointer = &out_obj; + buf.length = sizeof(out_obj); + + status = acpi_evaluate_object(dev->acpi_dev->handle, "INFO", + NULL, &buf); + if (ACPI_FAILURE(status) || out_obj.type != ACPI_TYPE_INTEGER) { + pr_err("ACPI INFO method execution failed\n"); + return -EIO; + } + + return out_obj.integer.value; +} + +static void toshiba_acpi_report_hotkey(struct toshiba_acpi_dev *dev, + int scancode) +{ + if (scancode == 0x100) + return; + + /* act on key press; ignore key release */ + if (scancode & 0x80) + return; + + if (!sparse_keymap_report_event(dev->hotkey_dev, scancode, 1, true)) + pr_info("Unknown key %x\n", scancode); +} + static int __devinit toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev) { acpi_status status; + acpi_handle ec_handle, handle; int error; + u32 hci_result; dev->hotkey_dev = input_allocate_device(); if (!dev->hotkey_dev) { @@ -866,21 +951,67 @@ static int __devinit toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev) if (error) goto err_free_dev; + /* + * For some machines the SCI responsible for providing hotkey + * notification doesn't fire. We can trigger the notification + * whenever the Fn key is pressed using the NTFY method, if + * supported, so if it's present set up an i8042 key filter + * for this purpose. + */ + status = AE_ERROR; + ec_handle = ec_get_handle(); + if (ec_handle) + status = acpi_get_handle(ec_handle, "NTFY", &handle); + + if (ACPI_SUCCESS(status)) { + INIT_WORK(&dev->hotkey_work, toshiba_acpi_hotkey_work); + + error = i8042_install_filter(toshiba_acpi_i8042_filter); + if (error) { + pr_err("Error installing key filter\n"); + goto err_free_keymap; + } + + dev->ntfy_supported = 1; + } + + /* + * Determine hotkey query interface. Prefer using the INFO + * method when it is available. + */ + status = acpi_get_handle(dev->acpi_dev->handle, "INFO", &handle); + if (ACPI_SUCCESS(status)) { + dev->info_supported = 1; + } else { + hci_write1(dev, HCI_SYSTEM_EVENT, 1, &hci_result); + if (hci_result == HCI_SUCCESS) + dev->system_event_supported = 1; + } + + if (!dev->info_supported && !dev->system_event_supported) { + pr_warn("No hotkey query interface found\n"); + goto err_remove_filter; + } + status = acpi_evaluate_object(dev->acpi_dev->handle, "ENAB", NULL, NULL); if (ACPI_FAILURE(status)) { pr_info("Unable to enable hotkeys\n"); error = -ENODEV; - goto err_free_keymap; + goto err_remove_filter; } error = input_register_device(dev->hotkey_dev); if (error) { pr_info("Unable to register input device\n"); - goto err_free_keymap; + goto err_remove_filter; } + hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE, &hci_result); return 0; + err_remove_filter: + if (dev->ntfy_supported) + i8042_remove_filter(toshiba_acpi_i8042_filter); err_free_keymap: sparse_keymap_free(dev->hotkey_dev); err_free_dev: @@ -895,6 +1026,11 @@ static int toshiba_acpi_remove(struct acpi_device *acpi_dev, int type) remove_toshiba_proc_entries(dev); + if (dev->ntfy_supported) { + i8042_remove_filter(toshiba_acpi_i8042_filter); + cancel_work_sync(&dev->hotkey_work); + } + if (dev->hotkey_dev) { input_unregister_device(dev->hotkey_dev); sparse_keymap_free(dev->hotkey_dev); @@ -911,6 +1047,9 @@ static int toshiba_acpi_remove(struct acpi_device *acpi_dev, int type) if (dev->illumination_supported) led_classdev_unregister(&dev->led_dev); + if (toshiba_acpi) + toshiba_acpi = NULL; + kfree(dev); return 0; @@ -936,12 +1075,14 @@ static int __devinit toshiba_acpi_add(struct acpi_device *acpi_dev) { struct toshiba_acpi_dev *dev; const char *hci_method; - u32 hci_result; u32 dummy; bool bt_present; int ret = 0; struct backlight_properties props; + if (toshiba_acpi) + return -EBUSY; + pr_info("Toshiba Laptop ACPI Extras version %s\n", TOSHIBA_ACPI_VERSION); @@ -963,11 +1104,6 @@ static int __devinit toshiba_acpi_add(struct acpi_device *acpi_dev) mutex_init(&dev->mutex); - /* enable event fifo */ - hci_write1(dev, HCI_SYSTEM_EVENT, 1, &hci_result); - if (hci_result == HCI_SUCCESS) - dev->system_event_supported = 1; - props.type = BACKLIGHT_PLATFORM; props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1; dev->backlight_dev = backlight_device_register("toshiba", @@ -1024,6 +1160,8 @@ static int __devinit toshiba_acpi_add(struct acpi_device *acpi_dev) create_toshiba_proc_entries(dev); + toshiba_acpi = dev; + return 0; error: @@ -1036,40 +1174,64 @@ static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event) struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev); u32 hci_result, value; int retries = 3; + int scancode; - if (!dev->system_event_supported || event != 0x80) + if (event != 0x80) return; - do { - hci_read1(dev, HCI_SYSTEM_EVENT, &value, &hci_result); - switch (hci_result) { - case HCI_SUCCESS: - if (value == 0x100) - continue; - /* act on key press; ignore key release */ - if (value & 0x80) - continue; - - if (!sparse_keymap_report_event(dev->hotkey_dev, - value, 1, true)) { - pr_info("Unknown key %x\n", - value); + if (dev->info_supported) { + scancode = toshiba_acpi_query_hotkey(dev); + if (scancode < 0) + pr_err("Failed to query hotkey event\n"); + else if (scancode != 0) + toshiba_acpi_report_hotkey(dev, scancode); + } else if (dev->system_event_supported) { + do { + hci_read1(dev, HCI_SYSTEM_EVENT, &value, &hci_result); + switch (hci_result) { + case HCI_SUCCESS: + toshiba_acpi_report_hotkey(dev, (int)value); + break; + case HCI_NOT_SUPPORTED: + /* + * This is a workaround for an unresolved + * issue on some machines where system events + * sporadically become disabled. + */ + hci_write1(dev, HCI_SYSTEM_EVENT, 1, + &hci_result); + pr_notice("Re-enabled hotkeys\n"); + /* fall through */ + default: + retries--; + break; } - break; - case HCI_NOT_SUPPORTED: - /* This is a workaround for an unresolved issue on - * some machines where system events sporadically - * become disabled. */ - hci_write1(dev, HCI_SYSTEM_EVENT, 1, &hci_result); - pr_notice("Re-enabled hotkeys\n"); - /* fall through */ - default: - retries--; - break; - } - } while (retries && hci_result != HCI_EMPTY); + } while (retries && hci_result != HCI_EMPTY); + } +} + +static int toshiba_acpi_suspend(struct acpi_device *acpi_dev, + pm_message_t state) +{ + struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev); + u32 result; + + if (dev->hotkey_dev) + hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_DISABLE, &result); + + return 0; } +static int toshiba_acpi_resume(struct acpi_device *acpi_dev) +{ + struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev); + u32 result; + + if (dev->hotkey_dev) + hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE, &result); + + return 0; +} static struct acpi_driver toshiba_acpi_driver = { .name = "Toshiba ACPI driver", @@ -1080,6 +1242,8 @@ static struct acpi_driver toshiba_acpi_driver = { .add = toshiba_acpi_add, .remove = toshiba_acpi_remove, .notify = toshiba_acpi_notify, + .suspend = toshiba_acpi_suspend, + .resume = toshiba_acpi_resume, }, }; @@ -1087,6 +1251,14 @@ static int __init toshiba_acpi_init(void) { int ret; + /* + * Machines with this WMI guid aren't supported due to bugs in + * their AML. This check relies on wmi initializing before + * toshiba_acpi to guarantee guids have been identified. + */ + if (wmi_has_guid(TOSHIBA_WMI_EVENT_GUID)) + return -ENODEV; + toshiba_proc_dir = proc_mkdir(PROC_TOSHIBA, acpi_root_dir); if (!toshiba_proc_dir) { pr_err("Unable to create proc dir " PROC_TOSHIBA "\n"); diff --git a/drivers/platform/x86/xo1-rfkill.c b/drivers/platform/x86/xo1-rfkill.c index e549eeeda121..41781ed8301c 100644 --- a/drivers/platform/x86/xo1-rfkill.c +++ b/drivers/platform/x86/xo1-rfkill.c @@ -67,19 +67,8 @@ static struct platform_driver xo1_rfkill_driver = { .remove = __devexit_p(xo1_rfkill_remove), }; -static int __init xo1_rfkill_init(void) -{ - return platform_driver_register(&xo1_rfkill_driver); -} - -static void __exit xo1_rfkill_exit(void) -{ - platform_driver_unregister(&xo1_rfkill_driver); -} +module_platform_driver(xo1_rfkill_driver); MODULE_AUTHOR("Daniel Drake <dsd@laptop.org>"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:xo1-rfkill"); - -module_init(xo1_rfkill_init); -module_exit(xo1_rfkill_exit); |