Commit 78d42567 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'platform-drivers-x86-v4.1-1' of...

Merge tag 'platform-drivers-x86-v4.1-1' of git://git.infradead.org/users/dvhart/linux-platform-drivers-x86

Pull x86 platform driver updates from Darren Hart:
 "This series includes significant updates to the toshiba_acpi driver
  and the reintroduction of the dell-laptop keyboard backlight additions
  I had to revert previously.  Also included are various fixes for
  typos, warnings, correctness, and minor bugs.

  Specifics:

  dell-laptop:
     - add support for keyboard backlight.

  toshiba_acpi:
     - adaptive keyboard, hotkey, USB sleep and charge, and backlight
       updates.  Update sysfs documentation.

  toshiba_bluetooth:
     - fix enabling/disabling loop on recent devices

  apple-gmux:
     - lock iGP IO to protect from vgaarb changes

  other:
     - Fix typos, clear gcc warnings, clarify pr_* messages, correct
       return types, update MAINTAINERS"

* tag 'platform-drivers-x86-v4.1-1' of git://git.infradead.org/users/dvhart/linux-platform-drivers-x86: (25 commits)
  toshiba_acpi: Do not register vendor backlight when acpi_video bl is available
  MAINTAINERS: Add me on list of Dell laptop drivers
  platform: x86: dell-laptop: Add support for keyboard backlight
  Documentation/ABI: Update sysfs-driver-toshiba_acpi entry
  toshiba_acpi: Fix pr_* messages from USB Sleep Functions
  toshiba_acpi: Update and fix USB Sleep and Charge modes
  wmi: Use bool function return values of true/false not 1/0
  toshiba_bluetooth: Fix enabling/disabling loop on recent devices
  toshiba_bluetooth: Clean up *_add function and disable BT device at removal
  toshiba_bluetooth: Add three new functions to the driver
  toshiba_acpi: Fix the enabling of the Special Functions
  toshiba_acpi: Use the Hotkey Event Type function for keymap choosing
  toshiba_acpi: Add Hotkey Event Type function and definitions
  x86/wmi: delete unused wmi_data_lock mutex causing gcc warning
  apple-gmux: lock iGP IO to protect from vgaarb changes
  MAINTAINERS: Add missing Toshiba devices and add myself as maintainer
  toshiba_acpi: Update events in toshiba_acpi_notify
  intel-oaktrail: Fix trivial typo in comment
  thinkpad_acpi: off by one in adaptive_keyboard_hotkey_notify_hotkey()
  thinkpad_acpi: signedness bugs getting current_mode
  ...
parents 36a8032d 358d6a2c
......@@ -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
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.
......@@ -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
------------------------------------
......
......@@ -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
......
......@@ -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---
......
......@@ -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 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
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);
......
......@@ -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");
......@@ -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
......
......@@ -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 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
* 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, &current_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);
/* --------------------------------------------------------------------- */
......
......@@ -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 } },
......@@ -280,6 +265,14 @@ static const struct key_entry toshiba_acpi_alt_keymap[] = {
{ KE_END, 0 },
};
/*
* 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
......
......@@ -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);
......@@ -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)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment