diff options
-rw-r--r-- | drivers/char/Kconfig | 2 | ||||
-rw-r--r-- | drivers/platform/x86/Kconfig | 22 | ||||
-rw-r--r-- | drivers/platform/x86/Makefile | 2 | ||||
-rw-r--r-- | drivers/platform/x86/alienware-wmi.c | 565 | ||||
-rw-r--r-- | drivers/platform/x86/fujitsu-tablet.c | 65 | ||||
-rw-r--r-- | drivers/platform/x86/intel_baytrail.c | 224 | ||||
-rw-r--r-- | drivers/platform/x86/intel_baytrail.h | 90 | ||||
-rw-r--r-- | drivers/platform/x86/panasonic-laptop.c | 11 | ||||
-rw-r--r-- | drivers/platform/x86/sony-laptop.c | 539 | ||||
-rw-r--r-- | drivers/platform/x86/thinkpad_acpi.c | 227 | ||||
-rw-r--r-- | drivers/platform/x86/toshiba_acpi.c | 635 |
11 files changed, 1901 insertions, 481 deletions
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 1386749b48ff..fbae63e3d304 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -408,7 +408,7 @@ config APPLICOM config SONYPI tristate "Sony Vaio Programmable I/O Control Device support" - depends on X86 && PCI && INPUT && !64BIT + depends on X86_32 && PCI && INPUT ---help--- This driver enables access to the Sony Programmable I/O Control Device which can be found in many (all ?) Sony Vaio laptops. diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 5f67843c7fb7..27df2c533b09 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -53,6 +53,18 @@ config ACERHDF If you have an Acer Aspire One netbook, say Y or M here. +config ALIENWARE_WMI + tristate "Alienware Special feature control" + depends on ACPI + depends on LEDS_CLASS + depends on NEW_LEDS + depends on ACPI_WMI + ---help--- + This is a driver for controlling Alienware BIOS driven + features. It exposes an interface for controlling the AlienFX + zones on Alienware machines that don't contain a dedicated AlienFX + USB MCU such as the X51 and X51-R2. + config ASUS_LAPTOP tristate "Asus Laptop Extras" depends on ACPI @@ -196,7 +208,7 @@ config HP_ACCEL be called hp_accel. config HP_WIRELESS - tristate "HP WIRELESS" + tristate "HP wireless button" depends on ACPI depends on INPUT help @@ -817,12 +829,4 @@ config PVPANIC a paravirtualized device provided by QEMU; it lets a virtual machine (guest) communicate panic events to the host. -config INTEL_BAYTRAIL_MBI - tristate - depends on PCI - ---help--- - Needed on Baytrail platforms for access to the IOSF Sideband Mailbox - Interface. This is a requirement for systems that need to configure - the PUNIT for power management features such as RAPL. - endif # X86_PLATFORM_DEVICES diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 9b87cfc42b84..1a2eafc9d48e 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -55,4 +55,4 @@ obj-$(CONFIG_INTEL_RST) += intel-rst.o obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o obj-$(CONFIG_PVPANIC) += pvpanic.o -obj-$(CONFIG_INTEL_BAYTRAIL_MBI) += intel_baytrail.o +obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o diff --git a/drivers/platform/x86/alienware-wmi.c b/drivers/platform/x86/alienware-wmi.c new file mode 100644 index 000000000000..541f9514f76f --- /dev/null +++ b/drivers/platform/x86/alienware-wmi.c @@ -0,0 +1,565 @@ +/* + * Alienware AlienFX control + * + * Copyright (C) 2014 Dell Inc <mario_limonciello@dell.com> + * + * 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. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/acpi.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/dmi.h> +#include <linux/acpi.h> +#include <linux/leds.h> + +#define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492" +#define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" +#define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492" + +#define WMAX_METHOD_HDMI_SOURCE 0x1 +#define WMAX_METHOD_HDMI_STATUS 0x2 +#define WMAX_METHOD_BRIGHTNESS 0x3 +#define WMAX_METHOD_ZONE_CONTROL 0x4 + +MODULE_AUTHOR("Mario Limonciello <mario_limonciello@dell.com>"); +MODULE_DESCRIPTION("Alienware special feature control"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID); +MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID); + +enum INTERFACE_FLAGS { + LEGACY, + WMAX, +}; + +enum LEGACY_CONTROL_STATES { + LEGACY_RUNNING = 1, + LEGACY_BOOTING = 0, + LEGACY_SUSPEND = 3, +}; + +enum WMAX_CONTROL_STATES { + WMAX_RUNNING = 0xFF, + WMAX_BOOTING = 0, + WMAX_SUSPEND = 3, +}; + +struct quirk_entry { + u8 num_zones; +}; + +static struct quirk_entry *quirks; + +static struct quirk_entry quirk_unknown = { + .num_zones = 2, +}; + +static struct quirk_entry quirk_x51_family = { + .num_zones = 3, +}; + +static int dmi_matched(const struct dmi_system_id *dmi) +{ + quirks = dmi->driver_data; + return 1; +} + +static struct dmi_system_id alienware_quirks[] = { + { + .callback = dmi_matched, + .ident = "Alienware X51 R1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"), + }, + .driver_data = &quirk_x51_family, + }, + { + .callback = dmi_matched, + .ident = "Alienware X51 R2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"), + }, + .driver_data = &quirk_x51_family, + }, + {} +}; + +struct color_platform { + u8 blue; + u8 green; + u8 red; +} __packed; + +struct platform_zone { + u8 location; + struct device_attribute *attr; + struct color_platform colors; +}; + +struct wmax_brightness_args { + u32 led_mask; + u32 percentage; +}; + +struct hdmi_args { + u8 arg; +}; + +struct legacy_led_args { + struct color_platform colors; + u8 brightness; + u8 state; +} __packed; + +struct wmax_led_args { + u32 led_mask; + struct color_platform colors; + u8 state; +} __packed; + +static struct platform_device *platform_device; +static struct device_attribute *zone_dev_attrs; +static struct attribute **zone_attrs; +static struct platform_zone *zone_data; + +static struct platform_driver platform_driver = { + .driver = { + .name = "alienware-wmi", + .owner = THIS_MODULE, + } +}; + +static struct attribute_group zone_attribute_group = { + .name = "rgb_zones", +}; + +static u8 interface; +static u8 lighting_control_state; +static u8 global_brightness; + +/* + * Helpers used for zone control +*/ +static int parse_rgb(const char *buf, struct platform_zone *zone) +{ + long unsigned int rgb; + int ret; + union color_union { + struct color_platform cp; + int package; + } repackager; + + ret = kstrtoul(buf, 16, &rgb); + if (ret) + return ret; + + /* RGB triplet notation is 24-bit hexadecimal */ + if (rgb > 0xFFFFFF) + return -EINVAL; + + repackager.package = rgb & 0x0f0f0f0f; + pr_debug("alienware-wmi: r: %d g:%d b: %d\n", + repackager.cp.red, repackager.cp.green, repackager.cp.blue); + zone->colors = repackager.cp; + return 0; +} + +static struct platform_zone *match_zone(struct device_attribute *attr) +{ + int i; + for (i = 0; i < quirks->num_zones; i++) { + if ((struct device_attribute *)zone_data[i].attr == attr) { + pr_debug("alienware-wmi: matched zone location: %d\n", + zone_data[i].location); + return &zone_data[i]; + } + } + return NULL; +} + +/* + * Individual RGB zone control +*/ +static int alienware_update_led(struct platform_zone *zone) +{ + int method_id; + acpi_status status; + char *guid; + struct acpi_buffer input; + struct legacy_led_args legacy_args; + struct wmax_led_args wmax_args; + if (interface == WMAX) { + wmax_args.led_mask = 1 << zone->location; + wmax_args.colors = zone->colors; + wmax_args.state = lighting_control_state; + guid = WMAX_CONTROL_GUID; + method_id = WMAX_METHOD_ZONE_CONTROL; + + input.length = (acpi_size) sizeof(wmax_args); + input.pointer = &wmax_args; + } else { + legacy_args.colors = zone->colors; + legacy_args.brightness = global_brightness; + legacy_args.state = 0; + if (lighting_control_state == LEGACY_BOOTING || + lighting_control_state == LEGACY_SUSPEND) { + guid = LEGACY_POWER_CONTROL_GUID; + legacy_args.state = lighting_control_state; + } else + guid = LEGACY_CONTROL_GUID; + method_id = zone->location + 1; + + input.length = (acpi_size) sizeof(legacy_args); + input.pointer = &legacy_args; + } + pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id); + + status = wmi_evaluate_method(guid, 1, method_id, &input, NULL); + if (ACPI_FAILURE(status)) + pr_err("alienware-wmi: zone set failure: %u\n", status); + return ACPI_FAILURE(status); +} + +static ssize_t zone_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct platform_zone *target_zone; + target_zone = match_zone(attr); + if (target_zone == NULL) + return sprintf(buf, "red: -1, green: -1, blue: -1\n"); + return sprintf(buf, "red: %d, green: %d, blue: %d\n", + target_zone->colors.red, + target_zone->colors.green, target_zone->colors.blue); + +} + +static ssize_t zone_set(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_zone *target_zone; + int ret; + target_zone = match_zone(attr); + if (target_zone == NULL) { + pr_err("alienware-wmi: invalid target zone\n"); + return 1; + } + ret = parse_rgb(buf, target_zone); + if (ret) + return ret; + ret = alienware_update_led(target_zone); + return ret ? ret : count; +} + +/* + * LED Brightness (Global) +*/ +static int wmax_brightness(int brightness) +{ + acpi_status status; + struct acpi_buffer input; + struct wmax_brightness_args args = { + .led_mask = 0xFF, + .percentage = brightness, + }; + input.length = (acpi_size) sizeof(args); + input.pointer = &args; + status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1, + WMAX_METHOD_BRIGHTNESS, &input, NULL); + if (ACPI_FAILURE(status)) + pr_err("alienware-wmi: brightness set failure: %u\n", status); + return ACPI_FAILURE(status); +} + +static void global_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + int ret; + global_brightness = brightness; + if (interface == WMAX) + ret = wmax_brightness(brightness); + else + ret = alienware_update_led(&zone_data[0]); + if (ret) + pr_err("LED brightness update failed\n"); +} + +static enum led_brightness global_led_get(struct led_classdev *led_cdev) +{ + return global_brightness; +} + +static struct led_classdev global_led = { + .brightness_set = global_led_set, + .brightness_get = global_led_get, + .name = "alienware::global_brightness", +}; + +/* + * Lighting control state device attribute (Global) +*/ +static ssize_t show_control_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (lighting_control_state == LEGACY_BOOTING) + return scnprintf(buf, PAGE_SIZE, "[booting] running suspend\n"); + else if (lighting_control_state == LEGACY_SUSPEND) + return scnprintf(buf, PAGE_SIZE, "booting running [suspend]\n"); + return scnprintf(buf, PAGE_SIZE, "booting [running] suspend\n"); +} + +static ssize_t store_control_state(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + long unsigned int val; + if (strcmp(buf, "booting\n") == 0) + val = LEGACY_BOOTING; + else if (strcmp(buf, "suspend\n") == 0) + val = LEGACY_SUSPEND; + else if (interface == LEGACY) + val = LEGACY_RUNNING; + else + val = WMAX_RUNNING; + lighting_control_state = val; + pr_debug("alienware-wmi: updated control state to %d\n", + lighting_control_state); + return count; +} + +static DEVICE_ATTR(lighting_control_state, 0644, show_control_state, + store_control_state); + +static int alienware_zone_init(struct platform_device *dev) +{ + int i; + char buffer[10]; + char *name; + + if (interface == WMAX) { + global_led.max_brightness = 100; + lighting_control_state = WMAX_RUNNING; + } else if (interface == LEGACY) { + global_led.max_brightness = 0x0F; + lighting_control_state = LEGACY_RUNNING; + } + global_brightness = global_led.max_brightness; + + /* + * - zone_dev_attrs num_zones + 1 is for individual zones and then + * null terminated + * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs + + * the lighting control + null terminated + * - zone_data num_zones is for the distinct zones + */ + zone_dev_attrs = + kzalloc(sizeof(struct device_attribute) * (quirks->num_zones + 1), + GFP_KERNEL); + if (!zone_dev_attrs) + return -ENOMEM; + + zone_attrs = + kzalloc(sizeof(struct attribute *) * (quirks->num_zones + 2), + GFP_KERNEL); + if (!zone_attrs) + return -ENOMEM; + + zone_data = + kzalloc(sizeof(struct platform_zone) * (quirks->num_zones), + GFP_KERNEL); + if (!zone_data) + return -ENOMEM; + + for (i = 0; i < quirks->num_zones; i++) { + sprintf(buffer, "zone%02X", i); + name = kstrdup(buffer, GFP_KERNEL); + if (name == NULL) + return 1; + sysfs_attr_init(&zone_dev_attrs[i].attr); + zone_dev_attrs[i].attr.name = name; + zone_dev_attrs[i].attr.mode = 0644; + zone_dev_attrs[i].show = zone_show; + zone_dev_attrs[i].store = zone_set; + zone_data[i].location = i; + zone_attrs[i] = &zone_dev_attrs[i].attr; + zone_data[i].attr = &zone_dev_attrs[i]; + } + zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr; + zone_attribute_group.attrs = zone_attrs; + + led_classdev_register(&dev->dev, &global_led); + + return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group); +} + +static void alienware_zone_exit(struct platform_device *dev) +{ + sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group); + led_classdev_unregister(&global_led); + if (zone_dev_attrs) { + int i; + for (i = 0; i < quirks->num_zones; i++) + kfree(zone_dev_attrs[i].attr.name); + } + kfree(zone_dev_attrs); + kfree(zone_data); + kfree(zone_attrs); +} + +/* + The HDMI mux sysfs node indicates the status of the HDMI input mux. + It can toggle between standard system GPU output and HDMI input. +*/ +static ssize_t show_hdmi(struct device *dev, struct device_attribute *attr, + char *buf) +{ + acpi_status status; + struct acpi_buffer input; + union acpi_object *obj; + u32 tmp = 0; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + struct hdmi_args in_args = { + .arg = 0, + }; + input.length = (acpi_size) sizeof(in_args); + input.pointer = &in_args; + status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1, + WMAX_METHOD_HDMI_STATUS, &input, &output); + + if (ACPI_SUCCESS(status)) { + obj = (union acpi_object *)output.pointer; + if (obj && obj->type == ACPI_TYPE_INTEGER) + tmp = (u32) obj->integer.value; + if (tmp == 1) + return scnprintf(buf, PAGE_SIZE, + "[input] gpu unknown\n"); + else if (tmp == 2) + return scnprintf(buf, PAGE_SIZE, + "input [gpu] unknown\n"); + } + pr_err("alienware-wmi: unknown HDMI status: %d\n", status); + return scnprintf(buf, PAGE_SIZE, "input gpu [unknown]\n"); +} + +static ssize_t toggle_hdmi(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_buffer input; + acpi_status status; + struct hdmi_args args; + if (strcmp(buf, "gpu\n") == 0) + args.arg = 1; + else if (strcmp(buf, "input\n") == 0) + args.arg = 2; + else + args.arg = 3; + pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf); + input.length = (acpi_size) sizeof(args); + input.pointer = &args; + status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1, + WMAX_METHOD_HDMI_SOURCE, &input, NULL); + if (ACPI_FAILURE(status)) + pr_err("alienware-wmi: HDMI toggle failed: results: %u\n", + status); + return count; +} + +static DEVICE_ATTR(hdmi, S_IRUGO | S_IWUSR, show_hdmi, toggle_hdmi); + +static void remove_hdmi(struct platform_device *device) +{ + device_remove_file(&device->dev, &dev_attr_hdmi); +} + +static int create_hdmi(void) +{ + int ret = -ENOMEM; + ret = device_create_file(&platform_device->dev, &dev_attr_hdmi); + if (ret) + goto error_create_hdmi; + return 0; + +error_create_hdmi: + remove_hdmi(platform_device); + return ret; +} + +static int __init alienware_wmi_init(void) +{ + int ret; + + if (wmi_has_guid(LEGACY_CONTROL_GUID)) + interface = LEGACY; + else if (wmi_has_guid(WMAX_CONTROL_GUID)) + interface = WMAX; + else { + pr_warn("alienware-wmi: No known WMI GUID found\n"); + return -ENODEV; + } + + dmi_check_system(alienware_quirks); + if (quirks == NULL) + quirks = &quirk_unknown; + + ret = platform_driver_register(&platform_driver); + if (ret) + goto fail_platform_driver; + platform_device = platform_device_alloc("alienware-wmi", -1); + if (!platform_device) { + ret = -ENOMEM; + goto fail_platform_device1; + } + ret = platform_device_add(platform_device); + if (ret) + goto fail_platform_device2; + + if (interface == WMAX) { + ret = create_hdmi(); + if (ret) + goto fail_prep_hdmi; + } + + ret = alienware_zone_init(platform_device); + if (ret) + goto fail_prep_zones; + + return 0; + +fail_prep_zones: + alienware_zone_exit(platform_device); +fail_prep_hdmi: + platform_device_del(platform_device); +fail_platform_device2: + platform_device_put(platform_device); +fail_platform_device1: + platform_driver_unregister(&platform_driver); +fail_platform_driver: + return ret; +} + +module_init(alienware_wmi_init); + +static void __exit alienware_wmi_exit(void) +{ + if (platform_device) { + alienware_zone_exit(platform_device); + remove_hdmi(platform_device); + platform_device_unregister(platform_device); + platform_driver_unregister(&platform_driver); + } +} + +module_exit(alienware_wmi_exit); diff --git a/drivers/platform/x86/fujitsu-tablet.c b/drivers/platform/x86/fujitsu-tablet.c index 570926c10014..c3784baceae3 100644 --- a/drivers/platform/x86/fujitsu-tablet.c +++ b/drivers/platform/x86/fujitsu-tablet.c @@ -71,6 +71,44 @@ static unsigned short keymap_Lifebook_Tseries[KEYMAP_LEN] __initdata = { KEY_LEFTALT }; +static unsigned short keymap_Lifebook_T901[KEYMAP_LEN] __initdata = { + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_SCROLLDOWN, + KEY_SCROLLUP, + KEY_CYCLEWINDOWS, + KEY_LEFTCTRL, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_LEFTMETA +}; + +static unsigned short keymap_Lifebook_T902[KEYMAP_LEN] __initdata = { + KEY_RESERVED, + KEY_VOLUMEDOWN, + KEY_VOLUMEUP, + KEY_CYCLEWINDOWS, + KEY_PROG1, + KEY_PROG2, + KEY_LEFTMETA, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, +}; + static unsigned short keymap_Lifebook_U810[KEYMAP_LEN] __initdata = { KEY_RESERVED, KEY_RESERVED, @@ -302,6 +340,33 @@ static int fujitsu_dmi_stylistic(const struct dmi_system_id *dmi) static const struct dmi_system_id dmi_ids[] __initconst = { { .callback = fujitsu_dmi_lifebook, + .ident = "Fujitsu Lifebook T901", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook T901") + }, + .driver_data = keymap_Lifebook_T901 + }, + { + .callback = fujitsu_dmi_lifebook, + .ident = "Fujitsu Lifebook T901", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK T901") + }, + .driver_data = keymap_Lifebook_T901 + }, + { + .callback = fujitsu_dmi_lifebook, + .ident = "Fujitsu Lifebook T902", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK T902") + }, + .driver_data = keymap_Lifebook_T902 + }, + { + .callback = fujitsu_dmi_lifebook, .ident = "Fujitsu Siemens P/T Series", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), diff --git a/drivers/platform/x86/intel_baytrail.c b/drivers/platform/x86/intel_baytrail.c deleted file mode 100644 index f96626b17260..000000000000 --- a/drivers/platform/x86/intel_baytrail.c +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Baytrail IOSF-SB MailBox Interface Driver - * Copyright (c) 2013, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope 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. - * - * - * The IOSF-SB is a fabric bus available on Atom based SOC's that uses a - * mailbox interface (MBI) to communicate with mutiple devices. This - * driver implements BayTrail-specific access to this interface. - */ - -#include <linux/module.h> -#include <linux/init.h> -#include <linux/spinlock.h> -#include <linux/pci.h> - -#include "intel_baytrail.h" - -static DEFINE_SPINLOCK(iosf_mbi_lock); - -static inline u32 iosf_mbi_form_mcr(u8 op, u8 port, u8 offset) -{ - return (op << 24) | (port << 16) | (offset << 8) | BT_MBI_ENABLE; -} - -static struct pci_dev *mbi_pdev; /* one mbi device */ - -/* Hold lock before calling */ -static int iosf_mbi_pci_read_mdr(u32 mcrx, u32 mcr, u32 *mdr) -{ - int result; - - if (!mbi_pdev) - return -ENODEV; - - if (mcrx) { - result = pci_write_config_dword(mbi_pdev, - BT_MBI_MCRX_OFFSET, mcrx); - if (result < 0) - goto iosf_mbi_read_err; - } - - result = pci_write_config_dword(mbi_pdev, - BT_MBI_MCR_OFFSET, mcr); - if (result < 0) - goto iosf_mbi_read_err; - - result = pci_read_config_dword(mbi_pdev, - BT_MBI_MDR_OFFSET, mdr); - if (result < 0) - goto iosf_mbi_read_err; - - return 0; - -iosf_mbi_read_err: - dev_err(&mbi_pdev->dev, "error: PCI config operation returned %d\n", - result); - return result; -} - -/* Hold lock before calling */ -static int iosf_mbi_pci_write_mdr(u32 mcrx, u32 mcr, u32 mdr) -{ - int result; - - if (!mbi_pdev) - return -ENODEV; - - result = pci_write_config_dword(mbi_pdev, - BT_MBI_MDR_OFFSET, mdr); - if (result < 0) - goto iosf_mbi_write_err; - - if (mcrx) { - result = pci_write_config_dword(mbi_pdev, - BT_MBI_MCRX_OFFSET, mcrx); - if (result < 0) - goto iosf_mbi_write_err; - } - - result = pci_write_config_dword(mbi_pdev, - BT_MBI_MCR_OFFSET, mcr); - if (result < 0) - goto iosf_mbi_write_err; - - return 0; - -iosf_mbi_write_err: - dev_err(&mbi_pdev->dev, "error: PCI config operation returned %d\n", - result); - return result; -} - -int bt_mbi_read(u8 port, u8 opcode, u32 offset, u32 *mdr) -{ - u32 mcr, mcrx; - unsigned long flags; - int ret; - - /*Access to the GFX unit is handled by GPU code */ - BUG_ON(port == BT_MBI_UNIT_GFX); - - mcr = iosf_mbi_form_mcr(opcode, port, offset & BT_MBI_MASK_LO); - mcrx = offset & BT_MBI_MASK_HI; - - spin_lock_irqsave(&iosf_mbi_lock, flags); - ret = iosf_mbi_pci_read_mdr(mcrx, mcr, mdr); - spin_unlock_irqrestore(&iosf_mbi_lock, flags); - - return ret; -} -EXPORT_SYMBOL(bt_mbi_read); - -int bt_mbi_write(u8 port, u8 opcode, u32 offset, u32 mdr) -{ - u32 mcr, mcrx; - unsigned long flags; - int ret; - - /*Access to the GFX unit is handled by GPU code */ - BUG_ON(port == BT_MBI_UNIT_GFX); - - mcr = iosf_mbi_form_mcr(opcode, port, offset & BT_MBI_MASK_LO); - mcrx = offset & BT_MBI_MASK_HI; - - spin_lock_irqsave(&iosf_mbi_lock, flags); - ret = iosf_mbi_pci_write_mdr(mcrx, mcr, mdr); - spin_unlock_irqrestore(&iosf_mbi_lock, flags); - - return ret; -} -EXPORT_SYMBOL(bt_mbi_write); - -int bt_mbi_modify(u8 port, u8 opcode, u32 offset, u32 mdr, u32 mask) -{ - u32 mcr, mcrx; - u32 value; - unsigned long flags; - int ret; - - /*Access to the GFX unit is handled by GPU code */ - BUG_ON(port == BT_MBI_UNIT_GFX); - - mcr = iosf_mbi_form_mcr(opcode, port, offset & BT_MBI_MASK_LO); - mcrx = offset & BT_MBI_MASK_HI; - - spin_lock_irqsave(&iosf_mbi_lock, flags); - - /* Read current mdr value */ - ret = iosf_mbi_pci_read_mdr(mcrx, mcr & BT_MBI_RD_MASK, &value); - if (ret < 0) { - spin_unlock_irqrestore(&iosf_mbi_lock, flags); - return ret; - } - - /* Apply mask */ - value &= ~mask; - mdr &= mask; - value |= mdr; - - /* Write back */ - ret = iosf_mbi_pci_write_mdr(mcrx, mcr | BT_MBI_WR_MASK, value); - - spin_unlock_irqrestore(&iosf_mbi_lock, flags); - - return ret; -} -EXPORT_SYMBOL(bt_mbi_modify); - -static int iosf_mbi_probe(struct pci_dev *pdev, - const struct pci_device_id *unused) -{ - int ret; - - ret = pci_enable_device(pdev); - if (ret < 0) { - dev_err(&pdev->dev, "error: could not enable device\n"); - return ret; - } - - mbi_pdev = pci_dev_get(pdev); - return 0; -} - -static DEFINE_PCI_DEVICE_TABLE(iosf_mbi_pci_ids) = { - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0F00) }, - { 0, }, -}; -MODULE_DEVICE_TABLE(pci, iosf_mbi_pci_ids); - -static struct pci_driver iosf_mbi_pci_driver = { - .name = "iosf_mbi_pci", - .probe = iosf_mbi_probe, - .id_table = iosf_mbi_pci_ids, -}; - -static int __init bt_mbi_init(void) -{ - return pci_register_driver(&iosf_mbi_pci_driver); -} - -static void __exit bt_mbi_exit(void) -{ - pci_unregister_driver(&iosf_mbi_pci_driver); - if (mbi_pdev) { - pci_dev_put(mbi_pdev); - mbi_pdev = NULL; - } -} - -module_init(bt_mbi_init); -module_exit(bt_mbi_exit); - -MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>"); -MODULE_DESCRIPTION("BayTrail Mailbox Interface accessor"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_baytrail.h b/drivers/platform/x86/intel_baytrail.h deleted file mode 100644 index 8bcc311262e9..000000000000 --- a/drivers/platform/x86/intel_baytrail.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * intel_baytrail.h: MailBox access support for Intel BayTrail platforms - */ - -#ifndef INTEL_BAYTRAIL_MBI_SYMS_H -#define INTEL_BAYTRAIL_MBI_SYMS_H - -#define BT_MBI_MCR_OFFSET 0xD0 -#define BT_MBI_MDR_OFFSET 0xD4 -#define BT_MBI_MCRX_OFFSET 0xD8 - -#define BT_MBI_RD_MASK 0xFEFFFFFF -#define BT_MBI_WR_MASK 0X01000000 - -#define BT_MBI_MASK_HI 0xFFFFFF00 -#define BT_MBI_MASK_LO 0x000000FF -#define BT_MBI_ENABLE 0xF0 - -/* BT-SB unit access methods */ -#define BT_MBI_UNIT_AUNIT 0x00 -#define BT_MBI_UNIT_SMC 0x01 -#define BT_MBI_UNIT_CPU 0x02 -#define BT_MBI_UNIT_BUNIT 0x03 -#define BT_MBI_UNIT_PMC 0x04 -#define BT_MBI_UNIT_GFX 0x06 -#define BT_MBI_UNIT_SMI 0x0C -#define BT_MBI_UNIT_USB 0x43 -#define BT_MBI_UNIT_SATA 0xA3 -#define BT_MBI_UNIT_PCIE 0xA6 - -/* Read/write opcodes */ -#define BT_MBI_AUNIT_READ 0x10 -#define BT_MBI_AUNIT_WRITE 0x11 -#define BT_MBI_SMC_READ 0x10 -#define BT_MBI_SMC_WRITE 0x11 -#define BT_MBI_CPU_READ 0x10 -#define BT_MBI_CPU_WRITE 0x11 -#define BT_MBI_BUNIT_READ 0x10 -#define BT_MBI_BUNIT_WRITE 0x11 -#define BT_MBI_PMC_READ 0x06 -#define BT_MBI_PMC_WRITE 0x07 -#define BT_MBI_GFX_READ 0x00 -#define BT_MBI_GFX_WRITE 0x01 -#define BT_MBI_SMIO_READ 0x06 -#define BT_MBI_SMIO_WRITE 0x07 -#define BT_MBI_USB_READ 0x06 -#define BT_MBI_USB_WRITE 0x07 -#define BT_MBI_SATA_READ 0x00 -#define BT_MBI_SATA_WRITE 0x01 -#define BT_MBI_PCIE_READ 0x00 -#define BT_MBI_PCIE_WRITE 0x01 - -/** - * bt_mbi_read() - MailBox Interface read command - * @port: port indicating subunit being accessed - * @opcode: port specific read or write opcode - * @offset: register address offset - * @mdr: register data to be read - * - * Locking is handled by spinlock - cannot sleep. - * Return: Nonzero on error - */ -int bt_mbi_read(u8 port, u8 opcode, u32 offset, u32 *mdr); - -/** - * bt_mbi_write() - MailBox unmasked write command - * @port: port indicating subunit being accessed - * @opcode: port specific read or write opcode - * @offset: register address offset - * @mdr: register data to be written - * - * Locking is handled by spinlock - cannot sleep. - * Return: Nonzero on error - */ -int bt_mbi_write(u8 port, u8 opcode, u32 offset, u32 mdr); - -/** - * bt_mbi_modify() - MailBox masked write command - * @port: port indicating subunit being accessed - * @opcode: port specific read or write opcode - * @offset: register address offset - * @mdr: register data being modified - * @mask: mask indicating bits in mdr to be modified - * - * Locking is handled by spinlock - cannot sleep. - * Return: Nonzero on error - */ -int bt_mbi_modify(u8 port, u8 opcode, u32 offset, u32 mdr, u32 mask); - -#endif /* INTEL_BAYTRAIL_MBI_SYMS_H */ diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index 609d38779b26..3f870972247c 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -449,6 +449,7 @@ static struct attribute_group pcc_attr_group = { /* hotkey input device driver */ +static int sleep_keydown_seen; static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc) { struct input_dev *hotk_input_dev = pcc->input_dev; @@ -462,6 +463,16 @@ static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc) "error getting hotkey status\n")); return; } + + /* hack: some firmware sends no key down for sleep / hibernate */ + if ((result & 0xf) == 0x7 || (result & 0xf) == 0xa) { + if (result & 0x80) + sleep_keydown_seen = 1; + if (!sleep_keydown_seen) + sparse_keymap_report_event(hotk_input_dev, + result & 0xf, 0x80, false); + } + if (!sparse_keymap_report_event(hotk_input_dev, result & 0xf, result & 0x80, false)) ACPI_DEBUG_PRINT((ACPI_DB_ERROR, diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index 8f8551a63cc0..9c5a07417b2b 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -76,8 +76,6 @@ do { \ pr_warn(fmt, ##__VA_ARGS__); \ } while (0) -#define SONY_LAPTOP_DRIVER_VERSION "0.6" - #define SONY_NC_CLASS "sony-nc" #define SONY_NC_HID "SNY5001" #define SONY_NC_DRIVER_NAME "Sony Notebook Control Driver" @@ -89,7 +87,6 @@ do { \ MODULE_AUTHOR("Stelian Pop, Mattia Dongili"); MODULE_DESCRIPTION("Sony laptop extras driver (SPIC and SNC ACPI device)"); MODULE_LICENSE("GPL"); -MODULE_VERSION(SONY_LAPTOP_DRIVER_VERSION); static int debug; module_param(debug, int, 0); @@ -129,7 +126,8 @@ static int kbd_backlight = -1; module_param(kbd_backlight, int, 0444); MODULE_PARM_DESC(kbd_backlight, "set this to 0 to disable keyboard backlight, " - "1 to enable it (default: no change from current value)"); + "1 to enable it with automatic control and 2 to have it always " + "on (default: no change from current value)"); static int kbd_backlight_timeout = -1; module_param(kbd_backlight_timeout, int, 0444); @@ -152,7 +150,8 @@ static void sony_nc_battery_care_cleanup(struct platform_device *pd); static int sony_nc_thermal_setup(struct platform_device *pd); static void sony_nc_thermal_cleanup(struct platform_device *pd); -static int sony_nc_lid_resume_setup(struct platform_device *pd); +static int sony_nc_lid_resume_setup(struct platform_device *pd, + unsigned int handle); static void sony_nc_lid_resume_cleanup(struct platform_device *pd); static int sony_nc_gfx_switch_setup(struct platform_device *pd, @@ -163,6 +162,21 @@ static int __sony_nc_gfx_switch_status_get(void); static int sony_nc_highspeed_charging_setup(struct platform_device *pd); static void sony_nc_highspeed_charging_cleanup(struct platform_device *pd); +static int sony_nc_lowbatt_setup(struct platform_device *pd); +static void sony_nc_lowbatt_cleanup(struct platform_device *pd); + +static int sony_nc_fanspeed_setup(struct platform_device *pd); +static void sony_nc_fanspeed_cleanup(struct platform_device *pd); + +static int sony_nc_usb_charge_setup(struct platform_device *pd); +static void sony_nc_usb_charge_cleanup(struct platform_device *pd); + +static int sony_nc_panelid_setup(struct platform_device *pd); +static void sony_nc_panelid_cleanup(struct platform_device *pd); + +static int sony_nc_smart_conn_setup(struct platform_device *pd); +static void sony_nc_smart_conn_cleanup(struct platform_device *pd); + static int sony_nc_touchpad_setup(struct platform_device *pd, unsigned int handle); static void sony_nc_touchpad_cleanup(struct platform_device *pd); @@ -1122,6 +1136,8 @@ static struct sony_nc_event sony_100_events[] = { { 0x25, SONYPI_EVENT_ANYBUTTON_RELEASED }, { 0xa6, SONYPI_EVENT_HELP_PRESSED }, { 0x26, SONYPI_EVENT_ANYBUTTON_RELEASED }, + { 0xa8, SONYPI_EVENT_FNKEY_1 }, + { 0x28, SONYPI_EVENT_ANYBUTTON_RELEASED }, { 0, 0 }, }; @@ -1339,7 +1355,8 @@ static void sony_nc_function_setup(struct acpi_device *device, result); break; case 0x0119: - result = sony_nc_lid_resume_setup(pf_device); + case 0x015D: + result = sony_nc_lid_resume_setup(pf_device, handle); if (result) pr_err("couldn't set up lid resume function (%d)\n", result); @@ -1381,6 +1398,36 @@ static void sony_nc_function_setup(struct acpi_device *device, pr_err("couldn't set up keyboard backlight function (%d)\n", result); break; + case 0x0121: + result = sony_nc_lowbatt_setup(pf_device); + if (result) + pr_err("couldn't set up low battery function (%d)\n", + result); + break; + case 0x0149: + result = sony_nc_fanspeed_setup(pf_device); + if (result) + pr_err("couldn't set up fan speed function (%d)\n", + result); + break; + case 0x0155: + result = sony_nc_usb_charge_setup(pf_device); + if (result) + pr_err("couldn't set up USB charge support (%d)\n", + result); + break; + case 0x011D: + result = sony_nc_panelid_setup(pf_device); + if (result) + pr_err("couldn't set up panel ID function (%d)\n", + result); + break; + case 0x0168: + result = sony_nc_smart_conn_setup(pf_device); + if (result) + pr_err("couldn't set up smart connect support (%d)\n", + result); + break; default: continue; } @@ -1420,6 +1467,7 @@ static void sony_nc_function_cleanup(struct platform_device *pd) sony_nc_battery_care_cleanup(pd); break; case 0x0119: + case 0x015D: sony_nc_lid_resume_cleanup(pd); break; case 0x0122: @@ -1444,6 +1492,21 @@ static void sony_nc_function_cleanup(struct platform_device *pd) case 0x0163: sony_nc_kbd_backlight_cleanup(pd, handle); break; + case 0x0121: + sony_nc_lowbatt_cleanup(pd); + break; + case 0x0149: + sony_nc_fanspeed_cleanup(pd); + break; + case 0x0155: + sony_nc_usb_charge_cleanup(pd); + break; + case 0x011D: + sony_nc_panelid_cleanup(pd); + break; + case 0x0168: + sony_nc_smart_conn_cleanup(pd); + break; default: continue; } @@ -1719,7 +1782,7 @@ static ssize_t __sony_nc_kbd_backlight_mode_set(u8 value) { int result; - if (value > 1) + if (value > 2) return -EINVAL; if (sony_call_snc_handle(kbdbl_ctl->handle, @@ -1727,8 +1790,10 @@ static ssize_t __sony_nc_kbd_backlight_mode_set(u8 value) return -EIO; /* Try to turn the light on/off immediately */ - sony_call_snc_handle(kbdbl_ctl->handle, - (value << 0x10) | (kbdbl_ctl->base + 0x100), &result); + if (value != 1) + sony_call_snc_handle(kbdbl_ctl->handle, + (value << 0x0f) | (kbdbl_ctl->base + 0x100), + &result); kbdbl_ctl->mode = value; @@ -2221,9 +2286,14 @@ static void sony_nc_thermal_resume(void) #endif /* resume on LID open */ +#define LID_RESUME_S5 0 +#define LID_RESUME_S4 1 +#define LID_RESUME_S3 2 +#define LID_RESUME_MAX 3 struct snc_lid_resume_control { - struct device_attribute attrs[3]; + struct device_attribute attrs[LID_RESUME_MAX]; unsigned int status; + int handle; }; static struct snc_lid_resume_control *lid_ctl; @@ -2231,8 +2301,9 @@ static ssize_t sony_nc_lid_resume_store(struct device *dev, struct device_attribute *attr, const char *buffer, size_t count) { - unsigned int result, pos; + unsigned int result; unsigned long value; + unsigned int pos = LID_RESUME_S5; if (count > 31) return -EINVAL; @@ -2245,21 +2316,21 @@ static ssize_t sony_nc_lid_resume_store(struct device *dev, * +--------------+ * 2 1 0 */ - if (strcmp(attr->attr.name, "lid_resume_S3") == 0) - pos = 2; - else if (strcmp(attr->attr.name, "lid_resume_S4") == 0) - pos = 1; - else if (strcmp(attr->attr.name, "lid_resume_S5") == 0) - pos = 0; - else - return -EINVAL; + while (pos < LID_RESUME_MAX) { + if (&lid_ctl->attrs[pos].attr == &attr->attr) + break; + pos++; + } + if (pos == LID_RESUME_MAX) + return -EINVAL; if (value) value = lid_ctl->status | (1 << pos); else value = lid_ctl->status & ~(1 << pos); - if (sony_call_snc_handle(0x0119, value << 0x10 | 0x0100, &result)) + if (sony_call_snc_handle(lid_ctl->handle, value << 0x10 | 0x0100, + &result)) return -EIO; lid_ctl->status = value; @@ -2268,29 +2339,27 @@ static ssize_t sony_nc_lid_resume_store(struct device *dev, } static ssize_t sony_nc_lid_resume_show(struct device *dev, - struct device_attribute *attr, char *buffer) + struct device_attribute *attr, + char *buffer) { - unsigned int pos; + unsigned int pos = LID_RESUME_S5; - if (strcmp(attr->attr.name, "lid_resume_S3") == 0) - pos = 2; - else if (strcmp(attr->attr.name, "lid_resume_S4") == 0) - pos = 1; - else if (strcmp(attr->attr.name, "lid_resume_S5") == 0) - pos = 0; - else - return -EINVAL; - - return snprintf(buffer, PAGE_SIZE, "%d\n", - (lid_ctl->status >> pos) & 0x01); + while (pos < LID_RESUME_MAX) { + if (&lid_ctl->attrs[pos].attr == &attr->attr) + return snprintf(buffer, PAGE_SIZE, "%d\n", + (lid_ctl->status >> pos) & 0x01); + pos++; + } + return -EINVAL; } -static int sony_nc_lid_resume_setup(struct platform_device *pd) +static int sony_nc_lid_resume_setup(struct platform_device *pd, + unsigned int handle) { unsigned int result; int i; - if (sony_call_snc_handle(0x0119, 0x0000, &result)) + if (sony_call_snc_handle(handle, 0x0000, &result)) return -EIO; lid_ctl = kzalloc(sizeof(struct snc_lid_resume_control), GFP_KERNEL); @@ -2298,26 +2367,29 @@ static int sony_nc_lid_resume_setup(struct platform_device *pd) return -ENOMEM; lid_ctl->status = result & 0x7; + lid_ctl->handle = handle; sysfs_attr_init(&lid_ctl->attrs[0].attr); - lid_ctl->attrs[0].attr.name = "lid_resume_S3"; - lid_ctl->attrs[0].attr.mode = S_IRUGO | S_IWUSR; - lid_ctl->attrs[0].show = sony_nc_lid_resume_show; - lid_ctl->attrs[0].store = sony_nc_lid_resume_store; - - sysfs_attr_init(&lid_ctl->attrs[1].attr); - lid_ctl->attrs[1].attr.name = "lid_resume_S4"; - lid_ctl->attrs[1].attr.mode = S_IRUGO | S_IWUSR; - lid_ctl->attrs[1].show = sony_nc_lid_resume_show; - lid_ctl->attrs[1].store = sony_nc_lid_resume_store; - - sysfs_attr_init(&lid_ctl->attrs[2].attr); - lid_ctl->attrs[2].attr.name = "lid_resume_S5"; - lid_ctl->attrs[2].attr.mode = S_IRUGO | S_IWUSR; - lid_ctl->attrs[2].show = sony_nc_lid_resume_show; - lid_ctl->attrs[2].store = sony_nc_lid_resume_store; - - for (i = 0; i < 3; i++) { + lid_ctl->attrs[LID_RESUME_S5].attr.name = "lid_resume_S5"; + lid_ctl->attrs[LID_RESUME_S5].attr.mode = S_IRUGO | S_IWUSR; + lid_ctl->attrs[LID_RESUME_S5].show = sony_nc_lid_resume_show; + lid_ctl->attrs[LID_RESUME_S5].store = sony_nc_lid_resume_store; + + if (handle == 0x0119) { + sysfs_attr_init(&lid_ctl->attrs[1].attr); + lid_ctl->attrs[LID_RESUME_S4].attr.name = "lid_resume_S4"; + lid_ctl->attrs[LID_RESUME_S4].attr.mode = S_IRUGO | S_IWUSR; + lid_ctl->attrs[LID_RESUME_S4].show = sony_nc_lid_resume_show; + lid_ctl->attrs[LID_RESUME_S4].store = sony_nc_lid_resume_store; + + sysfs_attr_init(&lid_ctl->attrs[2].attr); + lid_ctl->attrs[LID_RESUME_S3].attr.name = "lid_resume_S3"; + lid_ctl->attrs[LID_RESUME_S3].attr.mode = S_IRUGO | S_IWUSR; + lid_ctl->attrs[LID_RESUME_S3].show = sony_nc_lid_resume_show; + lid_ctl->attrs[LID_RESUME_S3].store = sony_nc_lid_resume_store; + } + for (i = 0; i < LID_RESUME_MAX && + lid_ctl->attrs[LID_RESUME_S3].attr.name; i++) { result = device_create_file(&pd->dev, &lid_ctl->attrs[i]); if (result) goto liderror; @@ -2340,8 +2412,12 @@ static void sony_nc_lid_resume_cleanup(struct platform_device *pd) int i; if (lid_ctl) { - for (i = 0; i < 3; i++) + for (i = 0; i < LID_RESUME_MAX; i++) { + if (!lid_ctl->attrs[i].attr.name) + break; + device_remove_file(&pd->dev, &lid_ctl->attrs[i]); + } kfree(lid_ctl); lid_ctl = NULL; @@ -2524,6 +2600,355 @@ static void sony_nc_highspeed_charging_cleanup(struct platform_device *pd) } } +/* low battery function */ +static struct device_attribute *lowbatt_handle; + +static ssize_t sony_nc_lowbatt_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned int result; + unsigned long value; + + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value) || value > 1) + return -EINVAL; + + if (sony_call_snc_handle(0x0121, value << 8, &result)) + return -EIO; + + return count; +} + +static ssize_t sony_nc_lowbatt_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0121, 0x0200, &result)) + return -EIO; + + return snprintf(buffer, PAGE_SIZE, "%d\n", result & 1); +} + +static int sony_nc_lowbatt_setup(struct platform_device *pd) +{ + unsigned int result; + + lowbatt_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!lowbatt_handle) + return -ENOMEM; + + sysfs_attr_init(&lowbatt_handle->attr); + lowbatt_handle->attr.name = "lowbatt_hibernate"; + lowbatt_handle->attr.mode = S_IRUGO | S_IWUSR; + lowbatt_handle->show = sony_nc_lowbatt_show; + lowbatt_handle->store = sony_nc_lowbatt_store; + + result = device_create_file(&pd->dev, lowbatt_handle); + if (result) { + kfree(lowbatt_handle); + lowbatt_handle = NULL; + return result; + } + + return 0; +} + +static void sony_nc_lowbatt_cleanup(struct platform_device *pd) +{ + if (lowbatt_handle) { + device_remove_file(&pd->dev, lowbatt_handle); + kfree(lowbatt_handle); + lowbatt_handle = NULL; + } +} + +/* fan speed function */ +static struct device_attribute *fan_handle, *hsf_handle; + +static ssize_t sony_nc_hsfan_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned int result; + unsigned long value; + + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value) || value > 1) + return -EINVAL; + + if (sony_call_snc_handle(0x0149, value << 0x10 | 0x0200, &result)) + return -EIO; + + return count; +} + +static ssize_t sony_nc_hsfan_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0149, 0x0100, &result)) + return -EIO; + + return snprintf(buffer, PAGE_SIZE, "%d\n", result & 0x01); +} + +static ssize_t sony_nc_fanspeed_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0149, 0x0300, &result)) + return -EIO; + + return snprintf(buffer, PAGE_SIZE, "%d\n", result & 0xff); +} + +static int sony_nc_fanspeed_setup(struct platform_device *pd) +{ + unsigned int result; + + fan_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!fan_handle) + return -ENOMEM; + + hsf_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!hsf_handle) { + result = -ENOMEM; + goto out_hsf_handle_alloc; + } + + sysfs_attr_init(&fan_handle->attr); + fan_handle->attr.name = "fanspeed"; + fan_handle->attr.mode = S_IRUGO; + fan_handle->show = sony_nc_fanspeed_show; + fan_handle->store = NULL; + + sysfs_attr_init(&hsf_handle->attr); + hsf_handle->attr.name = "fan_forced"; + hsf_handle->attr.mode = S_IRUGO | S_IWUSR; + hsf_handle->show = sony_nc_hsfan_show; + hsf_handle->store = sony_nc_hsfan_store; + + result = device_create_file(&pd->dev, fan_handle); + if (result) + goto out_fan_handle; + + result = device_create_file(&pd->dev, hsf_handle); + if (result) + goto out_hsf_handle; + + return 0; + +out_hsf_handle: + device_remove_file(&pd->dev, fan_handle); + +out_fan_handle: + kfree(hsf_handle); + hsf_handle = NULL; + +out_hsf_handle_alloc: + kfree(fan_handle); + fan_handle = NULL; + return result; +} + +static void sony_nc_fanspeed_cleanup(struct platform_device *pd) +{ + if (fan_handle) { + device_remove_file(&pd->dev, fan_handle); + kfree(fan_handle); + fan_handle = NULL; + } + if (hsf_handle) { + device_remove_file(&pd->dev, hsf_handle); + kfree(hsf_handle); + hsf_handle = NULL; + } +} + +/* USB charge function */ +static struct device_attribute *uc_handle; + +static ssize_t sony_nc_usb_charge_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned int result; + unsigned long value; + + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value) || value > 1) + return -EINVAL; + + if (sony_call_snc_handle(0x0155, value << 0x10 | 0x0100, &result)) + return -EIO; + + return count; +} + +static ssize_t sony_nc_usb_charge_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0155, 0x0000, &result)) + return -EIO; + + return snprintf(buffer, PAGE_SIZE, "%d\n", result & 0x01); +} + +static int sony_nc_usb_charge_setup(struct platform_device *pd) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0155, 0x0000, &result) || !(result & 0x01)) { + /* some models advertise the handle but have no implementation + * for it + */ + pr_info("No USB Charge capability found\n"); + return 0; + } + + uc_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!uc_handle) + return -ENOMEM; + + sysfs_attr_init(&uc_handle->attr); + uc_handle->attr.name = "usb_charge"; + uc_handle->attr.mode = S_IRUGO | S_IWUSR; + uc_handle->show = sony_nc_usb_charge_show; + uc_handle->store = sony_nc_usb_charge_store; + + result = device_create_file(&pd->dev, uc_handle); + if (result) { + kfree(uc_handle); + uc_handle = NULL; + return result; + } + + return 0; +} + +static void sony_nc_usb_charge_cleanup(struct platform_device *pd) +{ + if (uc_handle) { + device_remove_file(&pd->dev, uc_handle); + kfree(uc_handle); + uc_handle = NULL; + } +} + +/* Panel ID function */ +static struct device_attribute *panel_handle; + +static ssize_t sony_nc_panelid_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result; + + if (sony_call_snc_handle(0x011D, 0x0000, &result)) + return -EIO; + + return snprintf(buffer, PAGE_SIZE, "%d\n", result); +} + +static int sony_nc_panelid_setup(struct platform_device *pd) +{ + unsigned int result; + + panel_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!panel_handle) + return -ENOMEM; + + sysfs_attr_init(&panel_handle->attr); + panel_handle->attr.name = "panel_id"; + panel_handle->attr.mode = S_IRUGO; + panel_handle->show = sony_nc_panelid_show; + panel_handle->store = NULL; + + result = device_create_file(&pd->dev, panel_handle); + if (result) { + kfree(panel_handle); + panel_handle = NULL; + return result; + } + + return 0; +} + +static void sony_nc_panelid_cleanup(struct platform_device *pd) +{ + if (panel_handle) { + device_remove_file(&pd->dev, panel_handle); + kfree(panel_handle); + panel_handle = NULL; + } +} + +/* smart connect function */ +static struct device_attribute *sc_handle; + +static ssize_t sony_nc_smart_conn_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned int result; + unsigned long value; + + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value) || value > 1) + return -EINVAL; + + if (sony_call_snc_handle(0x0168, value << 0x10, &result)) + return -EIO; + + return count; +} + +static int sony_nc_smart_conn_setup(struct platform_device *pd) +{ + unsigned int result; + + sc_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!sc_handle) + return -ENOMEM; + + sysfs_attr_init(&sc_handle->attr); + sc_handle->attr.name = "smart_connect"; + sc_handle->attr.mode = S_IWUSR; + sc_handle->show = NULL; + sc_handle->store = sony_nc_smart_conn_store; + + result = device_create_file(&pd->dev, sc_handle); + if (result) { + kfree(sc_handle); + sc_handle = NULL; + return result; + } + + return 0; +} + +static void sony_nc_smart_conn_cleanup(struct platform_device *pd) +{ + if (sc_handle) { + device_remove_file(&pd->dev, sc_handle); + kfree(sc_handle); + sc_handle = NULL; + } +} + /* Touchpad enable/disable */ struct touchpad_control { struct device_attribute attr; @@ -2726,8 +3151,6 @@ static int sony_nc_add(struct acpi_device *device) int result = 0; struct sony_nc_value *item; - pr_info("%s v%s\n", SONY_NC_DRIVER_NAME, SONY_LAPTOP_DRIVER_VERSION); - sony_nc_acpi_device = device; strcpy(acpi_device_class(device), "sony/hotkey"); @@ -2821,6 +3244,7 @@ static int sony_nc_add(struct acpi_device *device) } } + pr_info("SNC setup done.\n"); return 0; out_sysfs: @@ -4259,8 +4683,6 @@ static int sony_pic_add(struct acpi_device *device) struct sony_pic_ioport *io, *tmp_io; struct sony_pic_irq *irq, *tmp_irq; - pr_info("%s v%s\n", SONY_PIC_DRIVER_NAME, SONY_LAPTOP_DRIVER_VERSION); - spic_dev.acpi_dev = device; strcpy(acpi_device_class(device), "sony/hotkey"); sony_pic_detect_device_type(&spic_dev); @@ -4360,6 +4782,7 @@ static int sony_pic_add(struct acpi_device *device) if (result) goto err_remove_pf; + pr_info("SPIC setup done.\n"); return 0; err_remove_pf: diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 94bb6157c957..15e61c16736e 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -2321,53 +2321,55 @@ static void hotkey_read_nvram(struct tp_nvram_state *n, const u32 m) } } -static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, - struct tp_nvram_state *newn, - const u32 event_mask) -{ - #define TPACPI_COMPARE_KEY(__scancode, __member) \ - do { \ - if ((event_mask & (1 << __scancode)) && \ - oldn->__member != newn->__member) \ - tpacpi_hotkey_send_key(__scancode); \ - } while (0) +do { \ + if ((event_mask & (1 << __scancode)) && \ + oldn->__member != newn->__member) \ + tpacpi_hotkey_send_key(__scancode); \ +} while (0) #define TPACPI_MAY_SEND_KEY(__scancode) \ - do { \ - if (event_mask & (1 << __scancode)) \ - tpacpi_hotkey_send_key(__scancode); \ - } while (0) +do { \ + if (event_mask & (1 << __scancode)) \ + tpacpi_hotkey_send_key(__scancode); \ +} while (0) - void issue_volchange(const unsigned int oldvol, - const unsigned int newvol) - { - unsigned int i = oldvol; +static void issue_volchange(const unsigned int oldvol, + const unsigned int newvol, + const u32 event_mask) +{ + unsigned int i = oldvol; - while (i > newvol) { - TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN); - i--; - } - while (i < newvol) { - TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP); - i++; - } + while (i > newvol) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN); + i--; + } + while (i < newvol) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP); + i++; } +} - void issue_brightnesschange(const unsigned int oldbrt, - const unsigned int newbrt) - { - unsigned int i = oldbrt; +static void issue_brightnesschange(const unsigned int oldbrt, + const unsigned int newbrt, + const u32 event_mask) +{ + unsigned int i = oldbrt; - while (i > newbrt) { - TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND); - i--; - } - while (i < newbrt) { - TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME); - i++; - } + while (i > newbrt) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND); + i--; } + while (i < newbrt) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME); + i++; + } +} + +static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, + struct tp_nvram_state *newn, + const u32 event_mask) +{ TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_THINKPAD, thinkpad_toggle); TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNSPACE, zoom_toggle); @@ -2402,7 +2404,8 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, oldn->volume_level != newn->volume_level) { /* recently muted, or repeated mute keypress, or * multiple presses ending in mute */ - issue_volchange(oldn->volume_level, newn->volume_level); + issue_volchange(oldn->volume_level, newn->volume_level, + event_mask); TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE); } } else { @@ -2412,7 +2415,8 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP); } if (oldn->volume_level != newn->volume_level) { - issue_volchange(oldn->volume_level, newn->volume_level); + issue_volchange(oldn->volume_level, newn->volume_level, + event_mask); } else if (oldn->volume_toggle != newn->volume_toggle) { /* repeated vol up/down keypress at end of scale ? */ if (newn->volume_level == 0) @@ -2425,7 +2429,7 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, /* handle brightness */ if (oldn->brightness_level != newn->brightness_level) { issue_brightnesschange(oldn->brightness_level, - newn->brightness_level); + newn->brightness_level, event_mask); } else if (oldn->brightness_toggle != newn->brightness_toggle) { /* repeated key presses that didn't change state */ if (newn->brightness_level == 0) @@ -3437,6 +3441,106 @@ err_exit: return (res < 0)? res : 1; } +/* Thinkpad X1 Carbon support 5 modes including Home mode, Web browser + * mode, Web conference mode, Function mode and Lay-flat mode. + * We support Home mode and Function mode currently. + * + * Will consider support rest of modes in future. + * + */ +enum ADAPTIVE_KEY_MODE { + HOME_MODE, + WEB_BROWSER_MODE, + WEB_CONFERENCE_MODE, + FUNCTION_MODE, + LAYFLAT_MODE +}; + +const int adaptive_keyboard_modes[] = { + HOME_MODE, +/* WEB_BROWSER_MODE = 2, + WEB_CONFERENCE_MODE = 3, */ + FUNCTION_MODE +}; + +#define DFR_CHANGE_ROW 0x101 +#define DFR_SHOW_QUICKVIEW_ROW 0x102 + +/* press Fn key a while second, it will switch to Function Mode. Then + * release Fn key, previous mode be restored. + */ +static bool adaptive_keyboard_mode_is_saved; +static int adaptive_keyboard_prev_mode; + +static int adaptive_keyboard_get_next_mode(int mode) +{ + size_t i; + size_t max_mode = ARRAY_SIZE(adaptive_keyboard_modes) - 1; + + for (i = 0; i <= max_mode; i++) { + if (adaptive_keyboard_modes[i] == mode) + break; + } + + if (i >= max_mode) + i = 0; + else + i++; + + return adaptive_keyboard_modes[i]; +} + +static bool adaptive_keyboard_hotkey_notify_hotkey(unsigned int scancode) +{ + u32 current_mode = 0; + int new_mode = 0; + + switch (scancode) { + case DFR_CHANGE_ROW: + if (adaptive_keyboard_mode_is_saved) { + new_mode = adaptive_keyboard_prev_mode; + adaptive_keyboard_mode_is_saved = false; + } else { + if (!acpi_evalf( + hkey_handle, ¤t_mode, + "GTRW", "dd", 0)) { + pr_err("Cannot read adaptive keyboard mode\n"); + return false; + } else { + new_mode = adaptive_keyboard_get_next_mode( + current_mode); + } + } + + if (!acpi_evalf(hkey_handle, NULL, "STRW", "vd", new_mode)) { + pr_err("Cannot set adaptive keyboard mode\n"); + return false; + } + + return true; + + case DFR_SHOW_QUICKVIEW_ROW: + if (!acpi_evalf(hkey_handle, + &adaptive_keyboard_prev_mode, + "GTRW", "dd", 0)) { + pr_err("Cannot read adaptive keyboard mode\n"); + return false; + } else { + adaptive_keyboard_mode_is_saved = true; + + if (!acpi_evalf(hkey_handle, + NULL, "STRW", "vd", FUNCTION_MODE)) { + pr_err("Cannot set adaptive keyboard mode\n"); + return false; + } + } + return true; + + default: + return false; + } +} + static bool hotkey_notify_hotkey(const u32 hkey, bool *send_acpi_ev, bool *ignore_acpi_ev) @@ -3456,6 +3560,8 @@ static bool hotkey_notify_hotkey(const u32 hkey, *ignore_acpi_ev = true; } return true; + } else { + return adaptive_keyboard_hotkey_notify_hotkey(scancode); } return false; } @@ -3728,13 +3834,28 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) static void hotkey_suspend(void) { + int hkeyv; + /* Do these on suspend, we get the events on early resume! */ hotkey_wakeup_reason = TP_ACPI_WAKEUP_NONE; hotkey_autosleep_ack = 0; + + /* save previous mode of adaptive keyboard of X1 Carbon */ + if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) { + if ((hkeyv >> 8) == 2) { + if (!acpi_evalf(hkey_handle, + &adaptive_keyboard_prev_mode, + "GTRW", "dd", 0)) { + pr_err("Cannot read adaptive keyboard mode.\n"); + } + } + } } static void hotkey_resume(void) { + int hkeyv; + tpacpi_disable_brightness_delay(); if (hotkey_status_set(true) < 0 || @@ -3747,6 +3868,18 @@ static void hotkey_resume(void) hotkey_wakeup_reason_notify_change(); hotkey_wakeup_hotunplug_complete_notify_change(); hotkey_poll_setup_safe(false); + + /* restore previous mode of adapive keyboard of X1 Carbon */ + if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) { + if ((hkeyv >> 8) == 2) { + if (!acpi_evalf(hkey_handle, + NULL, + "STRW", "vd", + adaptive_keyboard_prev_mode)) { + pr_err("Cannot set adaptive keyboard mode.\n"); + } + } + } } /* procfs -------------------------------------------------------------- */ @@ -8447,9 +8580,21 @@ static void mute_led_exit(void) tpacpi_led_set(i, false); } +static void mute_led_resume(void) +{ + int i; + + for (i = 0; i < TPACPI_LED_MAX; i++) { + struct tp_led_table *t = &led_tables[i]; + if (t->state >= 0) + mute_led_on_off(t, t->state); + } +} + static struct ibm_struct mute_led_driver_data = { .name = "mute_led", .exit = mute_led_exit, + .resume = mute_led_resume, }; /**************************************************************************** diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index 90dd7645a9e5..46473ca7566b 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -5,6 +5,7 @@ * Copyright (C) 2002-2004 John Belmonte * Copyright (C) 2008 Philip Langdale * Copyright (C) 2010 Pierre Ducroquet + * Copyright (C) 2014 Azael Avalos * * 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 @@ -37,7 +38,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define TOSHIBA_ACPI_VERSION "0.19" +#define TOSHIBA_ACPI_VERSION "0.20" #define PROC_INTERFACE_VERSION 1 #include <linux/kernel.h> @@ -77,6 +78,9 @@ MODULE_LICENSE("GPL"); * However the ACPI methods seem to be incomplete in some areas (for * example they allow setting, but not reading, the LCD brightness value), * so this is still useful. + * + * SCI stands for "System Configuration Interface" which aim is to + * conceal differences in hardware between different models. */ #define HCI_WORDS 6 @@ -84,12 +88,23 @@ MODULE_LICENSE("GPL"); /* operations */ #define HCI_SET 0xff00 #define HCI_GET 0xfe00 +#define SCI_OPEN 0xf100 +#define SCI_CLOSE 0xf200 +#define SCI_GET 0xf300 +#define SCI_SET 0xf400 /* return codes */ #define HCI_SUCCESS 0x0000 #define HCI_FAILURE 0x1000 #define HCI_NOT_SUPPORTED 0x8000 #define HCI_EMPTY 0x8c00 +#define HCI_DATA_NOT_AVAILABLE 0x8d20 +#define HCI_NOT_INITIALIZED 0x8d50 +#define SCI_OPEN_CLOSE_OK 0x0044 +#define SCI_ALREADY_OPEN 0x8100 +#define SCI_NOT_OPENED 0x8200 +#define SCI_INPUT_DATA_ERROR 0x8300 +#define SCI_NOT_PRESENT 0x8600 /* registers */ #define HCI_FAN 0x0004 @@ -99,13 +114,22 @@ MODULE_LICENSE("GPL"); #define HCI_HOTKEY_EVENT 0x001e #define HCI_LCD_BRIGHTNESS 0x002a #define HCI_WIRELESS 0x0056 +#define HCI_ACCELEROMETER 0x006d +#define HCI_KBD_ILLUMINATION 0x0095 +#define HCI_ECO_MODE 0x0097 +#define HCI_ACCELEROMETER2 0x00a6 +#define SCI_ILLUMINATION 0x014e +#define SCI_KBD_ILLUM_STATUS 0x015c +#define SCI_TOUCHPAD 0x050e /* field definitions */ +#define HCI_ACCEL_MASK 0x7fff #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) +#define HCI_MISC_SHIFT 0x10 #define HCI_VIDEO_OUT_LCD 0x1 #define HCI_VIDEO_OUT_CRT 0x2 #define HCI_VIDEO_OUT_TV 0x4 @@ -113,6 +137,8 @@ MODULE_LICENSE("GPL"); #define HCI_WIRELESS_BT_PRESENT 0x0f #define HCI_WIRELESS_BT_ATTACH 0x40 #define HCI_WIRELESS_BT_POWER 0x80 +#define SCI_KBD_MODE_FNZ 0x1 +#define SCI_KBD_MODE_AUTO 0x2 struct toshiba_acpi_dev { struct acpi_device *acpi_dev; @@ -122,10 +148,14 @@ struct toshiba_acpi_dev { struct work_struct hotkey_work; struct backlight_device *backlight_dev; struct led_classdev led_dev; + struct led_classdev kbd_led; + struct led_classdev eco_led; int force_fan; int last_key_event; int key_event_valid; + int kbd_mode; + int kbd_time; unsigned int illumination_supported:1; unsigned int video_supported:1; @@ -134,6 +164,12 @@ struct toshiba_acpi_dev { unsigned int ntfy_supported:1; unsigned int info_supported:1; unsigned int tr_backlight_supported:1; + unsigned int kbd_illum_supported:1; + unsigned int kbd_led_registered:1; + unsigned int touchpad_supported:1; + unsigned int eco_supported:1; + unsigned int accelerometer_supported:1; + unsigned int sysfs_created:1; struct mutex mutex; }; @@ -280,21 +316,94 @@ static acpi_status hci_read2(struct toshiba_acpi_dev *dev, u32 reg, return status; } +/* common sci tasks + */ + +static int sci_open(struct toshiba_acpi_dev *dev) +{ + u32 in[HCI_WORDS] = { SCI_OPEN, 0, 0, 0, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status; + + status = hci_raw(dev, in, out); + if (ACPI_FAILURE(status) || out[0] == HCI_FAILURE) { + pr_err("ACPI call to open SCI failed\n"); + return 0; + } + + if (out[0] == SCI_OPEN_CLOSE_OK) { + return 1; + } else if (out[0] == SCI_ALREADY_OPEN) { + pr_info("Toshiba SCI already opened\n"); + return 1; + } else if (out[0] == SCI_NOT_PRESENT) { + pr_info("Toshiba SCI is not present\n"); + } + + return 0; +} + +static void sci_close(struct toshiba_acpi_dev *dev) +{ + u32 in[HCI_WORDS] = { SCI_CLOSE, 0, 0, 0, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status; + + status = hci_raw(dev, in, out); + if (ACPI_FAILURE(status) || out[0] == HCI_FAILURE) { + pr_err("ACPI call to close SCI failed\n"); + return; + } + + if (out[0] == SCI_OPEN_CLOSE_OK) + return; + else if (out[0] == SCI_NOT_OPENED) + pr_info("Toshiba SCI not opened\n"); + else if (out[0] == SCI_NOT_PRESENT) + pr_info("Toshiba SCI is not present\n"); +} + +static acpi_status sci_read(struct toshiba_acpi_dev *dev, u32 reg, + u32 *out1, u32 *result) +{ + u32 in[HCI_WORDS] = { SCI_GET, reg, 0, 0, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status = hci_raw(dev, in, out); + *out1 = out[2]; + *result = (ACPI_SUCCESS(status)) ? out[0] : HCI_FAILURE; + return status; +} + +static acpi_status sci_write(struct toshiba_acpi_dev *dev, u32 reg, + u32 in1, u32 *result) +{ + u32 in[HCI_WORDS] = { SCI_SET, reg, in1, 0, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status = hci_raw(dev, in, out); + *result = (ACPI_SUCCESS(status)) ? out[0] : HCI_FAILURE; + return status; +} + /* Illumination support */ static int toshiba_illumination_available(struct toshiba_acpi_dev *dev) { - u32 in[HCI_WORDS] = { 0, 0, 0, 0, 0, 0 }; + u32 in[HCI_WORDS] = { SCI_GET, SCI_ILLUMINATION, 0, 0, 0, 0 }; u32 out[HCI_WORDS]; acpi_status status; - in[0] = 0xf100; + if (!sci_open(dev)) + return 0; + status = hci_raw(dev, in, out); - if (ACPI_FAILURE(status)) { + sci_close(dev); + if (ACPI_FAILURE(status) || out[0] == HCI_FAILURE) { + pr_err("ACPI call to query Illumination support failed\n"); + return 0; + } else if (out[0] == HCI_NOT_SUPPORTED || out[1] != 1) { pr_info("Illumination device not available\n"); return 0; } - in[0] = 0xf400; - status = hci_raw(dev, in, out); + return 1; } @@ -303,82 +412,270 @@ static void toshiba_illumination_set(struct led_classdev *cdev, { struct toshiba_acpi_dev *dev = container_of(cdev, struct toshiba_acpi_dev, led_dev); - u32 in[HCI_WORDS] = { 0, 0, 0, 0, 0, 0 }; - u32 out[HCI_WORDS]; + u32 state, result; acpi_status status; /* First request : initialize communication. */ - in[0] = 0xf100; - status = hci_raw(dev, in, out); + if (!sci_open(dev)) + return; + + /* Switch the illumination on/off */ + state = brightness ? 1 : 0; + status = sci_write(dev, SCI_ILLUMINATION, state, &result); + sci_close(dev); if (ACPI_FAILURE(status)) { - pr_info("Illumination device not available\n"); + pr_err("ACPI call for illumination failed\n"); + return; + } else if (result == HCI_NOT_SUPPORTED) { + pr_info("Illumination not supported\n"); return; } +} - if (brightness) { - /* Switch the illumination on */ - in[0] = 0xf400; - in[1] = 0x14e; - in[2] = 1; - status = hci_raw(dev, in, out); - if (ACPI_FAILURE(status)) { - pr_info("ACPI call for illumination failed\n"); - return; - } - } else { - /* Switch the illumination off */ - in[0] = 0xf400; - in[1] = 0x14e; - in[2] = 0; - status = hci_raw(dev, in, out); - if (ACPI_FAILURE(status)) { - pr_info("ACPI call for illumination failed.\n"); - return; - } +static enum led_brightness toshiba_illumination_get(struct led_classdev *cdev) +{ + struct toshiba_acpi_dev *dev = container_of(cdev, + struct toshiba_acpi_dev, led_dev); + u32 state, result; + acpi_status status; + + /* First request : initialize communication. */ + if (!sci_open(dev)) + return LED_OFF; + + /* Check the illumination */ + status = sci_read(dev, SCI_ILLUMINATION, &state, &result); + sci_close(dev); + if (ACPI_FAILURE(status) || result == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call for illumination failed\n"); + return LED_OFF; + } else if (result == HCI_NOT_SUPPORTED) { + pr_info("Illumination not supported\n"); + return LED_OFF; } - /* Last request : close communication. */ - in[0] = 0xf200; - in[1] = 0; - in[2] = 0; - hci_raw(dev, in, out); + return state ? LED_FULL : LED_OFF; } -static enum led_brightness toshiba_illumination_get(struct led_classdev *cdev) +/* KBD Illumination */ +static int toshiba_kbd_illum_status_set(struct toshiba_acpi_dev *dev, u32 time) +{ + u32 result; + acpi_status status; + + if (!sci_open(dev)) + return -EIO; + + status = sci_write(dev, SCI_KBD_ILLUM_STATUS, time, &result); + sci_close(dev); + if (ACPI_FAILURE(status) || result == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call to set KBD backlight status failed\n"); + return -EIO; + } else if (result == HCI_NOT_SUPPORTED) { + pr_info("Keyboard backlight status not supported\n"); + return -ENODEV; + } + + return 0; +} + +static int toshiba_kbd_illum_status_get(struct toshiba_acpi_dev *dev, u32 *time) +{ + u32 result; + acpi_status status; + + if (!sci_open(dev)) + return -EIO; + + status = sci_read(dev, SCI_KBD_ILLUM_STATUS, time, &result); + sci_close(dev); + if (ACPI_FAILURE(status) || result == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call to get KBD backlight status failed\n"); + return -EIO; + } else if (result == HCI_NOT_SUPPORTED) { + pr_info("Keyboard backlight status not supported\n"); + return -ENODEV; + } + + return 0; +} + +static enum led_brightness toshiba_kbd_backlight_get(struct led_classdev *cdev) { struct toshiba_acpi_dev *dev = container_of(cdev, - struct toshiba_acpi_dev, led_dev); - u32 in[HCI_WORDS] = { 0, 0, 0, 0, 0, 0 }; + struct toshiba_acpi_dev, kbd_led); + u32 state, result; + acpi_status status; + + /* Check the keyboard backlight state */ + status = hci_read1(dev, HCI_KBD_ILLUMINATION, &state, &result); + if (ACPI_FAILURE(status) || result == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call to get the keyboard backlight failed\n"); + return LED_OFF; + } else if (result == HCI_NOT_SUPPORTED) { + pr_info("Keyboard backlight not supported\n"); + return LED_OFF; + } + + return state ? LED_FULL : LED_OFF; +} + +static void toshiba_kbd_backlight_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct toshiba_acpi_dev *dev = container_of(cdev, + struct toshiba_acpi_dev, kbd_led); + u32 state, result; + acpi_status status; + + /* Set the keyboard backlight state */ + state = brightness ? 1 : 0; + status = hci_write1(dev, HCI_KBD_ILLUMINATION, state, &result); + if (ACPI_FAILURE(status) || result == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call to set KBD Illumination mode failed\n"); + return; + } else if (result == HCI_NOT_SUPPORTED) { + pr_info("Keyboard backlight not supported\n"); + return; + } +} + +/* TouchPad support */ +static int toshiba_touchpad_set(struct toshiba_acpi_dev *dev, u32 state) +{ + u32 result; + acpi_status status; + + if (!sci_open(dev)) + return -EIO; + + status = sci_write(dev, SCI_TOUCHPAD, state, &result); + sci_close(dev); + if (ACPI_FAILURE(status)) { + pr_err("ACPI call to set the touchpad failed\n"); + return -EIO; + } else if (result == HCI_NOT_SUPPORTED) { + return -ENODEV; + } + + return 0; +} + +static int toshiba_touchpad_get(struct toshiba_acpi_dev *dev, u32 *state) +{ + u32 result; + acpi_status status; + + if (!sci_open(dev)) + return -EIO; + + status = sci_read(dev, SCI_TOUCHPAD, state, &result); + sci_close(dev); + if (ACPI_FAILURE(status)) { + pr_err("ACPI call to query the touchpad failed\n"); + return -EIO; + } else if (result == HCI_NOT_SUPPORTED) { + return -ENODEV; + } + + return 0; +} + +/* Eco Mode support */ +static int toshiba_eco_mode_available(struct toshiba_acpi_dev *dev) +{ + acpi_status status; + u32 in[HCI_WORDS] = { HCI_GET, HCI_ECO_MODE, 0, 1, 0, 0 }; + u32 out[HCI_WORDS]; + + status = hci_raw(dev, in, out); + if (ACPI_FAILURE(status) || out[0] == SCI_INPUT_DATA_ERROR) { + pr_info("ACPI call to get ECO led failed\n"); + return 0; + } + + return 1; +} + +static enum led_brightness toshiba_eco_mode_get_status(struct led_classdev *cdev) +{ + struct toshiba_acpi_dev *dev = container_of(cdev, + struct toshiba_acpi_dev, eco_led); + u32 in[HCI_WORDS] = { HCI_GET, HCI_ECO_MODE, 0, 1, 0, 0 }; u32 out[HCI_WORDS]; acpi_status status; - enum led_brightness result; - /* First request : initialize communication. */ - in[0] = 0xf100; status = hci_raw(dev, in, out); - if (ACPI_FAILURE(status)) { - pr_info("Illumination device not available\n"); + if (ACPI_FAILURE(status) || out[0] == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call to get ECO led failed\n"); return LED_OFF; } - /* Check the illumination */ - in[0] = 0xf300; - in[1] = 0x14e; + return out[2] ? LED_FULL : LED_OFF; +} + +static void toshiba_eco_mode_set_status(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct toshiba_acpi_dev *dev = container_of(cdev, + struct toshiba_acpi_dev, eco_led); + u32 in[HCI_WORDS] = { HCI_SET, HCI_ECO_MODE, 0, 1, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status; + + /* Switch the Eco Mode led on/off */ + in[2] = (brightness) ? 1 : 0; status = hci_raw(dev, in, out); - if (ACPI_FAILURE(status)) { - pr_info("ACPI call for illumination failed.\n"); - return LED_OFF; + if (ACPI_FAILURE(status) || out[0] == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call to set ECO led failed\n"); + return; } +} - result = out[2] ? LED_FULL : LED_OFF; +/* Accelerometer support */ +static int toshiba_accelerometer_supported(struct toshiba_acpi_dev *dev) +{ + u32 in[HCI_WORDS] = { HCI_GET, HCI_ACCELEROMETER2, 0, 0, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status; + + /* Check if the accelerometer call exists, + * this call also serves as initialization + */ + status = hci_raw(dev, in, out); + if (ACPI_FAILURE(status) || out[0] == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call to query the accelerometer failed\n"); + return -EIO; + } else if (out[0] == HCI_DATA_NOT_AVAILABLE || + out[0] == HCI_NOT_INITIALIZED) { + pr_err("Accelerometer not initialized\n"); + return -EIO; + } else if (out[0] == HCI_NOT_SUPPORTED) { + pr_info("Accelerometer not supported\n"); + return -ENODEV; + } + + return 0; +} + +static int toshiba_accelerometer_get(struct toshiba_acpi_dev *dev, + u32 *xy, u32 *z) +{ + u32 in[HCI_WORDS] = { HCI_GET, HCI_ACCELEROMETER, 0, 1, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status; + + /* Check the Accelerometer status */ + status = hci_raw(dev, in, out); + if (ACPI_FAILURE(status) || out[0] == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call to query the accelerometer failed\n"); + return -EIO; + } - /* Last request : close communication. */ - in[0] = 0xf200; - in[1] = 0; - in[2] = 0; - hci_raw(dev, in, out); + *xy = out[2]; + *z = out[4]; - return result; + return 0; } /* Bluetooth rfkill handlers */ @@ -904,6 +1201,177 @@ static const struct backlight_ops toshiba_backlight_data = { .update_status = set_lcd_status, }; +/* + * Sysfs files + */ + +static ssize_t toshiba_kbd_bl_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + int mode = -1; + int time = -1; + + if (sscanf(buf, "%i", &mode) != 1 && (mode != 2 || mode != 1)) + return -EINVAL; + + /* Set the Keyboard Backlight Mode where: + * Mode - Auto (2) | FN-Z (1) + * Auto - KBD backlight turns off automatically in given time + * FN-Z - KBD backlight "toggles" when hotkey pressed + */ + if (mode != -1 && toshiba->kbd_mode != mode) { + time = toshiba->kbd_time << HCI_MISC_SHIFT; + time = time + toshiba->kbd_mode; + if (toshiba_kbd_illum_status_set(toshiba, time) < 0) + return -EIO; + toshiba->kbd_mode = mode; + } + + return count; +} + +static ssize_t toshiba_kbd_bl_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 time; + + if (toshiba_kbd_illum_status_get(toshiba, &time) < 0) + return -EIO; + + return sprintf(buf, "%i\n", time & 0x07); +} + +static ssize_t toshiba_kbd_bl_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + int time = -1; + + if (sscanf(buf, "%i", &time) != 1 && (time < 0 || time > 60)) + return -EINVAL; + + /* Set the Keyboard Backlight Timeout: 0-60 seconds */ + if (time != -1 && toshiba->kbd_time != time) { + time = time << HCI_MISC_SHIFT; + time = (toshiba->kbd_mode == SCI_KBD_MODE_AUTO) ? + time + 1 : time + 2; + if (toshiba_kbd_illum_status_set(toshiba, time) < 0) + return -EIO; + toshiba->kbd_time = time >> HCI_MISC_SHIFT; + } + + return count; +} + +static ssize_t toshiba_kbd_bl_timeout_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 time; + + if (toshiba_kbd_illum_status_get(toshiba, &time) < 0) + return -EIO; + + return sprintf(buf, "%i\n", time >> HCI_MISC_SHIFT); +} + +static ssize_t toshiba_touchpad_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + int state; + + /* Set the TouchPad on/off, 0 - Disable | 1 - Enable */ + if (sscanf(buf, "%i", &state) == 1 && (state == 0 || state == 1)) { + if (toshiba_touchpad_set(toshiba, state) < 0) + return -EIO; + } + + return count; +} + +static ssize_t toshiba_touchpad_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 state; + int ret; + + ret = toshiba_touchpad_get(toshiba, &state); + if (ret < 0) + return ret; + + return sprintf(buf, "%i\n", state); +} + +static ssize_t toshiba_position_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 xyval, zval, tmp; + u16 x, y, z; + int ret; + + xyval = zval = 0; + ret = toshiba_accelerometer_get(toshiba, &xyval, &zval); + if (ret < 0) + return ret; + + x = xyval & HCI_ACCEL_MASK; + tmp = xyval >> HCI_MISC_SHIFT; + y = tmp & HCI_ACCEL_MASK; + z = zval & HCI_ACCEL_MASK; + + return sprintf(buf, "%d %d %d\n", x, y, z); +} + +static DEVICE_ATTR(kbd_backlight_mode, S_IRUGO | S_IWUSR, + toshiba_kbd_bl_mode_show, toshiba_kbd_bl_mode_store); +static DEVICE_ATTR(kbd_backlight_timeout, S_IRUGO | S_IWUSR, + toshiba_kbd_bl_timeout_show, toshiba_kbd_bl_timeout_store); +static DEVICE_ATTR(touchpad, S_IRUGO | S_IWUSR, + toshiba_touchpad_show, toshiba_touchpad_store); +static DEVICE_ATTR(position, S_IRUGO, toshiba_position_show, NULL); + +static struct attribute *toshiba_attributes[] = { + &dev_attr_kbd_backlight_mode.attr, + &dev_attr_kbd_backlight_timeout.attr, + &dev_attr_touchpad.attr, + &dev_attr_position.attr, + NULL, +}; + +static umode_t toshiba_sysfs_is_visible(struct kobject *kobj, + struct attribute *attr, int idx) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct toshiba_acpi_dev *drv = dev_get_drvdata(dev); + bool exists = true; + + if (attr == &dev_attr_kbd_backlight_mode.attr) + exists = (drv->kbd_illum_supported) ? true : false; + else if (attr == &dev_attr_kbd_backlight_timeout.attr) + exists = (drv->kbd_mode == SCI_KBD_MODE_AUTO) ? true : false; + else if (attr == &dev_attr_touchpad.attr) + exists = (drv->touchpad_supported) ? true : false; + else if (attr == &dev_attr_position.attr) + exists = (drv->accelerometer_supported) ? true : false; + + return exists ? attr->mode : 0; +} + +static struct attribute_group toshiba_attr_group = { + .is_visible = toshiba_sysfs_is_visible, + .attrs = toshiba_attributes, +}; + static bool toshiba_acpi_i8042_filter(unsigned char data, unsigned char str, struct serio *port) { @@ -1106,6 +1574,10 @@ static int toshiba_acpi_remove(struct acpi_device *acpi_dev) remove_toshiba_proc_entries(dev); + if (dev->sysfs_created) + sysfs_remove_group(&dev->acpi_dev->dev.kobj, + &toshiba_attr_group); + if (dev->ntfy_supported) { i8042_remove_filter(toshiba_acpi_i8042_filter); cancel_work_sync(&dev->hotkey_work); @@ -1127,6 +1599,12 @@ static int toshiba_acpi_remove(struct acpi_device *acpi_dev) if (dev->illumination_supported) led_classdev_unregister(&dev->led_dev); + if (dev->kbd_led_registered) + led_classdev_unregister(&dev->kbd_led); + + if (dev->eco_supported) + led_classdev_unregister(&dev->eco_led); + if (toshiba_acpi) toshiba_acpi = NULL; @@ -1172,6 +1650,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) dev->acpi_dev = acpi_dev; dev->method_hci = hci_method; acpi_dev->driver_data = dev; + dev_set_drvdata(&acpi_dev->dev, dev); if (toshiba_acpi_setup_keyboard(dev)) pr_info("Unable to activate hotkeys\n"); @@ -1212,6 +1691,40 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) dev->illumination_supported = 1; } + if (toshiba_eco_mode_available(dev)) { + dev->eco_led.name = "toshiba::eco_mode"; + dev->eco_led.max_brightness = 1; + dev->eco_led.brightness_set = toshiba_eco_mode_set_status; + dev->eco_led.brightness_get = toshiba_eco_mode_get_status; + if (!led_classdev_register(&dev->acpi_dev->dev, &dev->eco_led)) + dev->eco_supported = 1; + } + + ret = toshiba_kbd_illum_status_get(dev, &dummy); + if (!ret) { + dev->kbd_time = dummy >> HCI_MISC_SHIFT; + dev->kbd_mode = dummy & 0x07; + } + dev->kbd_illum_supported = !ret; + /* + * Only register the LED if KBD illumination is supported + * and the keyboard backlight operation mode is set to FN-Z + */ + if (dev->kbd_illum_supported && dev->kbd_mode == SCI_KBD_MODE_FNZ) { + dev->kbd_led.name = "toshiba::kbd_backlight"; + dev->kbd_led.max_brightness = 1; + dev->kbd_led.brightness_set = toshiba_kbd_backlight_set; + dev->kbd_led.brightness_get = toshiba_kbd_backlight_get; + if (!led_classdev_register(&dev->acpi_dev->dev, &dev->kbd_led)) + dev->kbd_led_registered = 1; + } + + ret = toshiba_touchpad_get(dev, &dummy); + dev->touchpad_supported = !ret; + + ret = toshiba_accelerometer_supported(dev); + dev->accelerometer_supported = !ret; + /* Determine whether or not BIOS supports fan and video interfaces */ ret = get_video_status(dev, &dummy); @@ -1220,6 +1733,14 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) ret = get_fan_status(dev, &dummy); dev->fan_supported = !ret; + ret = sysfs_create_group(&dev->acpi_dev->dev.kobj, + &toshiba_attr_group); + if (ret) { + dev->sysfs_created = 0; + goto error; + } + dev->sysfs_created = !ret; + create_toshiba_proc_entries(dev); toshiba_acpi = dev; |