diff options
-rw-r--r-- | Documentation/ABI/testing/sysfs-driver-toshiba_acpi | 93 | ||||
-rw-r--r-- | Documentation/ABI/testing/sysfs-platform-dell-laptop | 69 | ||||
-rw-r--r-- | Documentation/laptops/thinkpad-acpi.txt | 18 | ||||
-rw-r--r-- | MAINTAINERS | 22 | ||||
-rw-r--r-- | drivers/platform/x86/Kconfig | 1 | ||||
-rw-r--r-- | drivers/platform/x86/apple-gmux.c | 48 | ||||
-rw-r--r-- | drivers/platform/x86/dell-laptop.c | 1089 | ||||
-rw-r--r-- | drivers/platform/x86/intel_oaktrail.c | 2 | ||||
-rw-r--r-- | drivers/platform/x86/thinkpad_acpi.c | 320 | ||||
-rw-r--r-- | drivers/platform/x86/toshiba_acpi.c | 256 | ||||
-rw-r--r-- | drivers/platform/x86/toshiba_bluetooth.c | 133 | ||||
-rw-r--r-- | drivers/platform/x86/wmi.c | 5 |
12 files changed, 1838 insertions, 218 deletions
diff --git a/Documentation/ABI/testing/sysfs-driver-toshiba_acpi b/Documentation/ABI/testing/sysfs-driver-toshiba_acpi index ca9c71a..eed922e 100644 --- a/Documentation/ABI/testing/sysfs-driver-toshiba_acpi +++ b/Documentation/ABI/testing/sysfs-driver-toshiba_acpi @@ -8,9 +8,11 @@ Description: This file controls the keyboard backlight operation mode, valid * 0x2 -> AUTO (also called TIMER) * 0x8 -> ON * 0x10 -> OFF - Note that the kernel 3.16 onwards this file accepts all listed + Note that from kernel 3.16 onwards this file accepts all listed parameters, kernel 3.15 only accepts the first two (FN-Z and AUTO). + Also note that toggling this value on type 1 devices, requires + a reboot for changes to take effect. Users: KToshiba What: /sys/devices/LNXSYSTM:00/LNXSYBUS:00/TOS{1900,620{0,7,8}}:00/kbd_backlight_timeout @@ -67,15 +69,72 @@ Description: This file shows the current keyboard backlight type, * 2 -> Type 2, supporting modes TIMER, ON and OFF Users: KToshiba +What: /sys/devices/LNXSYSTM:00/LNXSYBUS:00/TOS{1900,620{0,7,8}}:00/usb_sleep_charge +Date: January 23, 2015 +KernelVersion: 4.0 +Contact: Azael Avalos <coproscefalo@gmail.com> +Description: This file controls the USB Sleep & Charge charging mode, which + can be: + * 0 -> Disabled (0x00) + * 1 -> Alternate (0x09) + * 2 -> Auto (0x21) + * 3 -> Typical (0x11) + Note that from kernel 4.1 onwards this file accepts all listed + values, kernel 4.0 only supports the first three. + Note that this feature only works when connected to power, if + you want to use it under battery, see the entry named + "sleep_functions_on_battery" +Users: KToshiba + +What: /sys/devices/LNXSYSTM:00/LNXSYBUS:00/TOS{1900,620{0,7,8}}:00/sleep_functions_on_battery +Date: January 23, 2015 +KernelVersion: 4.0 +Contact: Azael Avalos <coproscefalo@gmail.com> +Description: This file controls the USB Sleep Functions under battery, and + set the level at which point they will be disabled, accepted + values can be: + * 0 -> Disabled + * 1-100 -> Battery level to disable sleep functions + Currently it prints two values, the first one indicates if the + feature is enabled or disabled, while the second one shows the + current battery level set. + Note that when the value is set to disabled, the sleep function + will only work when connected to power. +Users: KToshiba + +What: /sys/devices/LNXSYSTM:00/LNXSYBUS:00/TOS{1900,620{0,7,8}}:00/usb_rapid_charge +Date: January 23, 2015 +KernelVersion: 4.0 +Contact: Azael Avalos <coproscefalo@gmail.com> +Description: This file controls the USB Rapid Charge state, which can be: + * 0 -> Disabled + * 1 -> Enabled + Note that toggling this value requires a reboot for changes to + take effect. +Users: KToshiba + +What: /sys/devices/LNXSYSTM:00/LNXSYBUS:00/TOS{1900,620{0,7,8}}:00/usb_sleep_music +Date: January 23, 2015 +KernelVersion: 4.0 +Contact: Azael Avalos <coproscefalo@gmail.com> +Description: This file controls the Sleep & Music state, which values can be: + * 0 -> Disabled + * 1 -> Enabled + Note that this feature only works when connected to power, if + you want to use it under battery, see the entry named + "sleep_functions_on_battery" +Users: KToshiba + What: /sys/devices/LNXSYSTM:00/LNXSYBUS:00/TOS{1900,620{0,7,8}}:00/version -Date: February, 2015 -KernelVersion: 3.20 +Date: February 12, 2015 +KernelVersion: 4.0 Contact: Azael Avalos <coproscefalo@gmail.com> Description: This file shows the current version of the driver +Users: KToshiba What: /sys/devices/LNXSYSTM:00/LNXSYBUS:00/TOS{1900,620{0,7,8}}:00/fan -Date: February, 2015 -KernelVersion: 3.20 +Date: February 12, 2015 +KernelVersion: 4.0 Contact: Azael Avalos <coproscefalo@gmail.com> Description: This file controls the state of the internal fan, valid values are: @@ -83,8 +142,8 @@ Description: This file controls the state of the internal fan, valid * 1 -> ON What: /sys/devices/LNXSYSTM:00/LNXSYBUS:00/TOS{1900,620{0,7,8}}:00/kbd_function_keys -Date: February, 2015 -KernelVersion: 3.20 +Date: February 12, 2015 +KernelVersion: 4.0 Contact: Azael Avalos <coproscefalo@gmail.com> Description: This file controls the Special Functions (hotkeys) operation mode, valid values are: @@ -94,21 +153,29 @@ Description: This file controls the Special Functions (hotkeys) operation and the hotkeys are accessed via FN-F{1-12}. In the "Special Functions" mode, the F{1-12} keys trigger the hotkey and the F{1-12} keys are accessed via FN-F{1-12}. + Note that toggling this value requires a reboot for changes to + take effect. +Users: KToshiba What: /sys/devices/LNXSYSTM:00/LNXSYBUS:00/TOS{1900,620{0,7,8}}:00/panel_power_on -Date: February, 2015 -KernelVersion: 3.20 +Date: February 12, 2015 +KernelVersion: 4.0 Contact: Azael Avalos <coproscefalo@gmail.com> Description: This file controls whether the laptop should turn ON whenever the LID is opened, valid values are: * 0 -> Disabled * 1 -> Enabled + Note that toggling this value requires a reboot for changes to + take effect. +Users: KToshiba What: /sys/devices/LNXSYSTM:00/LNXSYBUS:00/TOS{1900,620{0,7,8}}:00/usb_three -Date: February, 2015 -KernelVersion: 3.20 +Date: February 12, 2015 +KernelVersion: 4.0 Contact: Azael Avalos <coproscefalo@gmail.com> -Description: This file controls whether the USB 3 functionality, valid - values are: +Description: This file controls the USB 3 functionality, valid values are: * 0 -> Disabled (Acts as a regular USB 2) * 1 -> Enabled (Full USB 3 functionality) + Note that toggling this value requires a reboot for changes to + take effect. +Users: KToshiba diff --git a/Documentation/ABI/testing/sysfs-platform-dell-laptop b/Documentation/ABI/testing/sysfs-platform-dell-laptop new file mode 100644 index 0000000..8c6a0b8 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-platform-dell-laptop @@ -0,0 +1,69 @@ +What: /sys/class/leds/dell::kbd_backlight/als_enabled +Date: December 2014 +KernelVersion: 3.19 +Contact: Gabriele Mazzotta <gabriele.mzt@gmail.com>, + Pali Rohár <pali.rohar@gmail.com> +Description: + This file allows to control the automatic keyboard + illumination mode on some systems that have an ambient + light sensor. Write 1 to this file to enable the auto + mode, 0 to disable it. + +What: /sys/class/leds/dell::kbd_backlight/als_setting +Date: December 2014 +KernelVersion: 3.19 +Contact: Gabriele Mazzotta <gabriele.mzt@gmail.com>, + Pali Rohár <pali.rohar@gmail.com> +Description: + This file allows to specifiy the on/off threshold value, + as reported by the ambient light sensor. + +What: /sys/class/leds/dell::kbd_backlight/start_triggers +Date: December 2014 +KernelVersion: 3.19 +Contact: Gabriele Mazzotta <gabriele.mzt@gmail.com>, + Pali Rohár <pali.rohar@gmail.com> +Description: + This file allows to control the input triggers that + turn on the keyboard backlight illumination that is + disabled because of inactivity. + Read the file to see the triggers available. The ones + enabled are preceded by '+', those disabled by '-'. + + To enable a trigger, write its name preceded by '+' to + this file. To disable a trigger, write its name preceded + by '-' instead. + + For example, to enable the keyboard as trigger run: + echo +keyboard > /sys/class/leds/dell::kbd_backlight/start_triggers + To disable it: + echo -keyboard > /sys/class/leds/dell::kbd_backlight/start_triggers + + Note that not all the available triggers can be configured. + +What: /sys/class/leds/dell::kbd_backlight/stop_timeout +Date: December 2014 +KernelVersion: 3.19 +Contact: Gabriele Mazzotta <gabriele.mzt@gmail.com>, + Pali Rohár <pali.rohar@gmail.com> +Description: + This file allows to specify the interval after which the + keyboard illumination is disabled because of inactivity. + The timeouts are expressed in seconds, minutes, hours and + days, for which the symbols are 's', 'm', 'h' and 'd' + respectively. + + To configure the timeout, write to this file a value along + with any the above units. If no unit is specified, the value + is assumed to be expressed in seconds. + + For example, to set the timeout to 10 minutes run: + echo 10m > /sys/class/leds/dell::kbd_backlight/stop_timeout + + Note that when this file is read, the returned value might be + expressed in a different unit than the one used when the timeout + was set. + + Also note that only some timeouts are supported and that + some systems might fall back to a specific timeout in case + an invalid timeout is written to this file. diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt index fc04c14..72a150d 100644 --- a/Documentation/laptops/thinkpad-acpi.txt +++ b/Documentation/laptops/thinkpad-acpi.txt @@ -1355,6 +1355,24 @@ Sysfs notes: rfkill controller switch "tpacpi_uwb_sw": refer to Documentation/rfkill.txt for details. +Adaptive keyboard +----------------- + +sysfs device attribute: adaptive_kbd_mode + +This sysfs attribute controls the keyboard "face" that will be shown on the +Lenovo X1 Carbon 2nd gen (2014)'s adaptive keyboard. The value can be read +and set. + +1 = Home mode +2 = Web-browser mode +3 = Web-conference mode +4 = Function mode +5 = Layflat mode + +For more details about which buttons will appear depending on the mode, please +review the laptop's user guide: +http://www.lenovo.com/shop/americas/content/user_guides/x1carbon_2_ug_en.pdf Multiple Commands, Module Parameters ------------------------------------ diff --git a/MAINTAINERS b/MAINTAINERS index 62cd43e..2b38266 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3066,10 +3066,16 @@ F: drivers/net/fddi/defxx.* DELL LAPTOP DRIVER M: Matthew Garrett <mjg59@srcf.ucam.org> +M: Pali Rohár <pali.rohar@gmail.com> L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/dell-laptop.c +DELL LAPTOP FREEFALL DRIVER +M: Pali Rohár <pali.rohar@gmail.com> +S: Maintained +F: drivers/platform/x86/dell-smo8800.c + DELL LAPTOP SMM DRIVER M: Guenter Roeck <linux@roeck-us.net> S: Maintained @@ -3084,6 +3090,7 @@ F: drivers/firmware/dcdbas.* DELL WMI EXTRAS DRIVER M: Matthew Garrett <mjg59@srcf.ucam.org> +M: Pali Rohár <pali.rohar@gmail.com> S: Maintained F: drivers/platform/x86/dell-wmi.c @@ -9949,10 +9956,23 @@ S: Maintained F: drivers/platform/x86/topstar-laptop.c TOSHIBA ACPI EXTRAS DRIVER +M: Azael Avalos <coproscefalo@gmail.com> L: platform-driver-x86@vger.kernel.org -S: Orphan +S: Maintained F: drivers/platform/x86/toshiba_acpi.c +TOSHIBA BLUETOOTH DRIVER +M: Azael Avalos <coproscefalo@gmail.com> +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/toshiba_bluetooth.c + +TOSHIBA HDD ACTIVE PROTECTION SENSOR DRIVER +M: Azael Avalos <coproscefalo@gmail.com> +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/toshiba_haps.c + TOSHIBA SMM DRIVER M: Jonathan Buzzard <jonathan@buzzard.org.uk> L: tlinux-users@tce.toshiba-dme.co.jp diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 9752761..f9f205c 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -614,6 +614,7 @@ config ACPI_TOSHIBA depends on INPUT depends on RFKILL || RFKILL = n depends on SERIO_I8042 || SERIO_I8042 = n + depends on ACPI_VIDEO || ACPI_VIDEO = n select INPUT_POLLDEV select INPUT_SPARSEKMAP ---help--- diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c index 66d6d22..6808715 100644 --- a/drivers/platform/x86/apple-gmux.c +++ b/drivers/platform/x86/apple-gmux.c @@ -22,6 +22,7 @@ #include <linux/delay.h> #include <linux/pci.h> #include <linux/vga_switcheroo.h> +#include <linux/vgaarb.h> #include <acpi/video.h> #include <asm/io.h> @@ -31,6 +32,7 @@ struct apple_gmux_data { bool indexed; struct mutex index_lock; + struct pci_dev *pdev; struct backlight_device *bdev; /* switcheroo data */ @@ -415,6 +417,23 @@ static int gmux_resume(struct device *dev) return 0; } +static struct pci_dev *gmux_get_io_pdev(void) +{ + struct pci_dev *pdev = NULL; + + while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev))) { + u16 cmd; + + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (!(cmd & PCI_COMMAND_IO)) + continue; + + return pdev; + } + + return NULL; +} + static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) { struct apple_gmux_data *gmux_data; @@ -425,6 +444,7 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) int ret = -ENXIO; acpi_status status; unsigned long long gpe; + struct pci_dev *pdev = NULL; if (apple_gmux_data) return -EBUSY; @@ -475,7 +495,7 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) ver_minor = (version >> 16) & 0xff; ver_release = (version >> 8) & 0xff; } else { - pr_info("gmux device not present\n"); + pr_info("gmux device not present or IO disabled\n"); ret = -ENODEV; goto err_release; } @@ -483,6 +503,23 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) pr_info("Found gmux version %d.%d.%d [%s]\n", ver_major, ver_minor, ver_release, (gmux_data->indexed ? "indexed" : "classic")); + /* + * Apple systems with gmux are EFI based and normally don't use + * VGA. In addition changing IO+MEM ownership between IGP and dGPU + * disables IO/MEM used for backlight control on some systems. + * Lock IO+MEM to GPU with active IO to prevent switch. + */ + pdev = gmux_get_io_pdev(); + if (pdev && vga_tryget(pdev, + VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM)) { + pr_err("IO+MEM vgaarb-locking for PCI:%s failed\n", + pci_name(pdev)); + ret = -EBUSY; + goto err_release; + } else if (pdev) + pr_info("locked IO for PCI:%s\n", pci_name(pdev)); + gmux_data->pdev = pdev; + memset(&props, 0, sizeof(props)); props.type = BACKLIGHT_PLATFORM; props.max_brightness = gmux_read32(gmux_data, GMUX_PORT_MAX_BRIGHTNESS); @@ -574,6 +611,10 @@ err_enable_gpe: err_notify: backlight_device_unregister(bdev); err_release: + if (gmux_data->pdev) + vga_put(gmux_data->pdev, + VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM); + pci_dev_put(pdev); release_region(gmux_data->iostart, gmux_data->iolen); err_free: kfree(gmux_data); @@ -593,6 +634,11 @@ static void gmux_remove(struct pnp_dev *pnp) &gmux_notify_handler); } + if (gmux_data->pdev) { + vga_put(gmux_data->pdev, + VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM); + pci_dev_put(gmux_data->pdev); + } backlight_device_unregister(gmux_data->bdev); release_region(gmux_data->iostart, gmux_data->iolen); diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index 3d21efe..d688d80 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c @@ -2,9 +2,11 @@ * Driver for Dell laptop extras * * Copyright (c) Red Hat <mjg@redhat.com> + * Copyright (c) 2014 Gabriele Mazzotta <gabriele.mzt@gmail.com> + * Copyright (c) 2014 Pali Rohár <pali.rohar@gmail.com> * - * Based on documentation in the libsmbios package, Copyright (C) 2005 Dell - * Inc. + * Based on documentation in the libsmbios package: + * Copyright (C) 2005-2014 Dell Inc. * * 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 @@ -32,6 +34,13 @@ #include "../../firmware/dcdbas.h" #define BRIGHTNESS_TOKEN 0x7d +#define KBD_LED_OFF_TOKEN 0x01E1 +#define KBD_LED_ON_TOKEN 0x01E2 +#define KBD_LED_AUTO_TOKEN 0x01E3 +#define KBD_LED_AUTO_25_TOKEN 0x02EA +#define KBD_LED_AUTO_50_TOKEN 0x02EB +#define KBD_LED_AUTO_75_TOKEN 0x02EC +#define KBD_LED_AUTO_100_TOKEN 0x02F6 /* This structure will be modified by the firmware when we enter * system management mode, hence the volatiles */ @@ -62,6 +71,13 @@ struct calling_interface_structure { struct quirk_entry { u8 touchpad_led; + + int needs_kbd_timeouts; + /* + * Ordered list of timeouts expressed in seconds. + * The list must end with -1 + */ + int kbd_timeouts[]; }; static struct quirk_entry *quirks; @@ -76,6 +92,15 @@ static int __init dmi_matched(const struct dmi_system_id *dmi) return 1; } +/* + * These values come from Windows utility provided by Dell. If any other value + * is used then BIOS silently set timeout to 0 without any error message. + */ +static struct quirk_entry quirk_dell_xps13_9333 = { + .needs_kbd_timeouts = 1, + .kbd_timeouts = { 0, 5, 15, 60, 5 * 60, 15 * 60, -1 }, +}; + static int da_command_address; static int da_command_code; static int da_num_tokens; @@ -267,6 +292,15 @@ static const struct dmi_system_id dell_quirks[] __initconst = { }, .driver_data = &quirk_dell_vostro_v130, }, + { + .callback = dmi_matched, + .ident = "Dell XPS13 9333", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "XPS13 9333"), + }, + .driver_data = &quirk_dell_xps13_9333, + }, { } }; @@ -331,17 +365,29 @@ static void __init find_tokens(const struct dmi_header *dm, void *dummy) } } -static int find_token_location(int tokenid) +static int find_token_id(int tokenid) { int i; + for (i = 0; i < da_num_tokens; i++) { if (da_tokens[i].tokenID == tokenid) - return da_tokens[i].location; + return i; } return -1; } +static int find_token_location(int tokenid) +{ + int id; + + id = find_token_id(tokenid); + if (id == -1) + return -1; + + return da_tokens[id].location; +} + static struct calling_interface_buffer * dell_send_request(struct calling_interface_buffer *buffer, int class, int select) @@ -362,6 +408,20 @@ dell_send_request(struct calling_interface_buffer *buffer, int class, return buffer; } +static inline int dell_smi_error(int value) +{ + switch (value) { + case 0: /* Completed successfully */ + return 0; + case -1: /* Completed with error */ + return -EIO; + case -2: /* Function not supported */ + return -ENXIO; + default: /* Unknown error */ + return -EINVAL; + } +} + /* Derived from information in DellWirelessCtl.cpp: Class 17, select 11 is radio control. It returns an array of 32-bit values. @@ -716,7 +776,7 @@ static int dell_send_intensity(struct backlight_device *bd) else dell_send_request(buffer, 1, 1); -out: + out: release_buffer(); return ret; } @@ -740,7 +800,7 @@ static int dell_get_intensity(struct backlight_device *bd) ret = buffer->output[1]; -out: + out: release_buffer(); return ret; } @@ -789,6 +849,1018 @@ static void touchpad_led_exit(void) led_classdev_unregister(&touchpad_led); } +/* + * Derived from information in smbios-keyboard-ctl: + * + * cbClass 4 + * cbSelect 11 + * Keyboard illumination + * cbArg1 determines the function to be performed + * + * cbArg1 0x0 = Get Feature Information + * cbRES1 Standard return codes (0, -1, -2) + * cbRES2, word0 Bitmap of user-selectable modes + * bit 0 Always off (All systems) + * bit 1 Always on (Travis ATG, Siberia) + * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) + * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off + * bit 4 Auto: Input-activity-based On; input-activity based Off + * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off + * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off + * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off + * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off + * bits 9-15 Reserved for future use + * cbRES2, byte2 Reserved for future use + * cbRES2, byte3 Keyboard illumination type + * 0 Reserved + * 1 Tasklight + * 2 Backlight + * 3-255 Reserved for future use + * cbRES3, byte0 Supported auto keyboard illumination trigger bitmap. + * bit 0 Any keystroke + * bit 1 Touchpad activity + * bit 2 Pointing stick + * bit 3 Any mouse + * bits 4-7 Reserved for future use + * cbRES3, byte1 Supported timeout unit bitmap + * bit 0 Seconds + * bit 1 Minutes + * bit 2 Hours + * bit 3 Days + * bits 4-7 Reserved for future use + * cbRES3, byte2 Number of keyboard light brightness levels + * cbRES4, byte0 Maximum acceptable seconds value (0 if seconds not supported). + * cbRES4, byte1 Maximum acceptable minutes value (0 if minutes not supported). + * cbRES4, byte2 Maximum acceptable hours value (0 if hours not supported). + * cbRES4, byte3 Maximum acceptable days value (0 if days not supported) + * + * cbArg1 0x1 = Get Current State + * cbRES1 Standard return codes (0, -1, -2) + * cbRES2, word0 Bitmap of current mode state + * bit 0 Always off (All systems) + * bit 1 Always on (Travis ATG, Siberia) + * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) + * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off + * bit 4 Auto: Input-activity-based On; input-activity based Off + * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off + * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off + * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off + * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off + * bits 9-15 Reserved for future use + * Note: Only One bit can be set + * cbRES2, byte2 Currently active auto keyboard illumination triggers. + * bit 0 Any keystroke + * bit 1 Touchpad activity + * bit 2 Pointing stick + * bit 3 Any mouse + * bits 4-7 Reserved for future use + * cbRES2, byte3 Current Timeout + * bits 7:6 Timeout units indicator: + * 00b Seconds + * 01b Minutes + * 10b Hours + * 11b Days + * bits 5:0 Timeout value (0-63) in sec/min/hr/day + * NOTE: A value of 0 means always on (no timeout) if any bits of RES3 byte + * are set upon return from the [Get feature information] call. + * cbRES3, byte0 Current setting of ALS value that turns the light on or off. + * cbRES3, byte1 Current ALS reading + * cbRES3, byte2 Current keyboard light level. + * + * cbArg1 0x2 = Set New State + * cbRES1 Standard return codes (0, -1, -2) + * cbArg2, word0 Bitmap of current mode state + * bit 0 Always off (All systems) + * bit 1 Always on (Travis ATG, Siberia) + * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) + * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off + * bit 4 Auto: Input-activity-based On; input-activity based Off + * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off + * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off + * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off + * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off + * bits 9-15 Reserved for future use + * Note: Only One bit can be set + * cbArg2, byte2 Desired auto keyboard illumination triggers. Must remain inactive to allow + * keyboard to turn off automatically. + * bit 0 Any keystroke + * bit 1 Touchpad activity + * bit 2 Pointing stick + * bit 3 Any mouse + * bits 4-7 Reserved for future use + * cbArg2, byte3 Desired Timeout + * bits 7:6 Timeout units indicator: + * 00b Seconds + * 01b Minutes + * 10b Hours + * 11b Days + * bits 5:0 Timeout value (0-63) in sec/min/hr/day + * cbArg3, byte0 Desired setting of ALS value that turns the light on or off. + * cbArg3, byte2 Desired keyboard light level. + */ + + +enum kbd_timeout_unit { + KBD_TIMEOUT_SECONDS = 0, + KBD_TIMEOUT_MINUTES, + KBD_TIMEOUT_HOURS, + KBD_TIMEOUT_DAYS, +}; + +enum kbd_mode_bit { + KBD_MODE_BIT_OFF = 0, + KBD_MODE_BIT_ON, + KBD_MODE_BIT_ALS, + KBD_MODE_BIT_TRIGGER_ALS, + KBD_MODE_BIT_TRIGGER, + KBD_MODE_BIT_TRIGGER_25, + KBD_MODE_BIT_TRIGGER_50, + KBD_MODE_BIT_TRIGGER_75, + KBD_MODE_BIT_TRIGGER_100, +}; + +#define kbd_is_als_mode_bit(bit) \ + ((bit) == KBD_MODE_BIT_ALS || (bit) == KBD_MODE_BIT_TRIGGER_ALS) +#define kbd_is_trigger_mode_bit(bit) \ + ((bit) >= KBD_MODE_BIT_TRIGGER_ALS && (bit) <= KBD_MODE_BIT_TRIGGER_100) +#define kbd_is_level_mode_bit(bit) \ + ((bit) >= KBD_MODE_BIT_TRIGGER_25 && (bit) <= KBD_MODE_BIT_TRIGGER_100) + +struct kbd_info { + u16 modes; + u8 type; + u8 triggers; + u8 levels; + u8 seconds; + u8 minutes; + u8 hours; + u8 days; +}; + +struct kbd_state { + u8 mode_bit; + u8 triggers; + u8 timeout_value; + u8 timeout_unit; + u8 als_setting; + u8 als_value; + u8 level; +}; + +static const int kbd_tokens[] = { + KBD_LED_OFF_TOKEN, + KBD_LED_AUTO_25_TOKEN, + KBD_LED_AUTO_50_TOKEN, + KBD_LED_AUTO_75_TOKEN, + KBD_LED_AUTO_100_TOKEN, + KBD_LED_ON_TOKEN, +}; + +static u16 kbd_token_bits; + +static struct kbd_info kbd_info; +static bool kbd_als_supported; +static bool kbd_triggers_supported; + +static u8 kbd_mode_levels[16]; +static int kbd_mode_levels_count; + +static u8 kbd_previous_level; +static u8 kbd_previous_mode_bit; + +static bool kbd_led_present; + +/* + * NOTE: there are three ways to set the keyboard backlight level. + * First, via kbd_state.mode_bit (assigning KBD_MODE_BIT_TRIGGER_* value). + * Second, via kbd_state.level (assigning numerical value <= kbd_info.levels). + * Third, via SMBIOS tokens (KBD_LED_* in kbd_tokens) + * + * There are laptops which support only one of these methods. If we want to + * support as many machines as possible we need to implement all three methods. + * The first two methods use the kbd_state structure. The third uses SMBIOS + * tokens. If kbd_info.levels == 0, the machine does not support setting the + * keyboard backlight level via kbd_state.level. + */ + +static int kbd_get_info(struct kbd_info *info) +{ + u8 units; + int ret; + + get_buffer(); + + buffer->input[0] = 0x0; + dell_send_request(buffer, 4, 11); + ret = buffer->output[0]; + + if (ret) { + ret = dell_smi_error(ret); + goto out; + } + + info->modes = buffer->output[1] & 0xFFFF; + info->type = (buffer->output[1] >> 24) & 0xFF; + info->triggers = buffer->output[2] & 0xFF; + units = (buffer->output[2] >> 8) & 0xFF; + info->levels = (buffer->output[2] >> 16) & 0xFF; + + if (units & BIT(0)) + info->seconds = (buffer->output[3] >> 0) & 0xFF; + if (units & BIT(1)) + info->minutes = (buffer->output[3] >> 8) & 0xFF; + if (units & BIT(2)) + info->hours = (buffer->output[3] >> 16) & 0xFF; + if (units & BIT(3)) + info->days = (buffer->output[3] >> 24) & 0xFF; + + out: + release_buffer(); + return ret; +} + +static unsigned int kbd_get_max_level(void) +{ + if (kbd_info.levels != 0) + return kbd_info.levels; + if (kbd_mode_levels_count > 0) + return kbd_mode_levels_count - 1; + return 0; +} + +static int kbd_get_level(struct kbd_state *state) +{ + int i; + + if (kbd_info.levels != 0) + return state->level; + + if (kbd_mode_levels_count > 0) { + for (i = 0; i < kbd_mode_levels_count; ++i) + if (kbd_mode_levels[i] == state->mode_bit) + return i; + return 0; + } + + return -EINVAL; +} + +static int kbd_set_level(struct kbd_state *state, u8 level) +{ + if (kbd_info.levels != 0) { + if (level != 0) + kbd_previous_level = level; + if (state->level == level) + return 0; + state->level = level; + if (level != 0 && state->mode_bit == KBD_MODE_BIT_OFF) + state->mode_bit = kbd_previous_mode_bit; + else if (level == 0 && state->mode_bit != KBD_MODE_BIT_OFF) { + kbd_previous_mode_bit = state->mode_bit; + state->mode_bit = KBD_MODE_BIT_OFF; + } + return 0; + } + + if (kbd_mode_levels_count > 0 && level < kbd_mode_levels_count) { + if (level != 0) + kbd_previous_level = level; + state->mode_bit = kbd_mode_levels[level]; + return 0; + } + + return -EINVAL; +} + +static int kbd_get_state(struct kbd_state *state) +{ + int ret; + + get_buffer(); + + buffer->input[0] = 0x1; + dell_send_request(buffer, 4, 11); + ret = buffer->output[0]; + + if (ret) { + ret = dell_smi_error(ret); + goto out; + } + + state->mode_bit = ffs(buffer->output[1] & 0xFFFF); + if (state->mode_bit != 0) + state->mode_bit--; + + state->triggers = (buffer->output[1] >> 16) & 0xFF; + state->timeout_value = (buffer->output[1] >> 24) & 0x3F; + state->timeout_unit = (buffer->output[1] >> 30) & 0x3; + state->als_setting = buffer->output[2] & 0xFF; + state->als_value = (buffer->output[2] >> 8) & 0xFF; + state->level = (buffer->output[2] >> 16) & 0xFF; + + out: + release_buffer(); + return ret; +} + +static int kbd_set_state(struct kbd_state *state) +{ + int ret; + + get_buffer(); + buffer->input[0] = 0x2; + buffer->input[1] = BIT(state->mode_bit) & 0xFFFF; + buffer->input[1] |= (state->triggers & 0xFF) << 16; + buffer->input[1] |= (state->timeout_value & 0x3F) << 24; + buffer->input[1] |= (state->timeout_unit & 0x3) << 30; + buffer->input[2] = state->als_setting & 0xFF; + buffer->input[2] |= (state->level & 0xFF) << 16; + dell_send_request(buffer, 4, 11); + ret = buffer->output[0]; + release_buffer(); + + return dell_smi_error(ret); +} + +static int kbd_set_state_safe(struct kbd_state *state, struct kbd_state *old) +{ + int ret; + + ret = kbd_set_state(state); + if (ret == 0) + return 0; + + /* + * When setting the new state fails,try to restore the previous one. + * This is needed on some machines where BIOS sets a default state when + * setting a new state fails. This default state could be all off. + */ + + if (kbd_set_state(old)) + pr_err("Setting old previous keyboard state failed\n"); + + return ret; +} + +static int kbd_set_token_bit(u8 bit) +{ + int id; + int ret; + + if (bit >= ARRAY_SIZE(kbd_tokens)) + return -EINVAL; + + id = find_token_id(kbd_tokens[bit]); + if (id == -1) + return -EINVAL; + + get_buffer(); + buffer->input[0] = da_tokens[id].location; + buffer->input[1] = da_tokens[id].value; + dell_send_request(buffer, 1, 0); + ret = buffer->output[0]; + release_buffer(); + + return dell_smi_error(ret); +} + +static int kbd_get_token_bit(u8 bit) +{ + int id; + int ret; + int val; + + if (bit >= ARRAY_SIZE(kbd_tokens)) + return -EINVAL; + + id = find_token_id(kbd_tokens[bit]); + if (id == -1) + return -EINVAL; + + get_buffer(); + buffer->input[0] = da_tokens[id].location; + dell_send_request(buffer, 0, 0); + ret = buffer->output[0]; + val = buffer->output[1]; + release_buffer(); + + if (ret) + return dell_smi_error(ret); + + return (val == da_tokens[id].value); +} + +static int kbd_get_first_active_token_bit(void) +{ + int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(kbd_tokens); ++i) { + ret = kbd_get_token_bit(i); + if (ret == 1) + return i; + } + + return ret; +} + +static int kbd_get_valid_token_counts(void) +{ + return hweight16(kbd_token_bits); +} + +static inline int kbd_init_info(void) +{ + struct kbd_state state; + int ret; + int i; + + ret = kbd_get_info(&kbd_info); + if (ret) + return ret; + + kbd_get_state(&state); + + /* NOTE: timeout value is stored in 6 bits so max value is 63 */ + if (kbd_info.seconds > 63) + kbd_info.seconds = 63; + if (kbd_info.minutes > 63) + kbd_info.minutes = 63; + if (kbd_info.hours > 63) + kbd_info.hours = 63; + if (kbd_info.days > 63) + kbd_info.days = 63; + + /* NOTE: On tested machines ON mode did not work and caused + * problems (turned backlight off) so do not use it + */ + kbd_info.modes &= ~BIT(KBD_MODE_BIT_ON); + + kbd_previous_level = kbd_get_level(&state); + kbd_previous_mode_bit = state.mode_bit; + + if (kbd_previous_level == 0 && kbd_get_max_level() != 0) + kbd_previous_level = 1; + + if (kbd_previous_mode_bit == KBD_MODE_BIT_OFF) { + kbd_previous_mode_bit = + ffs(kbd_info.modes & ~BIT(KBD_MODE_BIT_OFF)); + if (kbd_previous_mode_bit != 0) + kbd_previous_mode_bit--; + } + + if (kbd_info.modes & (BIT(KBD_MODE_BIT_ALS) | + BIT(KBD_MODE_BIT_TRIGGER_ALS))) + kbd_als_supported = true; + + if (kbd_info.modes & ( + BIT(KBD_MODE_BIT_TRIGGER_ALS) | BIT(KBD_MODE_BIT_TRIGGER) | + BIT(KBD_MODE_BIT_TRIGGER_25) | BIT(KBD_MODE_BIT_TRIGGER_50) | + BIT(KBD_MODE_BIT_TRIGGER_75) | BIT(KBD_MODE_BIT_TRIGGER_100) + )) + kbd_triggers_supported = true; + + /* kbd_mode_levels[0] is reserved, see below */ + for (i = 0; i < 16; ++i) + if (kbd_is_level_mode_bit(i) && (BIT(i) & kbd_info.modes)) + kbd_mode_levels[1 + kbd_mode_levels_count++] = i; + + /* + * Find the first supported mode and assign to kbd_mode_levels[0]. + * This should be 0 (off), but we cannot depend on the BIOS to + * support 0. + */ + if (kbd_mode_levels_count > 0) { + for (i = 0; i < 16; ++i) { + if (BIT(i) & kbd_info.modes) { + kbd_mode_levels[0] = i; + break; + } + } + kbd_mode_levels_count++; + } + + return 0; + +} + +static inline void kbd_init_tokens(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(kbd_tokens); ++i) + if (find_token_id(kbd_tokens[i]) != -1) + kbd_token_bits |= BIT(i); +} + +static void kbd_init(void) +{ + int ret; + + ret = kbd_init_info(); + kbd_init_tokens(); + + if (kbd_token_bits != 0 || ret == 0) + kbd_led_present = true; +} + +static ssize_t kbd_led_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kbd_state new_state; + struct kbd_state state; + bool convert; + int value; + int ret; + char ch; + u8 unit; + int i; + + ret = sscanf(buf, "%d %c", &value, &ch); + if (ret < 1) + return -EINVAL; + else if (ret == 1) + ch = 's'; + + if (value < 0) + return -EINVAL; + + convert = false; + + switch (ch) { + case 's': + if (value > kbd_info.seconds) + convert = true; + unit = KBD_TIMEOUT_SECONDS; + break; + case 'm': + if (value > kbd_info.minutes) + convert = true; + unit = KBD_TIMEOUT_MINUTES; + break; + case 'h': + if (value > kbd_info.hours) + convert = true; + unit = KBD_TIMEOUT_HOURS; + break; + case 'd': + if (value > kbd_info.days) + convert = true; + unit = KBD_TIMEOUT_DAYS; + break; + default: + return -EINVAL; + } + + if (quirks && quirks->needs_kbd_timeouts) + convert = true; + + if (convert) { + /* Convert value from current units to seconds */ + switch (unit) { + case KBD_TIMEOUT_DAYS: + value *= 24; + case KBD_TIMEOUT_HOURS: + value *= 60; + case KBD_TIMEOUT_MINUTES: + value *= 60; + unit = KBD_TIMEOUT_SECONDS; + } + + if (quirks && quirks->needs_kbd_timeouts) { + for (i = 0; quirks->kbd_timeouts[i] != -1; i++) { + if (value <= quirks->kbd_timeouts[i]) { + value = quirks->kbd_timeouts[i]; + break; + } + } + } + + if (value <= kbd_info.seconds && kbd_info.seconds) { + unit = KBD_TIMEOUT_SECONDS; + } else if (value / 60 <= kbd_info.minutes && kbd_info.minutes) { + value /= 60; + unit = KBD_TIMEOUT_MINUTES; + } else if (value / (60 * 60) <= kbd_info.hours && kbd_info.hours) { + value /= (60 * 60); + unit = KBD_TIMEOUT_HOURS; + } else if (value / (60 * 60 * 24) <= kbd_info.days && kbd_info.days) { + value /= (60 * 60 * 24); + unit = KBD_TIMEOUT_DAYS; + } else { + return -EINVAL; + } + } + + ret = kbd_get_state(&state); + if (ret) + return ret; + + new_state = state; + new_state.timeout_value = value; + new_state.timeout_unit = unit; + + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + return ret; + + return count; +} + +static ssize_t kbd_led_timeout_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kbd_state state; + int ret; + int len; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + len = sprintf(buf, "%d", state.timeout_value); + + switch (state.timeout_unit) { + case KBD_TIMEOUT_SECONDS: + return len + sprintf(buf+len, "s\n"); + case KBD_TIMEOUT_MINUTES: + return len + sprintf(buf+len, "m\n"); + case KBD_TIMEOUT_HOURS: + return len + sprintf(buf+len, "h\n"); + case KBD_TIMEOUT_DAYS: + return len + sprintf(buf+len, "d\n"); + default: + return -EINVAL; + } + + return len; +} + +static DEVICE_ATTR(stop_timeout, S_IRUGO | S_IWUSR, + kbd_led_timeout_show, kbd_led_timeout_store); + +static const char * const kbd_led_triggers[] = { + "keyboard", + "touchpad", + /*"trackstick"*/ NULL, /* NOTE: trackstick is just alias for touchpad */ + "mouse", +}; + +static ssize_t kbd_led_triggers_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kbd_state new_state; + struct kbd_state state; + bool triggers_enabled = false; + int trigger_bit = -1; + char trigger[21]; + int i, ret; + + ret = sscanf(buf, "%20s", trigger); + if (ret != 1) + return -EINVAL; + + if (trigger[0] != '+' && trigger[0] != '-') + return -EINVAL; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + if (kbd_triggers_supported) + triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); + + if (kbd_triggers_supported) { + for (i = 0; i < ARRAY_SIZE(kbd_led_triggers); ++i) { + if (!(kbd_info.triggers & BIT(i))) + continue; + if (!kbd_led_triggers[i]) + continue; + if (strcmp(trigger+1, kbd_led_triggers[i]) != 0) + continue; + if (trigger[0] == '+' && + triggers_enabled && (state.triggers & BIT(i))) + return count; + if (trigger[0] == '-' && + (!triggers_enabled || !(state.triggers & BIT(i)))) + return count; + trigger_bit = i; + break; + } + } + + if (trigger_bit != -1) { + new_state = state; + if (trigger[0] == '+') + new_state.triggers |= BIT(trigger_bit); + else { + new_state.triggers &= ~BIT(trigger_bit); + /* NOTE: trackstick bit (2) must be disabled when + * disabling touchpad bit (1), otherwise touchpad + * bit (1) will not be disabled */ + if (trigger_bit == 1) + new_state.triggers &= ~BIT(2); + } + if ((kbd_info.triggers & new_state.triggers) != + new_state.triggers) + return -EINVAL; + if (new_state.triggers && !triggers_enabled) { + new_state.mode_bit = KBD_MODE_BIT_TRIGGER; + kbd_set_level(&new_state, kbd_previous_level); + } else if (new_state.triggers == 0) { + kbd_set_level(&new_state, 0); + } + if (!(kbd_info.modes & BIT(new_state.mode_bit))) + return -EINVAL; + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + return ret; + if (new_state.mode_bit != KBD_MODE_BIT_OFF) + kbd_previous_mode_bit = new_state.mode_bit; + return count; + } + + return -EINVAL; +} + +static ssize_t kbd_led_triggers_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kbd_state state; + bool triggers_enabled; + int level, i, ret; + int len = 0; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + len = 0; + + if (kbd_triggers_supported) { + triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); + level = kbd_get_level(&state); + for (i = 0; i < ARRAY_SIZE(kbd_led_triggers); ++i) { + if (!(kbd_info.triggers & BIT(i))) + continue; + if (!kbd_led_triggers[i]) + continue; + if ((triggers_enabled || level <= 0) && + (state.triggers & BIT(i))) + buf[len++] = '+'; + else + buf[len++] = '-'; + len += sprintf(buf+len, "%s ", kbd_led_triggers[i]); + } + } + + if (len) + buf[len - 1] = '\n'; + + return len; +} + +static DEVICE_ATTR(start_triggers, S_IRUGO | S_IWUSR, + kbd_led_triggers_show, kbd_led_triggers_store); + +static ssize_t kbd_led_als_enabled_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kbd_state new_state; + struct kbd_state state; + bool triggers_enabled = false; + int enable; + int ret; + + ret = kstrtoint(buf, 0, &enable); + if (ret) + return ret; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + if (enable == kbd_is_als_mode_bit(state.mode_bit)) + return count; + + new_state = state; + + if (kbd_triggers_supported) + triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); + + if (enable) { + if (triggers_enabled) + new_state.mode_bit = KBD_MODE_BIT_TRIGGER_ALS; + else + new_state.mode_bit = KBD_MODE_BIT_ALS; + } else { + if (triggers_enabled) { + new_state.mode_bit = KBD_MODE_BIT_TRIGGER; + kbd_set_level(&new_state, kbd_previous_level); + } else { + new_state.mode_bit = KBD_MODE_BIT_ON; + } + } + if (!(kbd_info.modes & BIT(new_state.mode_bit))) + return -EINVAL; + + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + return ret; + kbd_previous_mode_bit = new_state.mode_bit; + + return count; +} + +static ssize_t kbd_led_als_enabled_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct kbd_state state; + bool enabled = false; + int ret; + + ret = kbd_get_state(&state); + if (ret) + return ret; + enabled = kbd_is_als_mode_bit(state.mode_bit); + + return sprintf(buf, "%d\n", enabled ? 1 : 0); +} + +static DEVICE_ATTR(als_enabled, S_IRUGO | S_IWUSR, + kbd_led_als_enabled_show, kbd_led_als_enabled_store); + +static ssize_t kbd_led_als_setting_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kbd_state state; + struct kbd_state new_state; + u8 setting; + int ret; + + ret = kstrtou8(buf, 10, &setting); + if (ret) + return ret; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + new_state = state; + new_state.als_setting = setting; + + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + return ret; + + return count; +} + +static ssize_t kbd_led_als_setting_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct kbd_state state; + int ret; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + return sprintf(buf, "%d\n", state.als_setting); +} + +static DEVICE_ATTR(als_setting, S_IRUGO | S_IWUSR, + kbd_led_als_setting_show, kbd_led_als_setting_store); + +static struct attribute *kbd_led_attrs[] = { + &dev_attr_stop_timeout.attr, + &dev_attr_start_triggers.attr, + NULL, +}; + +static const struct attribute_group kbd_led_group = { + .attrs = kbd_led_attrs, +}; + +static struct attribute *kbd_led_als_attrs[] = { + &dev_attr_als_enabled.attr, + &dev_attr_als_setting.attr, + NULL, +}; + +static const struct attribute_group kbd_led_als_group = { + .attrs = kbd_led_als_attrs, +}; + +static const struct attribute_group *kbd_led_groups[] = { + &kbd_led_group, + &kbd_led_als_group, + NULL, +}; + +static enum led_brightness kbd_led_level_get(struct led_classdev *led_cdev) +{ + int ret; + u16 num; + struct kbd_state state; + + if (kbd_get_max_level()) { + ret = kbd_get_state(&state); + if (ret) + return 0; + ret = kbd_get_level(&state); + if (ret < 0) + return 0; + return ret; + } + + if (kbd_get_valid_token_counts()) { + ret = kbd_get_first_active_token_bit(); + if (ret < 0) + return 0; + for (num = kbd_token_bits; num != 0 && ret > 0; --ret) + num &= num - 1; /* clear the first bit set */ + if (num == 0) + return 0; + return ffs(num) - 1; + } + + pr_warn("Keyboard brightness level control not supported\n"); + return 0; +} + +static void kbd_led_level_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct kbd_state state; + struct kbd_state new_state; + u16 num; + + if (kbd_get_max_level()) { + if (kbd_get_state(&state)) + return; + new_state = state; + if (kbd_set_level(&new_state, value)) + return; + kbd_set_state_safe(&new_state, &state); + return; + } + + if (kbd_get_valid_token_counts()) { + for (num = kbd_token_bits; num != 0 && value > 0; --value) + num &= num - 1; /* clear the first bit set */ + if (num == 0) + return; + kbd_set_token_bit(ffs(num) - 1); + return; + } + + pr_warn("Keyboard brightness level control not supported\n"); +} + +static struct led_classdev kbd_led = { + .name = "dell::kbd_backlight", + .brightness_set = kbd_led_level_set, + .brightness_get = kbd_led_level_get, + .groups = kbd_led_groups, +}; + +static int __init kbd_led_init(struct device *dev) +{ + kbd_init(); + if (!kbd_led_present) + return -ENODEV; + if (!kbd_als_supported) + kbd_led_groups[1] = NULL; + kbd_led.max_brightness = kbd_get_max_level(); + if (!kbd_led.max_brightness) { + kbd_led.max_brightness = kbd_get_valid_token_counts(); + if (kbd_led.max_brightness) + kbd_led.max_brightness--; + } + return led_classdev_register(dev, &kbd_led); +} + +static void brightness_set_exit(struct led_classdev *led_cdev, + enum led_brightness value) +{ + /* Don't change backlight level on exit */ +}; + +static void kbd_led_exit(void) +{ + if (!kbd_led_present) + return; + kbd_led.brightness_set = brightness_set_exit; + led_classdev_unregister(&kbd_led); +} + static int __init dell_init(void) { int max_intensity = 0; @@ -841,6 +1913,8 @@ static int __init dell_init(void) if (quirks && quirks->touchpad_led) touchpad_led_init(&platform_device->dev); + kbd_led_init(&platform_device->dev); + dell_laptop_dir = debugfs_create_dir("dell_laptop", NULL); if (dell_laptop_dir != NULL) debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL, @@ -908,6 +1982,7 @@ static void __exit dell_exit(void) debugfs_remove_recursive(dell_laptop_dir); if (quirks && quirks->touchpad_led) touchpad_led_exit(); + kbd_led_exit(); i8042_remove_filter(dell_laptop_i8042_filter); cancel_delayed_work_sync(&dell_rfkill_work); backlight_device_unregister(dell_backlight_device); @@ -924,5 +1999,7 @@ module_init(dell_init); module_exit(dell_exit); MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); +MODULE_AUTHOR("Gabriele Mazzotta <gabriele.mzt@gmail.com>"); +MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>"); MODULE_DESCRIPTION("Dell laptop driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/intel_oaktrail.c b/drivers/platform/x86/intel_oaktrail.c index a4a4258..8037c8b 100644 --- a/drivers/platform/x86/intel_oaktrail.c +++ b/drivers/platform/x86/intel_oaktrail.c @@ -62,7 +62,7 @@ * (1 << 1): Bluetooth enable/disable, RW. * (1 << 2): GPS enable/disable, RW. * (1 << 3): WiFi enable/disable, RW. - * (1 << 4): WWAN (3G) enable/disalbe, RW. + * (1 << 4): WWAN (3G) enable/disable, RW. * (1 << 5): Touchscreen enable/disable, Read Only. */ #define OT_EC_DEVICE_STATE_ADDRESS 0xD6 diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 3b8ceee..7769575 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -319,6 +319,7 @@ static struct { u32 sensors_pdrv_attrs_registered:1; u32 sensors_pdev_attrs_registered:1; u32 hotkey_poll_active:1; + u32 has_adaptive_kbd:1; } tp_features; static struct { @@ -1911,6 +1912,27 @@ enum { /* hot key scan codes (derived from ACPI DSDT) */ TP_ACPI_HOTKEYSCAN_UNK7, TP_ACPI_HOTKEYSCAN_UNK8, + TP_ACPI_HOTKEYSCAN_MUTE2, + TP_ACPI_HOTKEYSCAN_BRIGHTNESS_ZERO, + TP_ACPI_HOTKEYSCAN_CLIPPING_TOOL, + TP_ACPI_HOTKEYSCAN_CLOUD, + TP_ACPI_HOTKEYSCAN_UNK9, + TP_ACPI_HOTKEYSCAN_VOICE, + TP_ACPI_HOTKEYSCAN_UNK10, + TP_ACPI_HOTKEYSCAN_GESTURES, + TP_ACPI_HOTKEYSCAN_UNK11, + TP_ACPI_HOTKEYSCAN_UNK12, + TP_ACPI_HOTKEYSCAN_UNK13, + TP_ACPI_HOTKEYSCAN_CONFIG, + TP_ACPI_HOTKEYSCAN_NEW_TAB, + TP_ACPI_HOTKEYSCAN_RELOAD, + TP_ACPI_HOTKEYSCAN_BACK, + TP_ACPI_HOTKEYSCAN_MIC_DOWN, + TP_ACPI_HOTKEYSCAN_MIC_UP, + TP_ACPI_HOTKEYSCAN_MIC_CANCELLATION, + TP_ACPI_HOTKEYSCAN_CAMERA_MODE, + TP_ACPI_HOTKEYSCAN_ROTATE_DISPLAY, + /* Hotkey keymap size */ TPACPI_HOTKEY_MAP_LEN }; @@ -2647,9 +2669,7 @@ static ssize_t hotkey_enable_store(struct device *dev, return count; } -static struct device_attribute dev_attr_hotkey_enable = - __ATTR(hotkey_enable, S_IWUSR | S_IRUGO, - hotkey_enable_show, hotkey_enable_store); +static DEVICE_ATTR_RW(hotkey_enable); /* sysfs hotkey mask --------------------------------------------------- */ static ssize_t hotkey_mask_show(struct device *dev, @@ -2685,9 +2705,7 @@ static ssize_t hotkey_mask_store(struct device *dev, return (res) ? res : count; } -static struct device_attribute dev_attr_hotkey_mask = - __ATTR(hotkey_mask, S_IWUSR | S_IRUGO, - hotkey_mask_show, hotkey_mask_store); +static DEVICE_ATTR_RW(hotkey_mask); /* sysfs hotkey bios_enabled ------------------------------------------- */ static ssize_t hotkey_bios_enabled_show(struct device *dev, @@ -2697,8 +2715,7 @@ static ssize_t hotkey_bios_enabled_show(struct device *dev, return sprintf(buf, "0\n"); } -static struct device_attribute dev_attr_hotkey_bios_enabled = - __ATTR(hotkey_bios_enabled, S_IRUGO, hotkey_bios_enabled_show, NULL); +static DEVICE_ATTR_RO(hotkey_bios_enabled); /* sysfs hotkey bios_mask ---------------------------------------------- */ static ssize_t hotkey_bios_mask_show(struct device *dev, @@ -2710,8 +2727,7 @@ static ssize_t hotkey_bios_mask_show(struct device *dev, return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_orig_mask); } -static struct device_attribute dev_attr_hotkey_bios_mask = - __ATTR(hotkey_bios_mask, S_IRUGO, hotkey_bios_mask_show, NULL); +static DEVICE_ATTR_RO(hotkey_bios_mask); /* sysfs hotkey all_mask ----------------------------------------------- */ static ssize_t hotkey_all_mask_show(struct device *dev, @@ -2722,8 +2738,7 @@ static ssize_t hotkey_all_mask_show(struct device *dev, hotkey_all_mask | hotkey_source_mask); } -static struct device_attribute dev_attr_hotkey_all_mask = - __ATTR(hotkey_all_mask, S_IRUGO, hotkey_all_mask_show, NULL); +static DEVICE_ATTR_RO(hotkey_all_mask); /* sysfs hotkey recommended_mask --------------------------------------- */ static ssize_t hotkey_recommended_mask_show(struct device *dev, @@ -2735,9 +2750,7 @@ static ssize_t hotkey_recommended_mask_show(struct device *dev, & ~hotkey_reserved_mask); } -static struct device_attribute dev_attr_hotkey_recommended_mask = - __ATTR(hotkey_recommended_mask, S_IRUGO, - hotkey_recommended_mask_show, NULL); +static DEVICE_ATTR_RO(hotkey_recommended_mask); #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL @@ -2792,9 +2805,7 @@ static ssize_t hotkey_source_mask_store(struct device *dev, return (rc < 0) ? rc : count; } -static struct device_attribute dev_attr_hotkey_source_mask = - __ATTR(hotkey_source_mask, S_IWUSR | S_IRUGO, - hotkey_source_mask_show, hotkey_source_mask_store); +static DEVICE_ATTR_RW(hotkey_source_mask); /* sysfs hotkey hotkey_poll_freq --------------------------------------- */ static ssize_t hotkey_poll_freq_show(struct device *dev, @@ -2826,9 +2837,7 @@ static ssize_t hotkey_poll_freq_store(struct device *dev, return count; } -static struct device_attribute dev_attr_hotkey_poll_freq = - __ATTR(hotkey_poll_freq, S_IWUSR | S_IRUGO, - hotkey_poll_freq_show, hotkey_poll_freq_store); +static DEVICE_ATTR_RW(hotkey_poll_freq); #endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */ @@ -2849,8 +2858,7 @@ static ssize_t hotkey_radio_sw_show(struct device *dev, (res == TPACPI_RFK_RADIO_OFF) ? 0 : 1); } -static struct device_attribute dev_attr_hotkey_radio_sw = - __ATTR(hotkey_radio_sw, S_IRUGO, hotkey_radio_sw_show, NULL); +static DEVICE_ATTR_RO(hotkey_radio_sw); static void hotkey_radio_sw_notify_change(void) { @@ -2872,8 +2880,7 @@ static ssize_t hotkey_tablet_mode_show(struct device *dev, return snprintf(buf, PAGE_SIZE, "%d\n", !!s); } -static struct device_attribute dev_attr_hotkey_tablet_mode = - __ATTR(hotkey_tablet_mode, S_IRUGO, hotkey_tablet_mode_show, NULL); +static DEVICE_ATTR_RO(hotkey_tablet_mode); static void hotkey_tablet_mode_notify_change(void) { @@ -2890,8 +2897,7 @@ static ssize_t hotkey_wakeup_reason_show(struct device *dev, return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_wakeup_reason); } -static struct device_attribute dev_attr_hotkey_wakeup_reason = - __ATTR(wakeup_reason, S_IRUGO, hotkey_wakeup_reason_show, NULL); +static DEVICE_ATTR_RO(hotkey_wakeup_reason); static void hotkey_wakeup_reason_notify_change(void) { @@ -2907,9 +2913,7 @@ static ssize_t hotkey_wakeup_hotunplug_complete_show(struct device *dev, return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_autosleep_ack); } -static struct device_attribute dev_attr_hotkey_wakeup_hotunplug_complete = - __ATTR(wakeup_hotunplug_complete, S_IRUGO, - hotkey_wakeup_hotunplug_complete_show, NULL); +static DEVICE_ATTR_RO(hotkey_wakeup_hotunplug_complete); static void hotkey_wakeup_hotunplug_complete_notify_change(void) { @@ -2917,6 +2921,57 @@ static void hotkey_wakeup_hotunplug_complete_notify_change(void) "wakeup_hotunplug_complete"); } +/* sysfs adaptive kbd mode --------------------------------------------- */ + +static int adaptive_keyboard_get_mode(void); +static int adaptive_keyboard_set_mode(int new_mode); + +enum ADAPTIVE_KEY_MODE { + HOME_MODE, + WEB_BROWSER_MODE, + WEB_CONFERENCE_MODE, + FUNCTION_MODE, + LAYFLAT_MODE +}; + +static ssize_t adaptive_kbd_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int current_mode; + + current_mode = adaptive_keyboard_get_mode(); + if (current_mode < 0) + return current_mode; + + return snprintf(buf, PAGE_SIZE, "%d\n", current_mode); +} + +static ssize_t adaptive_kbd_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long t; + int res; + + if (parse_strtoul(buf, LAYFLAT_MODE, &t)) + return -EINVAL; + + res = adaptive_keyboard_set_mode(t); + return (res < 0) ? res : count; +} + +static DEVICE_ATTR_RW(adaptive_kbd_mode); + +static struct attribute *adaptive_kbd_attributes[] = { + &dev_attr_adaptive_kbd_mode.attr, + NULL +}; + +static const struct attribute_group adaptive_kbd_attr_group = { + .attrs = adaptive_kbd_attributes, +}; + /* --------------------------------------------------------------------- */ static struct attribute *hotkey_attributes[] __initdata = { @@ -3118,6 +3173,13 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) /* (assignments unknown, please report if found) */ KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + + /* No assignments, only used for Adaptive keyboards. */ + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, }, /* Generic keymap for Lenovo ThinkPads */ @@ -3174,6 +3236,35 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) /* Extra keys in use since the X240 / T440 / T540 */ KEY_CONFIG, KEY_SEARCH, KEY_SCALE, KEY_FILE, + + /* + * These are the adaptive keyboard keycodes for Carbon X1 2014. + * The first item in this list is the Mute button which is + * emitted with 0x103 through + * adaptive_keyboard_hotkey_notify_hotkey() when the sound + * symbol is held. + * We'll need to offset those by 0x20. + */ + KEY_RESERVED, /* Mute held, 0x103 */ + KEY_BRIGHTNESS_MIN, /* Backlight off */ + KEY_RESERVED, /* Clipping tool */ + KEY_RESERVED, /* Cloud */ + KEY_RESERVED, + KEY_VOICECOMMAND, /* Voice */ + KEY_RESERVED, + KEY_RESERVED, /* Gestures */ + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_CONFIG, /* Settings */ + KEY_RESERVED, /* New tab */ + KEY_REFRESH, /* Reload */ + KEY_BACK, /* Back */ + KEY_RESERVED, /* Microphone down */ + KEY_RESERVED, /* Microphone up */ + KEY_RESERVED, /* Microphone cancellation */ + KEY_RESERVED, /* Camera mode */ + KEY_RESERVED, /* Rotate display, 0x116 */ }, }; @@ -3227,6 +3318,20 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) if (!tp_features.hotkey) return 1; + /* + * Check if we have an adaptive keyboard, like on the + * Lenovo Carbon X1 2014 (2nd Gen). + */ + if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) { + if ((hkeyv >> 8) == 2) { + tp_features.has_adaptive_kbd = true; + res = sysfs_create_group(&tpacpi_pdev->dev.kobj, + &adaptive_kbd_attr_group); + if (res) + goto err_exit; + } + } + quirks = tpacpi_check_quirks(tpacpi_hotkey_qtable, ARRAY_SIZE(tpacpi_hotkey_qtable)); @@ -3437,6 +3542,9 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) err_exit: delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj); + sysfs_remove_group(&tpacpi_pdev->dev.kobj, + &adaptive_kbd_attr_group); + hotkey_dev_attributes = NULL; return (res < 0) ? res : 1; @@ -3449,14 +3557,6 @@ err_exit: * Will consider support rest of modes in future. * */ -enum ADAPTIVE_KEY_MODE { - HOME_MODE, - WEB_BROWSER_MODE, - WEB_CONFERENCE_MODE, - FUNCTION_MODE, - LAYFLAT_MODE -}; - static const int adaptive_keyboard_modes[] = { HOME_MODE, /* WEB_BROWSER_MODE = 2, @@ -3466,6 +3566,8 @@ static const int adaptive_keyboard_modes[] = { #define DFR_CHANGE_ROW 0x101 #define DFR_SHOW_QUICKVIEW_ROW 0x102 +#define FIRST_ADAPTIVE_KEY 0x103 +#define ADAPTIVE_KEY_OFFSET 0x020 /* press Fn key a while second, it will switch to Function Mode. Then * release Fn key, previous mode be restored. @@ -3473,6 +3575,32 @@ static const int adaptive_keyboard_modes[] = { static bool adaptive_keyboard_mode_is_saved; static int adaptive_keyboard_prev_mode; +static int adaptive_keyboard_get_mode(void) +{ + int mode = 0; + + if (!acpi_evalf(hkey_handle, &mode, "GTRW", "dd", 0)) { + pr_err("Cannot read adaptive keyboard mode\n"); + return -EIO; + } + + return mode; +} + +static int adaptive_keyboard_set_mode(int new_mode) +{ + if (new_mode < 0 || + new_mode > LAYFLAT_MODE) + return -EINVAL; + + if (!acpi_evalf(hkey_handle, NULL, "STRW", "vd", new_mode)) { + pr_err("Cannot set adaptive keyboard mode\n"); + return -EIO; + } + + return 0; +} + static int adaptive_keyboard_get_next_mode(int mode) { size_t i; @@ -3493,8 +3621,9 @@ static int adaptive_keyboard_get_next_mode(int mode) static bool adaptive_keyboard_hotkey_notify_hotkey(unsigned int scancode) { - u32 current_mode = 0; + int current_mode = 0; int new_mode = 0; + int keycode; switch (scancode) { case DFR_CHANGE_ROW: @@ -3502,43 +3631,51 @@ static bool adaptive_keyboard_hotkey_notify_hotkey(unsigned int scancode) 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"); + current_mode = adaptive_keyboard_get_mode(); + if (current_mode < 0) return false; - } else { - new_mode = adaptive_keyboard_get_next_mode( - current_mode); - } + 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"); + if (adaptive_keyboard_set_mode(new_mode) < 0) 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"); + current_mode = adaptive_keyboard_get_mode(); + if (current_mode < 0) 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; - } - } + adaptive_keyboard_prev_mode = current_mode; + adaptive_keyboard_mode_is_saved = true; + + if (adaptive_keyboard_set_mode (FUNCTION_MODE) < 0) + return false; return true; default: - return false; + if (scancode < FIRST_ADAPTIVE_KEY || + scancode >= FIRST_ADAPTIVE_KEY + TPACPI_HOTKEY_MAP_LEN - + ADAPTIVE_KEY_OFFSET) { + pr_info("Unhandled adaptive keyboard key: 0x%x\n", + scancode); + return false; + } + keycode = hotkey_keycode_map[scancode - FIRST_ADAPTIVE_KEY + ADAPTIVE_KEY_OFFSET]; + if (keycode != KEY_RESERVED) { + mutex_lock(&tpacpi_inputdev_send_mutex); + + input_report_key(tpacpi_inputdev, keycode, 1); + input_sync(tpacpi_inputdev); + + input_report_key(tpacpi_inputdev, keycode, 0); + input_sync(tpacpi_inputdev); + + mutex_unlock(&tpacpi_inputdev_send_mutex); + } + return true; } } @@ -3836,28 +3973,21 @@ 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"); - } + if (tp_features.has_adaptive_kbd) { + 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 || @@ -3872,14 +4002,10 @@ static void hotkey_resume(void) 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"); - } + if (tp_features.has_adaptive_kbd) { + if (!acpi_evalf(hkey_handle, NULL, "STRW", "vd", + adaptive_keyboard_prev_mode)) { + pr_err("Cannot set adaptive keyboard mode.\n"); } } } @@ -4079,9 +4205,7 @@ static ssize_t bluetooth_enable_store(struct device *dev, attr, buf, count); } -static struct device_attribute dev_attr_bluetooth_enable = - __ATTR(bluetooth_enable, S_IWUSR | S_IRUGO, - bluetooth_enable_show, bluetooth_enable_store); +static DEVICE_ATTR_RW(bluetooth_enable); /* --------------------------------------------------------------------- */ @@ -4269,9 +4393,7 @@ static ssize_t wan_enable_store(struct device *dev, attr, buf, count); } -static struct device_attribute dev_attr_wan_enable = - __ATTR(wwan_enable, S_IWUSR | S_IRUGO, - wan_enable_show, wan_enable_store); +static DEVICE_ATTR_RW(wan_enable); /* --------------------------------------------------------------------- */ @@ -5048,8 +5170,7 @@ static ssize_t cmos_command_store(struct device *dev, return (res) ? res : count; } -static struct device_attribute dev_attr_cmos_command = - __ATTR(cmos_command, S_IWUSR, NULL, cmos_command_store); +static DEVICE_ATTR_WO(cmos_command); /* --------------------------------------------------------------------- */ @@ -8017,9 +8138,7 @@ static ssize_t fan_pwm1_enable_store(struct device *dev, return count; } -static struct device_attribute dev_attr_fan_pwm1_enable = - __ATTR(pwm1_enable, S_IWUSR | S_IRUGO, - fan_pwm1_enable_show, fan_pwm1_enable_store); +static DEVICE_ATTR_RW(fan_pwm1_enable); /* sysfs fan pwm1 ------------------------------------------------------ */ static ssize_t fan_pwm1_show(struct device *dev, @@ -8079,9 +8198,7 @@ static ssize_t fan_pwm1_store(struct device *dev, return (rc) ? rc : count; } -static struct device_attribute dev_attr_fan_pwm1 = - __ATTR(pwm1, S_IWUSR | S_IRUGO, - fan_pwm1_show, fan_pwm1_store); +static DEVICE_ATTR_RW(fan_pwm1); /* sysfs fan fan1_input ------------------------------------------------ */ static ssize_t fan_fan1_input_show(struct device *dev, @@ -8098,9 +8215,7 @@ static ssize_t fan_fan1_input_show(struct device *dev, return snprintf(buf, PAGE_SIZE, "%u\n", speed); } -static struct device_attribute dev_attr_fan_fan1_input = - __ATTR(fan1_input, S_IRUGO, - fan_fan1_input_show, NULL); +static DEVICE_ATTR_RO(fan_fan1_input); /* sysfs fan fan2_input ------------------------------------------------ */ static ssize_t fan_fan2_input_show(struct device *dev, @@ -8117,9 +8232,7 @@ static ssize_t fan_fan2_input_show(struct device *dev, return snprintf(buf, PAGE_SIZE, "%u\n", speed); } -static struct device_attribute dev_attr_fan_fan2_input = - __ATTR(fan2_input, S_IRUGO, - fan_fan2_input_show, NULL); +static DEVICE_ATTR_RO(fan_fan2_input); /* sysfs fan fan_watchdog (hwmon driver) ------------------------------- */ static ssize_t fan_fan_watchdog_show(struct device_driver *drv, @@ -8735,8 +8848,7 @@ static ssize_t thinkpad_acpi_pdev_name_show(struct device *dev, return snprintf(buf, PAGE_SIZE, "%s\n", TPACPI_NAME); } -static struct device_attribute dev_attr_thinkpad_acpi_pdev_name = - __ATTR(name, S_IRUGO, thinkpad_acpi_pdev_name_show, NULL); +static DEVICE_ATTR_RO(thinkpad_acpi_pdev_name); /* --------------------------------------------------------------------- */ diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index dbcb7a8..9956b990 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -51,6 +51,7 @@ #include <linux/acpi.h> #include <linux/dmi.h> #include <linux/uaccess.h> +#include <acpi/video.h> MODULE_AUTHOR("John Belmonte"); MODULE_DESCRIPTION("Toshiba Laptop ACPI Extras Driver"); @@ -116,6 +117,7 @@ MODULE_LICENSE("GPL"); #define HCI_KBD_ILLUMINATION 0x0095 #define HCI_ECO_MODE 0x0097 #define HCI_ACCELEROMETER2 0x00a6 +#define HCI_SYSTEM_INFO 0xc000 #define SCI_PANEL_POWER_ON 0x010d #define SCI_ILLUMINATION 0x014e #define SCI_USB_SLEEP_CHARGE 0x0150 @@ -129,10 +131,13 @@ MODULE_LICENSE("GPL"); #define HCI_ACCEL_MASK 0x7fff #define HCI_HOTKEY_DISABLE 0x0b #define HCI_HOTKEY_ENABLE 0x09 +#define HCI_HOTKEY_SPECIAL_FUNCTIONS 0x10 #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_SYSTEM_TYPE1 0x10 +#define HCI_SYSTEM_TYPE2 0x11 #define HCI_VIDEO_OUT_LCD 0x1 #define HCI_VIDEO_OUT_CRT 0x2 #define HCI_VIDEO_OUT_TV 0x4 @@ -147,9 +152,10 @@ MODULE_LICENSE("GPL"); #define SCI_KBD_MODE_OFF 0x10 #define SCI_KBD_TIME_MAX 0x3c001a #define SCI_USB_CHARGE_MODE_MASK 0xff -#define SCI_USB_CHARGE_DISABLED 0x30000 -#define SCI_USB_CHARGE_ALTERNATE 0x30009 -#define SCI_USB_CHARGE_AUTO 0x30021 +#define SCI_USB_CHARGE_DISABLED 0x00 +#define SCI_USB_CHARGE_ALTERNATE 0x09 +#define SCI_USB_CHARGE_TYPICAL 0x11 +#define SCI_USB_CHARGE_AUTO 0x21 #define SCI_USB_CHARGE_BAT_MASK 0x7 #define SCI_USB_CHARGE_BAT_LVL_OFF 0x1 #define SCI_USB_CHARGE_BAT_LVL_ON 0x4 @@ -174,6 +180,8 @@ struct toshiba_acpi_dev { int kbd_mode; int kbd_time; int usbsc_bat_level; + int usbsc_mode_base; + int hotkey_event_type; unsigned int illumination_supported:1; unsigned int video_supported:1; @@ -243,29 +251,6 @@ static const struct key_entry toshiba_acpi_keymap[] = { { KE_END, 0 }, }; -/* alternative keymap */ -static const struct dmi_system_id toshiba_alt_keymap_dmi[] = { - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), - DMI_MATCH(DMI_PRODUCT_NAME, "Satellite M840"), - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), - DMI_MATCH(DMI_PRODUCT_NAME, "Qosmio X75-A"), - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), - DMI_MATCH(DMI_PRODUCT_NAME, "TECRA A50-A"), - }, - }, - {} -}; - static const struct key_entry toshiba_acpi_alt_keymap[] = { { KE_KEY, 0x157, { KEY_MUTE } }, { KE_KEY, 0x102, { KEY_ZOOMOUT } }, @@ -281,6 +266,14 @@ static const struct key_entry toshiba_acpi_alt_keymap[] = { }; /* + * List of models which have a broken acpi-video backlight interface and thus + * need to use the toshiba (vendor) interface instead. + */ +static const struct dmi_system_id toshiba_vendor_backlight_dmi[] = { + {} +}; + +/* * Utility */ @@ -819,6 +812,54 @@ static int toshiba_accelerometer_get(struct toshiba_acpi_dev *dev, } /* Sleep (Charge and Music) utilities support */ +static void toshiba_usb_sleep_charge_available(struct toshiba_acpi_dev *dev) +{ + u32 in[TCI_WORDS] = { SCI_GET, SCI_USB_SLEEP_CHARGE, 0, 0, 0, 0 }; + u32 out[TCI_WORDS]; + acpi_status status; + + /* Set the feature to "not supported" in case of error */ + dev->usb_sleep_charge_supported = 0; + + if (!sci_open(dev)) + return; + + status = tci_raw(dev, in, out); + if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + pr_err("ACPI call to get USB Sleep and Charge mode failed\n"); + sci_close(dev); + return; + } else if (out[0] == TOS_NOT_SUPPORTED) { + pr_info("USB Sleep and Charge not supported\n"); + sci_close(dev); + return; + } else if (out[0] == TOS_SUCCESS) { + dev->usbsc_mode_base = out[4]; + } + + in[5] = SCI_USB_CHARGE_BAT_LVL; + status = tci_raw(dev, in, out); + if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + pr_err("ACPI call to get USB Sleep and Charge mode failed\n"); + sci_close(dev); + return; + } else if (out[0] == TOS_NOT_SUPPORTED) { + pr_info("USB Sleep and Charge not supported\n"); + sci_close(dev); + return; + } else if (out[0] == TOS_SUCCESS) { + dev->usbsc_bat_level = out[2]; + /* + * If we reach this point, it means that the laptop has support + * for this feature and all values are initialized. + * Set it as supported. + */ + dev->usb_sleep_charge_supported = 1; + } + + sci_close(dev); +} + static int toshiba_usb_sleep_charge_get(struct toshiba_acpi_dev *dev, u32 *mode) { @@ -934,11 +975,11 @@ static int toshiba_usb_rapid_charge_get(struct toshiba_acpi_dev *dev, status = tci_raw(dev, in, out); sci_close(dev); if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { - pr_err("ACPI call to get USB S&C battery level failed\n"); + pr_err("ACPI call to get USB Rapid Charge failed\n"); return -EIO; } else if (out[0] == TOS_NOT_SUPPORTED || out[0] == TOS_INPUT_DATA_ERROR) { - pr_info("USB Sleep and Charge not supported\n"); + pr_info("USB Rapid Charge not supported\n"); return -ENODEV; } @@ -962,10 +1003,10 @@ static int toshiba_usb_rapid_charge_set(struct toshiba_acpi_dev *dev, status = tci_raw(dev, in, out); sci_close(dev); if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { - pr_err("ACPI call to set USB S&C battery level failed\n"); + pr_err("ACPI call to set USB Rapid Charge failed\n"); return -EIO; } else if (out[0] == TOS_NOT_SUPPORTED) { - pr_info("USB Sleep and Charge not supported\n"); + pr_info("USB Rapid Charge not supported\n"); return -ENODEV; } else if (out[0] == TOS_INPUT_DATA_ERROR) { return -EIO; @@ -984,10 +1025,10 @@ static int toshiba_usb_sleep_music_get(struct toshiba_acpi_dev *dev, u32 *state) result = sci_read(dev, SCI_USB_SLEEP_MUSIC, state); sci_close(dev); if (result == TOS_FAILURE) { - pr_err("ACPI call to set USB S&C mode failed\n"); + pr_err("ACPI call to get Sleep and Music failed\n"); return -EIO; } else if (result == TOS_NOT_SUPPORTED) { - pr_info("USB Sleep and Charge not supported\n"); + pr_info("Sleep and Music not supported\n"); return -ENODEV; } else if (result == TOS_INPUT_DATA_ERROR) { return -EIO; @@ -1006,10 +1047,10 @@ static int toshiba_usb_sleep_music_set(struct toshiba_acpi_dev *dev, u32 state) result = sci_write(dev, SCI_USB_SLEEP_MUSIC, state); sci_close(dev); if (result == TOS_FAILURE) { - pr_err("ACPI call to set USB S&C mode failed\n"); + pr_err("ACPI call to set Sleep and Music failed\n"); return -EIO; } else if (result == TOS_NOT_SUPPORTED) { - pr_info("USB Sleep and Charge not supported\n"); + pr_info("Sleep and Music not supported\n"); return -ENODEV; } else if (result == TOS_INPUT_DATA_ERROR) { return -EIO; @@ -1149,6 +1190,28 @@ static int toshiba_usb_three_set(struct toshiba_acpi_dev *dev, u32 state) return 0; } +/* Hotkey Event type */ +static int toshiba_hotkey_event_type_get(struct toshiba_acpi_dev *dev, + u32 *type) +{ + u32 val1 = 0x03; + u32 val2 = 0; + u32 result; + + result = hci_read2(dev, HCI_SYSTEM_INFO, &val1, &val2); + if (result == TOS_FAILURE) { + pr_err("ACPI call to get System type failed\n"); + return -EIO; + } else if (result == TOS_NOT_SUPPORTED) { + pr_info("System type not supported\n"); + return -ENODEV; + } + + *type = val2; + + return 0; +} + /* Bluetooth rfkill handlers */ static u32 hci_get_bt_present(struct toshiba_acpi_dev *dev, bool *present) @@ -1973,17 +2036,21 @@ static ssize_t usb_sleep_charge_store(struct device *dev, * 0 - Disabled * 1 - Alternate (Non USB conformant devices that require more power) * 2 - Auto (USB conformant devices) + * 3 - Typical */ - if (state != 0 && state != 1 && state != 2) + if (state != 0 && state != 1 && state != 2 && state != 3) return -EINVAL; /* Set the USB charging mode to internal value */ + mode = toshiba->usbsc_mode_base; if (state == 0) - mode = SCI_USB_CHARGE_DISABLED; + mode |= SCI_USB_CHARGE_DISABLED; else if (state == 1) - mode = SCI_USB_CHARGE_ALTERNATE; + mode |= SCI_USB_CHARGE_ALTERNATE; else if (state == 2) - mode = SCI_USB_CHARGE_AUTO; + mode |= SCI_USB_CHARGE_AUTO; + else if (state == 3) + mode |= SCI_USB_CHARGE_TYPICAL; ret = toshiba_usb_sleep_charge_set(toshiba, mode); if (ret) @@ -2333,6 +2400,20 @@ static int toshiba_acpi_enable_hotkeys(struct toshiba_acpi_dev *dev) return 0; } +static void toshiba_acpi_enable_special_functions(struct toshiba_acpi_dev *dev) +{ + u32 result; + + /* + * Re-activate the hotkeys, but this time, we are using the + * "Special Functions" mode. + */ + result = hci_write1(dev, HCI_HOTKEY_EVENT, + HCI_HOTKEY_SPECIAL_FUNCTIONS); + if (result != TOS_SUCCESS) + pr_err("Could not enable the Special Function mode\n"); +} + static bool toshiba_acpi_i8042_filter(unsigned char data, unsigned char str, struct serio *port) { @@ -2434,10 +2515,22 @@ static void toshiba_acpi_process_hotkeys(struct toshiba_acpi_dev *dev) static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev) { + const struct key_entry *keymap = toshiba_acpi_keymap; acpi_handle ec_handle; - int error; + u32 events_type; u32 hci_result; - const struct key_entry *keymap = toshiba_acpi_keymap; + int error; + + error = toshiba_acpi_enable_hotkeys(dev); + if (error) + return error; + + error = toshiba_hotkey_event_type_get(dev, &events_type); + if (error) { + pr_err("Unable to query Hotkey Event Type\n"); + return error; + } + dev->hotkey_event_type = events_type; dev->hotkey_dev = input_allocate_device(); if (!dev->hotkey_dev) @@ -2447,8 +2540,14 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev) dev->hotkey_dev->phys = "toshiba_acpi/input0"; dev->hotkey_dev->id.bustype = BUS_HOST; - if (dmi_check_system(toshiba_alt_keymap_dmi)) + if (events_type == HCI_SYSTEM_TYPE1 || + !dev->kbd_function_keys_supported) + keymap = toshiba_acpi_keymap; + else if (events_type == HCI_SYSTEM_TYPE2 || + dev->kbd_function_keys_supported) keymap = toshiba_acpi_alt_keymap; + else + pr_info("Unknown event type received %x\n", events_type); error = sparse_keymap_setup(dev->hotkey_dev, keymap, NULL); if (error) goto err_free_dev; @@ -2490,12 +2589,6 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev) goto err_remove_filter; } - error = toshiba_acpi_enable_hotkeys(dev); - if (error) { - pr_info("Unable to enable hotkeys\n"); - goto err_remove_filter; - } - error = input_register_device(dev->hotkey_dev); if (error) { pr_info("Unable to register input device\n"); @@ -2541,6 +2634,20 @@ static int toshiba_acpi_setup_backlight(struct toshiba_acpi_dev *dev) ret = get_tr_backlight_status(dev, &enabled); dev->tr_backlight_supported = !ret; + /* + * Tell acpi-video-detect code to prefer vendor backlight on all + * systems with transflective backlight and on dmi matched systems. + */ + if (dev->tr_backlight_supported || + dmi_check_system(toshiba_vendor_backlight_dmi)) + acpi_video_dmi_promote_vendor(); + + if (acpi_video_backlight_support()) + return 0; + + /* acpi-video may have loaded before we called dmi_promote_vendor() */ + acpi_video_unregister_backlight(); + memset(&props, 0, sizeof(props)); props.type = BACKLIGHT_PLATFORM; props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1; @@ -2624,6 +2731,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) { struct toshiba_acpi_dev *dev; const char *hci_method; + u32 special_functions; u32 dummy; bool bt_present; int ret = 0; @@ -2648,6 +2756,16 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) acpi_dev->driver_data = dev; dev_set_drvdata(&acpi_dev->dev, dev); + /* Query the BIOS for supported features */ + + /* + * The "Special Functions" are always supported by the laptops + * with the new keyboard layout, query for its presence to help + * determine the keymap layout to use. + */ + ret = toshiba_function_keys_get(dev, &special_functions); + dev->kbd_function_keys_supported = !ret; + if (toshiba_acpi_setup_keyboard(dev)) pr_info("Unable to activate hotkeys\n"); @@ -2716,8 +2834,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) ret = toshiba_accelerometer_supported(dev); dev->accelerometer_supported = !ret; - ret = toshiba_usb_sleep_charge_get(dev, &dummy); - dev->usb_sleep_charge_supported = !ret; + toshiba_usb_sleep_charge_available(dev); ret = toshiba_usb_rapid_charge_get(dev, &dummy); dev->usb_rapid_charge_supported = !ret; @@ -2725,23 +2842,25 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) ret = toshiba_usb_sleep_music_get(dev, &dummy); dev->usb_sleep_music_supported = !ret; - ret = toshiba_function_keys_get(dev, &dummy); - dev->kbd_function_keys_supported = !ret; - ret = toshiba_panel_power_on_get(dev, &dummy); dev->panel_power_on_supported = !ret; ret = toshiba_usb_three_get(dev, &dummy); dev->usb_three_supported = !ret; - /* Determine whether or not BIOS supports fan and video interfaces */ - ret = get_video_status(dev, &dummy); dev->video_supported = !ret; ret = get_fan_status(dev, &dummy); dev->fan_supported = !ret; + /* + * Enable the "Special Functions" mode only if they are + * supported and if they are activated. + */ + if (dev->kbd_function_keys_supported && special_functions) + toshiba_acpi_enable_special_functions(dev); + ret = sysfs_create_group(&dev->acpi_dev->dev.kobj, &toshiba_attr_group); if (ret) { @@ -2770,6 +2889,21 @@ static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event) case 0x80: /* Hotkeys and some system events */ toshiba_acpi_process_hotkeys(dev); break; + case 0x81: /* Dock events */ + case 0x82: + case 0x83: + pr_info("Dock event received %x\n", event); + break; + case 0x88: /* Thermal events */ + pr_info("Thermal event received\n"); + break; + case 0x8f: /* LID closed */ + case 0x90: /* LID is closed and Dock has been ejected */ + break; + case 0x8c: /* SATA power events */ + case 0x8b: + pr_info("SATA power event received %x\n", event); + break; case 0x92: /* Keyboard backlight mode changed */ /* Update sysfs entries */ ret = sysfs_update_group(&acpi_dev->dev.kobj, @@ -2777,17 +2911,19 @@ static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event) if (ret) pr_err("Unable to update sysfs entries\n"); break; - case 0x81: /* Unknown */ - case 0x82: /* Unknown */ - case 0x83: /* Unknown */ - case 0x8c: /* Unknown */ + case 0x85: /* Unknown */ + case 0x8d: /* Unknown */ case 0x8e: /* Unknown */ - case 0x8f: /* Unknown */ - case 0x90: /* Unknown */ + case 0x94: /* Unknown */ + case 0x95: /* Unknown */ default: pr_info("Unknown event received %x\n", event); break; } + + acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class, + dev_name(&acpi_dev->dev), + event, 0); } #ifdef CONFIG_PM_SLEEP diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c index 2cb1ea6..2498007 100644 --- a/drivers/platform/x86/toshiba_bluetooth.c +++ b/drivers/platform/x86/toshiba_bluetooth.c @@ -2,6 +2,7 @@ * Toshiba Bluetooth Enable Driver * * Copyright (C) 2009 Jes Sorensen <Jes.Sorensen@gmail.com> + * Copyright (C) 2015 Azael Avalos <coproscefalo@gmail.com> * * Thanks to Matthew Garrett for background info on ACPI innards which * normal people aren't meant to understand :-) @@ -25,6 +26,10 @@ #include <linux/types.h> #include <linux/acpi.h> +#define BT_KILLSWITCH_MASK 0x01 +#define BT_PLUGGED_MASK 0x40 +#define BT_POWER_MASK 0x80 + MODULE_AUTHOR("Jes Sorensen <Jes.Sorensen@gmail.com>"); MODULE_DESCRIPTION("Toshiba Laptop ACPI Bluetooth Enable Driver"); MODULE_LICENSE("GPL"); @@ -57,32 +62,107 @@ static struct acpi_driver toshiba_bt_rfkill_driver = { .drv.pm = &toshiba_bt_pm, }; +static int toshiba_bluetooth_present(acpi_handle handle) +{ + acpi_status result; + u64 bt_present; + + /* + * Some Toshiba laptops may have a fake TOS6205 device in + * their ACPI BIOS, so query the _STA method to see if there + * is really anything there. + */ + result = acpi_evaluate_integer(handle, "_STA", NULL, &bt_present); + if (ACPI_FAILURE(result)) { + pr_err("ACPI call to query Bluetooth presence failed"); + return -ENXIO; + } else if (!bt_present) { + pr_info("Bluetooth device not present\n"); + return -ENODEV; + } + + return 0; +} + +static int toshiba_bluetooth_status(acpi_handle handle) +{ + acpi_status result; + u64 status; + + result = acpi_evaluate_integer(handle, "BTST", NULL, &status); + if (ACPI_FAILURE(result)) { + pr_err("Could not get Bluetooth device status\n"); + return -ENXIO; + } + + pr_info("Bluetooth status %llu\n", status); + + return status; +} static int toshiba_bluetooth_enable(acpi_handle handle) { - acpi_status res1, res2; - u64 result; + acpi_status result; + bool killswitch; + bool powered; + bool plugged; + int status; /* * Query ACPI to verify RFKill switch is set to 'on'. * If not, we return silently, no need to report it as * an error. */ - res1 = acpi_evaluate_integer(handle, "BTST", NULL, &result); - if (ACPI_FAILURE(res1)) - return res1; - if (!(result & 0x01)) - return 0; + status = toshiba_bluetooth_status(handle); + if (status < 0) + return status; + + killswitch = (status & BT_KILLSWITCH_MASK) ? true : false; + powered = (status & BT_POWER_MASK) ? true : false; + plugged = (status & BT_PLUGGED_MASK) ? true : false; - pr_info("Re-enabling Toshiba Bluetooth\n"); - res1 = acpi_evaluate_object(handle, "AUSB", NULL, NULL); - res2 = acpi_evaluate_object(handle, "BTPO", NULL, NULL); - if (!ACPI_FAILURE(res1) || !ACPI_FAILURE(res2)) + if (!killswitch) return 0; + /* + * This check ensures to only enable the device if it is powered + * off or detached, as some recent devices somehow pass the killswitch + * test, causing a loop enabling/disabling the device, see bug 93911. + */ + if (powered || plugged) + return 0; + + result = acpi_evaluate_object(handle, "AUSB", NULL, NULL); + if (ACPI_FAILURE(result)) { + pr_err("Could not attach USB Bluetooth device\n"); + return -ENXIO; + } + + result = acpi_evaluate_object(handle, "BTPO", NULL, NULL); + if (ACPI_FAILURE(result)) { + pr_err("Could not power ON Bluetooth device\n"); + return -ENXIO; + } + + return 0; +} + +static int toshiba_bluetooth_disable(acpi_handle handle) +{ + acpi_status result; + + result = acpi_evaluate_object(handle, "BTPF", NULL, NULL); + if (ACPI_FAILURE(result)) { + pr_err("Could not power OFF Bluetooth device\n"); + return -ENXIO; + } - pr_warn("Failed to re-enable Toshiba Bluetooth\n"); + result = acpi_evaluate_object(handle, "DUSB", NULL, NULL); + if (ACPI_FAILURE(result)) { + pr_err("Could not detach USB Bluetooth device\n"); + return -ENXIO; + } - return -ENODEV; + return 0; } static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event) @@ -99,23 +179,18 @@ static int toshiba_bt_resume(struct device *dev) static int toshiba_bt_rfkill_add(struct acpi_device *device) { - acpi_status status; - u64 bt_present; - int result = -ENODEV; + int result; - /* - * Some Toshiba laptops may have a fake TOS6205 device in - * their ACPI BIOS, so query the _STA method to see if there - * is really anything there, before trying to enable it. - */ - status = acpi_evaluate_integer(device->handle, "_STA", NULL, - &bt_present); + result = toshiba_bluetooth_present(device->handle); + if (result) + return result; - if (!ACPI_FAILURE(status) && bt_present) { - pr_info("Detected Toshiba ACPI Bluetooth device - " - "installing RFKill handler\n"); - result = toshiba_bluetooth_enable(device->handle); - } + pr_info("Toshiba ACPI Bluetooth device driver\n"); + + /* Enable the BT device */ + result = toshiba_bluetooth_enable(device->handle); + if (result) + return result; return result; } @@ -123,7 +198,7 @@ static int toshiba_bt_rfkill_add(struct acpi_device *device) static int toshiba_bt_rfkill_remove(struct acpi_device *device) { /* clean up */ - return 0; + return toshiba_bluetooth_disable(device->handle); } module_acpi_driver(toshiba_bt_rfkill_driver); diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index 737e56d..aac4757 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -45,7 +45,6 @@ MODULE_LICENSE("GPL"); #define ACPI_WMI_CLASS "wmi" -static DEFINE_MUTEX(wmi_data_lock); static LIST_HEAD(wmi_block_list); struct guid_block { @@ -240,10 +239,10 @@ static bool find_guid(const char *guid_string, struct wmi_block **out) if (memcmp(block->guid, guid_input, 16) == 0) { if (out) *out = wblock; - return 1; + return true; } } - return 0; + return false; } static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable) |