platform-drivers-x86 got 4.2
asus-wmi: Fan control dell*: Add Dell airplane mode switch driver ideapad-laptop: Platform rfkill fixes, and regression fix pvpanic: Handle missing _STA correctly toshiba*: Rafactor bluetooth support, haps documentation, driver cleanup other: Use acpi_video_unregister_backlight instead of acpi_video_unregister in serveral drivers. Orphan msi-wmi. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAABAgAGBQJVks/CAAoJEKbMaAwKp364M5kH/06QhbX8NBSrzZk96jTmiFrP pSO1Z6Jwnm3SL/phUAaRusNVWh0ZsaezvQ6M3Lnsjeh+07qPsF+lag+nHGYXSrQJ 3LVOdqXAojnvPIPNxaTSXylypF776WqFcWwzksICaJo/RA8mE/I4S0A6PMzujivV 6aAZEApED9EFWgnBWz8yYDscTUF7Bypy5/6uNtI0CRXFwUw1C05f5LW5J/qPpIMW lerSLJKQSpuGbK3db4TqlwJOCZXdSb3HgzLAQKvzhRCukyEdA3zL76Jj6DfVhE9d nKybk30qYVy1smEWH/I2jMSrxcjY56pRnGhanc/U5gSo9WARGjed/XKtbkFqpSQ= =9RCr -----END PGP SIGNATURE----- Merge tag 'platform-drivers-x86-v4.2-1' of git://git.infradead.org/users/dvhart/linux-platform-drivers-x86 Pull x86 platform driver updates from Darren Hart: "Fairly routine update for platform-drivers-x86. Mostly fixes and cleanups, with a significant refactoring of toshiba* drivers. Includes the addition of the dell-rbtn driver. Details: asus-wmi: - fan control dell*: - add Dell airplane mode switch driver ideapad-laptop: - platform rfkill fixes, and regression fix pvpanic: - handle missing _STA correctly toshiba*: - rafactor bluetooth support - haps documentation - driver cleanup other: - Use acpi_video_unregister_backlight instead of acpi_video_unregister in serveral drivers. - Orphan msi-wmi. * tag 'platform-drivers-x86-v4.2-1' of git://git.infradead.org/users/dvhart/linux-platform-drivers-x86: (24 commits) MAINTAINERS: Orphan x86 driver msi-wmi ideapad: fix software rfkill setting dell-laptop: Use dell-rbtn instead i8042 filter when possible dell-rbtn: Export notifier for other kernel modules dell-rbtn: Dell Airplane Mode Switch driver samsung-laptop: Use acpi_video_unregister_backlight instead of acpi_video_unregister asus-wmi: Use acpi_video_unregister_backlight instead of acpi_video_unregister apple_gmux: Use acpi_video_unregister_backlight instead of acpi_video_unregister pvpanic: handle missing _STA correctly ideapad_laptop: Lenovo G50-30 fix rfkill reports wireless blocked asus-wmi: add fan control Documentation/ABI: Add file describing the sysfs entries for toshiba_haps toshiba_haps: Make use of DEVICE_ATTR_{RW, WO} macros toshiba_haps: Replace sscanf with kstrtoint toshiba_acpi: Bump driver version to 0.22 toshiba_acpi: Remove TOS_FAILURE check from some functions toshiba_acpi: Comments cleanup toshiba_acpi: Rename hci_{read, write}1 functions toshiba_acpi: Remove no longer needed hci_{read, write}2 functions toshiba_bluetooth: Change BT status message to debug ...
This commit is contained in:
commit
05fde26a94
13 changed files with 1121 additions and 289 deletions
20
Documentation/ABI/testing/sysfs-driver-toshiba_haps
Normal file
20
Documentation/ABI/testing/sysfs-driver-toshiba_haps
Normal file
|
@ -0,0 +1,20 @@
|
|||
What: /sys/devices/LNXSYSTM:00/LNXSYBUS:00/TOS620A:00/protection_level
|
||||
Date: August 16, 2014
|
||||
KernelVersion: 3.17
|
||||
Contact: Azael Avalos <coproscefalo@gmail.com>
|
||||
Description: This file controls the built-in accelerometer protection level,
|
||||
valid values are:
|
||||
* 0 -> Disabled
|
||||
* 1 -> Low
|
||||
* 2 -> Medium
|
||||
* 3 -> High
|
||||
The default potection value is set to 2 (Medium).
|
||||
Users: KToshiba
|
||||
|
||||
What: /sys/devices/LNXSYSTM:00/LNXSYBUS:00/TOS620A:00/reset_protection
|
||||
Date: August 16, 2014
|
||||
KernelVersion: 3.17
|
||||
Contact: Azael Avalos <coproscefalo@gmail.com>
|
||||
Description: This file turns off the built-in accelerometer for a few
|
||||
seconds and then restore normal operation. Accepting 1 as the
|
||||
only parameter.
|
|
@ -3216,6 +3216,11 @@ L: platform-driver-x86@vger.kernel.org
|
|||
S: Maintained
|
||||
F: drivers/platform/x86/dell-laptop.c
|
||||
|
||||
DELL LAPTOP RBTN DRIVER
|
||||
M: Pali Rohár <pali.rohar@gmail.com>
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/dell-rbtn.*
|
||||
|
||||
DELL LAPTOP FREEFALL DRIVER
|
||||
M: Pali Rohár <pali.rohar@gmail.com>
|
||||
S: Maintained
|
||||
|
@ -6786,9 +6791,8 @@ S: Maintained
|
|||
F: drivers/platform/x86/msi-laptop.c
|
||||
|
||||
MSI WMI SUPPORT
|
||||
M: Anisse Astier <anisse@astier.eu>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Supported
|
||||
S: Orphan
|
||||
F: drivers/platform/x86/msi-wmi.c
|
||||
|
||||
MSI001 MEDIA DRIVER
|
||||
|
|
|
@ -141,6 +141,22 @@ config DELL_SMO8800
|
|||
To compile this driver as a module, choose M here: the module will
|
||||
be called dell-smo8800.
|
||||
|
||||
config DELL_RBTN
|
||||
tristate "Dell Airplane Mode Switch driver"
|
||||
depends on ACPI
|
||||
depends on INPUT
|
||||
depends on RFKILL
|
||||
---help---
|
||||
Say Y here if you want to support Dell Airplane Mode Switch ACPI
|
||||
device on Dell laptops. Sometimes it has names: DELLABCE or DELRBTN.
|
||||
This driver register rfkill device or input hotkey device depending
|
||||
on hardware type (hw switch slider or keyboard toggle button). For
|
||||
rfkill devices it receive HW switch events and set correct hard
|
||||
rfkill state.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called dell-rbtn.
|
||||
|
||||
|
||||
config FUJITSU_LAPTOP
|
||||
tristate "Fujitsu Laptop Extras"
|
||||
|
@ -622,7 +638,6 @@ config ACPI_TOSHIBA
|
|||
select NEW_LEDS
|
||||
depends on BACKLIGHT_CLASS_DEVICE
|
||||
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
|
||||
|
@ -653,6 +668,7 @@ config ACPI_TOSHIBA
|
|||
config TOSHIBA_BT_RFKILL
|
||||
tristate "Toshiba Bluetooth RFKill switch support"
|
||||
depends on ACPI
|
||||
depends on RFKILL || RFKILL = n
|
||||
---help---
|
||||
This driver adds support for Bluetooth events for the RFKill
|
||||
switch on modern Toshiba laptops with full ACPI support and
|
||||
|
|
|
@ -14,6 +14,7 @@ obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o
|
|||
obj-$(CONFIG_DELL_WMI) += dell-wmi.o
|
||||
obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o
|
||||
obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o
|
||||
obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o
|
||||
obj-$(CONFIG_ACER_WMI) += acer-wmi.o
|
||||
obj-$(CONFIG_ACERHDF) += acerhdf.o
|
||||
obj-$(CONFIG_HP_ACCEL) += hp_accel.o
|
||||
|
|
|
@ -78,6 +78,7 @@ MODULE_LICENSE("GPL");
|
|||
#define ASUS_WMI_METHODID_GPID 0x44495047 /* Get Panel ID?? (Resol) */
|
||||
#define ASUS_WMI_METHODID_QMOD 0x444F4D51 /* Quiet MODe */
|
||||
#define ASUS_WMI_METHODID_SPLV 0x4C425053 /* Set Panel Light Value */
|
||||
#define ASUS_WMI_METHODID_AGFN 0x4E464741 /* FaN? */
|
||||
#define ASUS_WMI_METHODID_SFUN 0x4E554653 /* FUNCtionalities */
|
||||
#define ASUS_WMI_METHODID_SDSP 0x50534453 /* Set DiSPlay output */
|
||||
#define ASUS_WMI_METHODID_GDSP 0x50534447 /* Get DiSPlay output */
|
||||
|
@ -150,11 +151,37 @@ MODULE_LICENSE("GPL");
|
|||
#define ASUS_WMI_DSTS_BRIGHTNESS_MASK 0x000000FF
|
||||
#define ASUS_WMI_DSTS_MAX_BRIGTH_MASK 0x0000FF00
|
||||
|
||||
#define ASUS_FAN_DESC "cpu_fan"
|
||||
#define ASUS_FAN_MFUN 0x13
|
||||
#define ASUS_FAN_SFUN_READ 0x06
|
||||
#define ASUS_FAN_SFUN_WRITE 0x07
|
||||
#define ASUS_FAN_CTRL_MANUAL 1
|
||||
#define ASUS_FAN_CTRL_AUTO 2
|
||||
|
||||
struct bios_args {
|
||||
u32 arg0;
|
||||
u32 arg1;
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* Struct that's used for all methods called via AGFN. Naming is
|
||||
* identically to the AML code.
|
||||
*/
|
||||
struct agfn_args {
|
||||
u16 mfun; /* probably "Multi-function" to be called */
|
||||
u16 sfun; /* probably "Sub-function" to be called */
|
||||
u16 len; /* size of the hole struct, including subfunction fields */
|
||||
u8 stas; /* not used by now */
|
||||
u8 err; /* zero on success */
|
||||
} __packed;
|
||||
|
||||
/* struct used for calling fan read and write methods */
|
||||
struct fan_args {
|
||||
struct agfn_args agfn; /* common fields */
|
||||
u8 fan; /* fan number: 0: set auto mode 1: 1st fan */
|
||||
u32 speed; /* read: RPM/100 - write: 0-255 */
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* <platform>/ - debugfs root directory
|
||||
* dev_id - current dev_id
|
||||
|
@ -204,6 +231,10 @@ struct asus_wmi {
|
|||
struct asus_rfkill gps;
|
||||
struct asus_rfkill uwb;
|
||||
|
||||
bool asus_hwmon_fan_manual_mode;
|
||||
int asus_hwmon_num_fans;
|
||||
int asus_hwmon_pwm;
|
||||
|
||||
struct hotplug_slot *hotplug_slot;
|
||||
struct mutex hotplug_lock;
|
||||
struct mutex wmi_lock;
|
||||
|
@ -294,6 +325,36 @@ exit:
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int asus_wmi_evaluate_method_agfn(const struct acpi_buffer args)
|
||||
{
|
||||
struct acpi_buffer input;
|
||||
u64 phys_addr;
|
||||
u32 retval;
|
||||
u32 status = -1;
|
||||
|
||||
/*
|
||||
* Copy to dma capable address otherwise memory corruption occurs as
|
||||
* bios has to be able to access it.
|
||||
*/
|
||||
input.pointer = kzalloc(args.length, GFP_DMA | GFP_KERNEL);
|
||||
input.length = args.length;
|
||||
if (!input.pointer)
|
||||
return -ENOMEM;
|
||||
phys_addr = virt_to_phys(input.pointer);
|
||||
memcpy(input.pointer, args.pointer, args.length);
|
||||
|
||||
status = asus_wmi_evaluate_method(ASUS_WMI_METHODID_AGFN,
|
||||
phys_addr, 0, &retval);
|
||||
if (!status)
|
||||
memcpy(args.pointer, input.pointer, args.length);
|
||||
|
||||
kfree(input.pointer);
|
||||
if (status)
|
||||
return -ENXIO;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int asus_wmi_get_devstate(struct asus_wmi *asus, u32 dev_id, u32 *retval)
|
||||
{
|
||||
return asus_wmi_evaluate_method(asus->dsts_id, dev_id, 0, retval);
|
||||
|
@ -1022,35 +1083,228 @@ exit:
|
|||
/*
|
||||
* Hwmon device
|
||||
*/
|
||||
static ssize_t asus_hwmon_pwm1(struct device *dev,
|
||||
static int asus_hwmon_agfn_fan_speed_read(struct asus_wmi *asus, int fan,
|
||||
int *speed)
|
||||
{
|
||||
struct fan_args args = {
|
||||
.agfn.len = sizeof(args),
|
||||
.agfn.mfun = ASUS_FAN_MFUN,
|
||||
.agfn.sfun = ASUS_FAN_SFUN_READ,
|
||||
.fan = fan,
|
||||
.speed = 0,
|
||||
};
|
||||
struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
|
||||
int status;
|
||||
|
||||
if (fan != 1)
|
||||
return -EINVAL;
|
||||
|
||||
status = asus_wmi_evaluate_method_agfn(input);
|
||||
|
||||
if (status || args.agfn.err)
|
||||
return -ENXIO;
|
||||
|
||||
if (speed)
|
||||
*speed = args.speed;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asus_hwmon_agfn_fan_speed_write(struct asus_wmi *asus, int fan,
|
||||
int *speed)
|
||||
{
|
||||
struct fan_args args = {
|
||||
.agfn.len = sizeof(args),
|
||||
.agfn.mfun = ASUS_FAN_MFUN,
|
||||
.agfn.sfun = ASUS_FAN_SFUN_WRITE,
|
||||
.fan = fan,
|
||||
.speed = speed ? *speed : 0,
|
||||
};
|
||||
struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
|
||||
int status;
|
||||
|
||||
/* 1: for setting 1st fan's speed 0: setting auto mode */
|
||||
if (fan != 1 && fan != 0)
|
||||
return -EINVAL;
|
||||
|
||||
status = asus_wmi_evaluate_method_agfn(input);
|
||||
|
||||
if (status || args.agfn.err)
|
||||
return -ENXIO;
|
||||
|
||||
if (speed && fan == 1)
|
||||
asus->asus_hwmon_pwm = *speed;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if we can read the speed of one fan. If true we assume we can also
|
||||
* control it.
|
||||
*/
|
||||
static int asus_hwmon_get_fan_number(struct asus_wmi *asus, int *num_fans)
|
||||
{
|
||||
int status;
|
||||
int speed = 0;
|
||||
|
||||
*num_fans = 0;
|
||||
|
||||
status = asus_hwmon_agfn_fan_speed_read(asus, 1, &speed);
|
||||
if (!status)
|
||||
*num_fans = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asus_hwmon_fan_set_auto(struct asus_wmi *asus)
|
||||
{
|
||||
int status;
|
||||
|
||||
status = asus_hwmon_agfn_fan_speed_write(asus, 0, NULL);
|
||||
if (status)
|
||||
return -ENXIO;
|
||||
|
||||
asus->asus_hwmon_fan_manual_mode = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asus_hwmon_fan_rpm_show(struct device *dev, int fan)
|
||||
{
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
int value;
|
||||
int ret;
|
||||
|
||||
/* no speed readable on manual mode */
|
||||
if (asus->asus_hwmon_fan_manual_mode)
|
||||
return -ENXIO;
|
||||
|
||||
ret = asus_hwmon_agfn_fan_speed_read(asus, fan+1, &value);
|
||||
if (ret) {
|
||||
pr_warn("reading fan speed failed: %d\n", ret);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static void asus_hwmon_pwm_show(struct asus_wmi *asus, int fan, int *value)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (asus->asus_hwmon_pwm >= 0) {
|
||||
*value = asus->asus_hwmon_pwm;
|
||||
return;
|
||||
}
|
||||
|
||||
err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_CTRL, value);
|
||||
if (err < 0)
|
||||
return;
|
||||
|
||||
*value &= 0xFF;
|
||||
|
||||
if (*value == 1) /* Low Speed */
|
||||
*value = 85;
|
||||
else if (*value == 2)
|
||||
*value = 170;
|
||||
else if (*value == 3)
|
||||
*value = 255;
|
||||
else if (*value) {
|
||||
pr_err("Unknown fan speed %#x\n", *value);
|
||||
*value = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t pwm1_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
u32 value;
|
||||
int err;
|
||||
int value;
|
||||
|
||||
err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_CTRL, &value);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
value &= 0xFF;
|
||||
|
||||
if (value == 1) /* Low Speed */
|
||||
value = 85;
|
||||
else if (value == 2)
|
||||
value = 170;
|
||||
else if (value == 3)
|
||||
value = 255;
|
||||
else if (value != 0) {
|
||||
pr_err("Unknown fan speed %#x\n", value);
|
||||
value = -1;
|
||||
}
|
||||
asus_hwmon_pwm_show(asus, 0, &value);
|
||||
|
||||
return sprintf(buf, "%d\n", value);
|
||||
}
|
||||
|
||||
static ssize_t pwm1_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count) {
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
int value;
|
||||
int state;
|
||||
int ret;
|
||||
|
||||
ret = kstrtouint(buf, 10, &value);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
value = clamp(value, 0, 255);
|
||||
|
||||
state = asus_hwmon_agfn_fan_speed_write(asus, 1, &value);
|
||||
if (state)
|
||||
pr_warn("Setting fan speed failed: %d\n", state);
|
||||
else
|
||||
asus->asus_hwmon_fan_manual_mode = true;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t fan1_input_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int value = asus_hwmon_fan_rpm_show(dev, 0);
|
||||
|
||||
return sprintf(buf, "%d\n", value < 0 ? -1 : value*100);
|
||||
|
||||
}
|
||||
|
||||
static ssize_t pwm1_enable_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
|
||||
if (asus->asus_hwmon_fan_manual_mode)
|
||||
return sprintf(buf, "%d\n", ASUS_FAN_CTRL_MANUAL);
|
||||
|
||||
return sprintf(buf, "%d\n", ASUS_FAN_CTRL_AUTO);
|
||||
}
|
||||
|
||||
static ssize_t pwm1_enable_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
int status = 0;
|
||||
int state;
|
||||
int ret;
|
||||
|
||||
ret = kstrtouint(buf, 10, &state);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (state == ASUS_FAN_CTRL_MANUAL)
|
||||
asus->asus_hwmon_fan_manual_mode = true;
|
||||
else
|
||||
status = asus_hwmon_fan_set_auto(asus);
|
||||
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t fan1_label_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%s\n", ASUS_FAN_DESC);
|
||||
}
|
||||
|
||||
static ssize_t asus_hwmon_temp1(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
|
@ -1069,11 +1323,21 @@ static ssize_t asus_hwmon_temp1(struct device *dev,
|
|||
return sprintf(buf, "%d\n", value);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(pwm1, S_IRUGO, asus_hwmon_pwm1, NULL);
|
||||
/* Fan1 */
|
||||
static DEVICE_ATTR_RW(pwm1);
|
||||
static DEVICE_ATTR_RW(pwm1_enable);
|
||||
static DEVICE_ATTR_RO(fan1_input);
|
||||
static DEVICE_ATTR_RO(fan1_label);
|
||||
|
||||
/* Temperature */
|
||||
static DEVICE_ATTR(temp1_input, S_IRUGO, asus_hwmon_temp1, NULL);
|
||||
|
||||
static struct attribute *hwmon_attributes[] = {
|
||||
&dev_attr_pwm1.attr,
|
||||
&dev_attr_pwm1_enable.attr,
|
||||
&dev_attr_fan1_input.attr,
|
||||
&dev_attr_fan1_label.attr,
|
||||
|
||||
&dev_attr_temp1_input.attr,
|
||||
NULL
|
||||
};
|
||||
|
@ -1084,19 +1348,28 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj,
|
|||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct platform_device *pdev = to_platform_device(dev->parent);
|
||||
struct asus_wmi *asus = platform_get_drvdata(pdev);
|
||||
bool ok = true;
|
||||
int dev_id = -1;
|
||||
int fan_attr = -1;
|
||||
u32 value = ASUS_WMI_UNSUPPORTED_METHOD;
|
||||
bool ok = true;
|
||||
|
||||
if (attr == &dev_attr_pwm1.attr)
|
||||
dev_id = ASUS_WMI_DEVID_FAN_CTRL;
|
||||
else if (attr == &dev_attr_temp1_input.attr)
|
||||
dev_id = ASUS_WMI_DEVID_THERMAL_CTRL;
|
||||
|
||||
|
||||
if (attr == &dev_attr_fan1_input.attr
|
||||
|| attr == &dev_attr_fan1_label.attr
|
||||
|| attr == &dev_attr_pwm1.attr
|
||||
|| attr == &dev_attr_pwm1_enable.attr) {
|
||||
fan_attr = 1;
|
||||
}
|
||||
|
||||
if (dev_id != -1) {
|
||||
int err = asus_wmi_get_devstate(asus, dev_id, &value);
|
||||
|
||||
if (err < 0)
|
||||
if (err < 0 && fan_attr == -1)
|
||||
return 0; /* can't return negative here */
|
||||
}
|
||||
|
||||
|
@ -1112,10 +1385,16 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj,
|
|||
if (value == ASUS_WMI_UNSUPPORTED_METHOD || value & 0xFFF80000
|
||||
|| (!asus->sfun && !(value & ASUS_WMI_DSTS_PRESENCE_BIT)))
|
||||
ok = false;
|
||||
else
|
||||
ok = fan_attr <= asus->asus_hwmon_num_fans;
|
||||
} else if (dev_id == ASUS_WMI_DEVID_THERMAL_CTRL) {
|
||||
/* If value is zero, something is clearly wrong */
|
||||
if (value == 0)
|
||||
if (!value)
|
||||
ok = false;
|
||||
} else if (fan_attr <= asus->asus_hwmon_num_fans && fan_attr != -1) {
|
||||
ok = true;
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
return ok ? attr->mode : 0;
|
||||
|
@ -1723,6 +2002,25 @@ error_debugfs:
|
|||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static int asus_wmi_fan_init(struct asus_wmi *asus)
|
||||
{
|
||||
int status;
|
||||
|
||||
asus->asus_hwmon_pwm = -1;
|
||||
asus->asus_hwmon_num_fans = -1;
|
||||
asus->asus_hwmon_fan_manual_mode = false;
|
||||
|
||||
status = asus_hwmon_get_fan_number(asus, &asus->asus_hwmon_num_fans);
|
||||
if (status) {
|
||||
asus->asus_hwmon_num_fans = 0;
|
||||
pr_warn("Could not determine number of fans: %d\n", status);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
pr_info("Number of fans: %d\n", asus->asus_hwmon_num_fans);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* WMI Driver
|
||||
*/
|
||||
|
@ -1756,6 +2054,9 @@ static int asus_wmi_add(struct platform_device *pdev)
|
|||
if (err)
|
||||
goto fail_input;
|
||||
|
||||
err = asus_wmi_fan_init(asus); /* probably no problems on error */
|
||||
asus_hwmon_fan_set_auto(asus);
|
||||
|
||||
err = asus_wmi_hwmon_init(asus);
|
||||
if (err)
|
||||
goto fail_hwmon;
|
||||
|
@ -1831,6 +2132,7 @@ static int asus_wmi_remove(struct platform_device *device)
|
|||
asus_wmi_rfkill_exit(asus);
|
||||
asus_wmi_debugfs_exit(asus);
|
||||
asus_wmi_platform_exit(asus);
|
||||
asus_hwmon_fan_set_auto(asus);
|
||||
|
||||
kfree(asus);
|
||||
return 0;
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include <linux/seq_file.h>
|
||||
#include <acpi/video.h>
|
||||
#include "../../firmware/dcdbas.h"
|
||||
#include "dell-rbtn.h"
|
||||
|
||||
#define BRIGHTNESS_TOKEN 0x7d
|
||||
#define KBD_LED_OFF_TOKEN 0x01E1
|
||||
|
@ -643,6 +644,20 @@ static bool dell_laptop_i8042_filter(unsigned char data, unsigned char str,
|
|||
return false;
|
||||
}
|
||||
|
||||
static int (*dell_rbtn_notifier_register_func)(struct notifier_block *);
|
||||
static int (*dell_rbtn_notifier_unregister_func)(struct notifier_block *);
|
||||
|
||||
static int dell_laptop_rbtn_notifier_call(struct notifier_block *nb,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
schedule_delayed_work(&dell_rfkill_work, 0);
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static struct notifier_block dell_laptop_rbtn_notifier = {
|
||||
.notifier_call = dell_laptop_rbtn_notifier_call,
|
||||
};
|
||||
|
||||
static int __init dell_setup_rfkill(void)
|
||||
{
|
||||
int status, ret, whitelisted;
|
||||
|
@ -719,10 +734,62 @@ static int __init dell_setup_rfkill(void)
|
|||
goto err_wwan;
|
||||
}
|
||||
|
||||
ret = i8042_install_filter(dell_laptop_i8042_filter);
|
||||
if (ret) {
|
||||
pr_warn("Unable to install key filter\n");
|
||||
/*
|
||||
* Dell Airplane Mode Switch driver (dell-rbtn) supports ACPI devices
|
||||
* which can receive events from HW slider switch.
|
||||
*
|
||||
* Dell SMBIOS on whitelisted models supports controlling radio devices
|
||||
* but does not support receiving HW button switch events. We can use
|
||||
* i8042 filter hook function to receive keyboard data and handle
|
||||
* keycode for HW button.
|
||||
*
|
||||
* So if it is possible we will use Dell Airplane Mode Switch ACPI
|
||||
* driver for receiving HW events and Dell SMBIOS for setting rfkill
|
||||
* states. If ACPI driver or device is not available we will fallback to
|
||||
* i8042 filter hook function.
|
||||
*
|
||||
* To prevent duplicate rfkill devices which control and do same thing,
|
||||
* dell-rbtn driver will automatically remove its own rfkill devices
|
||||
* once function dell_rbtn_notifier_register() is called.
|
||||
*/
|
||||
|
||||
dell_rbtn_notifier_register_func =
|
||||
symbol_request(dell_rbtn_notifier_register);
|
||||
if (dell_rbtn_notifier_register_func) {
|
||||
dell_rbtn_notifier_unregister_func =
|
||||
symbol_request(dell_rbtn_notifier_unregister);
|
||||
if (!dell_rbtn_notifier_unregister_func) {
|
||||
symbol_put(dell_rbtn_notifier_register);
|
||||
dell_rbtn_notifier_register_func = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (dell_rbtn_notifier_register_func) {
|
||||
ret = dell_rbtn_notifier_register_func(
|
||||
&dell_laptop_rbtn_notifier);
|
||||
symbol_put(dell_rbtn_notifier_register);
|
||||
dell_rbtn_notifier_register_func = NULL;
|
||||
if (ret != 0) {
|
||||
symbol_put(dell_rbtn_notifier_unregister);
|
||||
dell_rbtn_notifier_unregister_func = NULL;
|
||||
}
|
||||
} else {
|
||||
pr_info("Symbols from dell-rbtn acpi driver are not available\n");
|
||||
ret = -ENODEV;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
pr_info("Using dell-rbtn acpi driver for receiving events\n");
|
||||
} else if (ret != -ENODEV) {
|
||||
pr_warn("Unable to register dell rbtn notifier\n");
|
||||
goto err_filter;
|
||||
} else {
|
||||
ret = i8042_install_filter(dell_laptop_i8042_filter);
|
||||
if (ret) {
|
||||
pr_warn("Unable to install key filter\n");
|
||||
goto err_filter;
|
||||
}
|
||||
pr_info("Using i8042 filter function for receiving events\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -745,6 +812,14 @@ err_wifi:
|
|||
|
||||
static void dell_cleanup_rfkill(void)
|
||||
{
|
||||
if (dell_rbtn_notifier_unregister_func) {
|
||||
dell_rbtn_notifier_unregister_func(&dell_laptop_rbtn_notifier);
|
||||
symbol_put(dell_rbtn_notifier_unregister);
|
||||
dell_rbtn_notifier_unregister_func = NULL;
|
||||
} else {
|
||||
i8042_remove_filter(dell_laptop_i8042_filter);
|
||||
}
|
||||
cancel_delayed_work_sync(&dell_rfkill_work);
|
||||
if (wifi_rfkill) {
|
||||
rfkill_unregister(wifi_rfkill);
|
||||
rfkill_destroy(wifi_rfkill);
|
||||
|
@ -1957,8 +2032,6 @@ static int __init dell_init(void)
|
|||
return 0;
|
||||
|
||||
fail_backlight:
|
||||
i8042_remove_filter(dell_laptop_i8042_filter);
|
||||
cancel_delayed_work_sync(&dell_rfkill_work);
|
||||
dell_cleanup_rfkill();
|
||||
fail_rfkill:
|
||||
free_page((unsigned long)bufferpage);
|
||||
|
@ -1979,8 +2052,6 @@ static void __exit dell_exit(void)
|
|||
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);
|
||||
dell_cleanup_rfkill();
|
||||
if (platform_device) {
|
||||
|
@ -1991,7 +2062,14 @@ static void __exit dell_exit(void)
|
|||
free_page((unsigned long)buffer);
|
||||
}
|
||||
|
||||
module_init(dell_init);
|
||||
/* dell-rbtn.c driver export functions which will not work correctly (and could
|
||||
* cause kernel crash) if they are called before dell-rbtn.c init code. This is
|
||||
* not problem when dell-rbtn.c is compiled as external module. When both files
|
||||
* (dell-rbtn.c and dell-laptop.c) are compiled statically into kernel, then we
|
||||
* need to ensure that dell_init() will be called after initializing dell-rbtn.
|
||||
* This can be achieved by late_initcall() instead module_init().
|
||||
*/
|
||||
late_initcall(dell_init);
|
||||
module_exit(dell_exit);
|
||||
|
||||
MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
|
||||
|
|
423
drivers/platform/x86/dell-rbtn.c
Normal file
423
drivers/platform/x86/dell-rbtn.c
Normal file
|
@ -0,0 +1,423 @@
|
|||
/*
|
||||
Dell Airplane Mode Switch driver
|
||||
Copyright (C) 2014-2015 Pali Rohár <pali.rohar@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/rfkill.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
enum rbtn_type {
|
||||
RBTN_UNKNOWN,
|
||||
RBTN_TOGGLE,
|
||||
RBTN_SLIDER,
|
||||
};
|
||||
|
||||
struct rbtn_data {
|
||||
enum rbtn_type type;
|
||||
struct rfkill *rfkill;
|
||||
struct input_dev *input_dev;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* acpi functions
|
||||
*/
|
||||
|
||||
static enum rbtn_type rbtn_check(struct acpi_device *device)
|
||||
{
|
||||
unsigned long long output;
|
||||
acpi_status status;
|
||||
|
||||
status = acpi_evaluate_integer(device->handle, "CRBT", NULL, &output);
|
||||
if (ACPI_FAILURE(status))
|
||||
return RBTN_UNKNOWN;
|
||||
|
||||
switch (output) {
|
||||
case 0:
|
||||
case 1:
|
||||
return RBTN_TOGGLE;
|
||||
case 2:
|
||||
case 3:
|
||||
return RBTN_SLIDER;
|
||||
default:
|
||||
return RBTN_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static int rbtn_get(struct acpi_device *device)
|
||||
{
|
||||
unsigned long long output;
|
||||
acpi_status status;
|
||||
|
||||
status = acpi_evaluate_integer(device->handle, "GRBT", NULL, &output);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EINVAL;
|
||||
|
||||
return !output;
|
||||
}
|
||||
|
||||
static int rbtn_acquire(struct acpi_device *device, bool enable)
|
||||
{
|
||||
struct acpi_object_list input;
|
||||
union acpi_object param;
|
||||
acpi_status status;
|
||||
|
||||
param.type = ACPI_TYPE_INTEGER;
|
||||
param.integer.value = enable;
|
||||
input.count = 1;
|
||||
input.pointer = ¶m;
|
||||
|
||||
status = acpi_evaluate_object(device->handle, "ARBT", &input, NULL);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* rfkill device
|
||||
*/
|
||||
|
||||
static void rbtn_rfkill_query(struct rfkill *rfkill, void *data)
|
||||
{
|
||||
struct acpi_device *device = data;
|
||||
int state;
|
||||
|
||||
state = rbtn_get(device);
|
||||
if (state < 0)
|
||||
return;
|
||||
|
||||
rfkill_set_states(rfkill, state, state);
|
||||
}
|
||||
|
||||
static int rbtn_rfkill_set_block(void *data, bool blocked)
|
||||
{
|
||||
/* NOTE: setting soft rfkill state is not supported */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static struct rfkill_ops rbtn_ops = {
|
||||
.query = rbtn_rfkill_query,
|
||||
.set_block = rbtn_rfkill_set_block,
|
||||
};
|
||||
|
||||
static int rbtn_rfkill_init(struct acpi_device *device)
|
||||
{
|
||||
struct rbtn_data *rbtn_data = device->driver_data;
|
||||
int ret;
|
||||
|
||||
if (rbtn_data->rfkill)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* NOTE: rbtn controls all radio devices, not only WLAN
|
||||
* but rfkill interface does not support "ANY" type
|
||||
* so "WLAN" type is used
|
||||
*/
|
||||
rbtn_data->rfkill = rfkill_alloc("dell-rbtn", &device->dev,
|
||||
RFKILL_TYPE_WLAN, &rbtn_ops, device);
|
||||
if (!rbtn_data->rfkill)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = rfkill_register(rbtn_data->rfkill);
|
||||
if (ret) {
|
||||
rfkill_destroy(rbtn_data->rfkill);
|
||||
rbtn_data->rfkill = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rbtn_rfkill_exit(struct acpi_device *device)
|
||||
{
|
||||
struct rbtn_data *rbtn_data = device->driver_data;
|
||||
|
||||
if (!rbtn_data->rfkill)
|
||||
return;
|
||||
|
||||
rfkill_unregister(rbtn_data->rfkill);
|
||||
rfkill_destroy(rbtn_data->rfkill);
|
||||
rbtn_data->rfkill = NULL;
|
||||
}
|
||||
|
||||
static void rbtn_rfkill_event(struct acpi_device *device)
|
||||
{
|
||||
struct rbtn_data *rbtn_data = device->driver_data;
|
||||
|
||||
if (rbtn_data->rfkill)
|
||||
rbtn_rfkill_query(rbtn_data->rfkill, device);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* input device
|
||||
*/
|
||||
|
||||
static int rbtn_input_init(struct rbtn_data *rbtn_data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
rbtn_data->input_dev = input_allocate_device();
|
||||
if (!rbtn_data->input_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
rbtn_data->input_dev->name = "DELL Wireless hotkeys";
|
||||
rbtn_data->input_dev->phys = "dellabce/input0";
|
||||
rbtn_data->input_dev->id.bustype = BUS_HOST;
|
||||
rbtn_data->input_dev->evbit[0] = BIT(EV_KEY);
|
||||
set_bit(KEY_RFKILL, rbtn_data->input_dev->keybit);
|
||||
|
||||
ret = input_register_device(rbtn_data->input_dev);
|
||||
if (ret) {
|
||||
input_free_device(rbtn_data->input_dev);
|
||||
rbtn_data->input_dev = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rbtn_input_exit(struct rbtn_data *rbtn_data)
|
||||
{
|
||||
input_unregister_device(rbtn_data->input_dev);
|
||||
rbtn_data->input_dev = NULL;
|
||||
}
|
||||
|
||||
static void rbtn_input_event(struct rbtn_data *rbtn_data)
|
||||
{
|
||||
input_report_key(rbtn_data->input_dev, KEY_RFKILL, 1);
|
||||
input_sync(rbtn_data->input_dev);
|
||||
input_report_key(rbtn_data->input_dev, KEY_RFKILL, 0);
|
||||
input_sync(rbtn_data->input_dev);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* acpi driver
|
||||
*/
|
||||
|
||||
static int rbtn_add(struct acpi_device *device);
|
||||
static int rbtn_remove(struct acpi_device *device);
|
||||
static void rbtn_notify(struct acpi_device *device, u32 event);
|
||||
|
||||
static const struct acpi_device_id rbtn_ids[] = {
|
||||
{ "DELRBTN", 0 },
|
||||
{ "DELLABCE", 0 },
|
||||
{ "", 0 },
|
||||
};
|
||||
|
||||
static struct acpi_driver rbtn_driver = {
|
||||
.name = "dell-rbtn",
|
||||
.ids = rbtn_ids,
|
||||
.ops = {
|
||||
.add = rbtn_add,
|
||||
.remove = rbtn_remove,
|
||||
.notify = rbtn_notify,
|
||||
},
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* notifier export functions
|
||||
*/
|
||||
|
||||
static bool auto_remove_rfkill = true;
|
||||
|
||||
static ATOMIC_NOTIFIER_HEAD(rbtn_chain_head);
|
||||
|
||||
static int rbtn_inc_count(struct device *dev, void *data)
|
||||
{
|
||||
struct acpi_device *device = to_acpi_device(dev);
|
||||
struct rbtn_data *rbtn_data = device->driver_data;
|
||||
int *count = data;
|
||||
|
||||
if (rbtn_data->type == RBTN_SLIDER)
|
||||
(*count)++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rbtn_switch_dev(struct device *dev, void *data)
|
||||
{
|
||||
struct acpi_device *device = to_acpi_device(dev);
|
||||
struct rbtn_data *rbtn_data = device->driver_data;
|
||||
bool enable = data;
|
||||
|
||||
if (rbtn_data->type != RBTN_SLIDER)
|
||||
return 0;
|
||||
|
||||
if (enable)
|
||||
rbtn_rfkill_init(device);
|
||||
else
|
||||
rbtn_rfkill_exit(device);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dell_rbtn_notifier_register(struct notifier_block *nb)
|
||||
{
|
||||
bool first;
|
||||
int count;
|
||||
int ret;
|
||||
|
||||
count = 0;
|
||||
ret = driver_for_each_device(&rbtn_driver.drv, NULL, &count,
|
||||
rbtn_inc_count);
|
||||
if (ret || count == 0)
|
||||
return -ENODEV;
|
||||
|
||||
first = !rbtn_chain_head.head;
|
||||
|
||||
ret = atomic_notifier_chain_register(&rbtn_chain_head, nb);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
if (auto_remove_rfkill && first)
|
||||
ret = driver_for_each_device(&rbtn_driver.drv, NULL,
|
||||
(void *)false, rbtn_switch_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dell_rbtn_notifier_register);
|
||||
|
||||
int dell_rbtn_notifier_unregister(struct notifier_block *nb)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = atomic_notifier_chain_unregister(&rbtn_chain_head, nb);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
if (auto_remove_rfkill && !rbtn_chain_head.head)
|
||||
ret = driver_for_each_device(&rbtn_driver.drv, NULL,
|
||||
(void *)true, rbtn_switch_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dell_rbtn_notifier_unregister);
|
||||
|
||||
|
||||
/*
|
||||
* acpi driver functions
|
||||
*/
|
||||
|
||||
static int rbtn_add(struct acpi_device *device)
|
||||
{
|
||||
struct rbtn_data *rbtn_data;
|
||||
enum rbtn_type type;
|
||||
int ret = 0;
|
||||
|
||||
type = rbtn_check(device);
|
||||
if (type == RBTN_UNKNOWN) {
|
||||
dev_info(&device->dev, "Unknown device type\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = rbtn_acquire(device, true);
|
||||
if (ret < 0) {
|
||||
dev_err(&device->dev, "Cannot enable device\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL);
|
||||
if (!rbtn_data)
|
||||
return -ENOMEM;
|
||||
|
||||
rbtn_data->type = type;
|
||||
device->driver_data = rbtn_data;
|
||||
|
||||
switch (rbtn_data->type) {
|
||||
case RBTN_TOGGLE:
|
||||
ret = rbtn_input_init(rbtn_data);
|
||||
break;
|
||||
case RBTN_SLIDER:
|
||||
if (auto_remove_rfkill && rbtn_chain_head.head)
|
||||
ret = 0;
|
||||
else
|
||||
ret = rbtn_rfkill_init(device);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
static int rbtn_remove(struct acpi_device *device)
|
||||
{
|
||||
struct rbtn_data *rbtn_data = device->driver_data;
|
||||
|
||||
switch (rbtn_data->type) {
|
||||
case RBTN_TOGGLE:
|
||||
rbtn_input_exit(rbtn_data);
|
||||
break;
|
||||
case RBTN_SLIDER:
|
||||
rbtn_rfkill_exit(device);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
rbtn_acquire(device, false);
|
||||
device->driver_data = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rbtn_notify(struct acpi_device *device, u32 event)
|
||||
{
|
||||
struct rbtn_data *rbtn_data = device->driver_data;
|
||||
|
||||
if (event != 0x80) {
|
||||
dev_info(&device->dev, "Received unknown event (0x%x)\n",
|
||||
event);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (rbtn_data->type) {
|
||||
case RBTN_TOGGLE:
|
||||
rbtn_input_event(rbtn_data);
|
||||
break;
|
||||
case RBTN_SLIDER:
|
||||
rbtn_rfkill_event(device);
|
||||
atomic_notifier_call_chain(&rbtn_chain_head, event, device);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* module functions
|
||||
*/
|
||||
|
||||
module_acpi_driver(rbtn_driver);
|
||||
|
||||
module_param(auto_remove_rfkill, bool, 0444);
|
||||
|
||||
MODULE_PARM_DESC(auto_remove_rfkill, "Automatically remove rfkill devices when "
|
||||
"other modules start receiving events "
|
||||
"from this module and re-add them when "
|
||||
"the last module stops receiving events "
|
||||
"(default true)");
|
||||
MODULE_DEVICE_TABLE(acpi, rbtn_ids);
|
||||
MODULE_DESCRIPTION("Dell Airplane Mode Switch driver");
|
||||
MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
|
||||
MODULE_LICENSE("GPL");
|
24
drivers/platform/x86/dell-rbtn.h
Normal file
24
drivers/platform/x86/dell-rbtn.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
Dell Airplane Mode Switch driver
|
||||
Copyright (C) 2014-2015 Pali Rohár <pali.rohar@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef _DELL_RBTN_H_
|
||||
#define _DELL_RBTN_H_
|
||||
|
||||
struct notifier_block;
|
||||
|
||||
int dell_rbtn_notifier_register(struct notifier_block *nb);
|
||||
int dell_rbtn_notifier_unregister(struct notifier_block *nb);
|
||||
|
||||
#endif
|
|
@ -465,8 +465,9 @@ static const struct ideapad_rfk_data ideapad_rfk_data[] = {
|
|||
static int ideapad_rfk_set(void *data, bool blocked)
|
||||
{
|
||||
struct ideapad_rfk_priv *priv = data;
|
||||
int opcode = ideapad_rfk_data[priv->dev].opcode;
|
||||
|
||||
return write_ec_cmd(priv->priv->adev->handle, priv->dev, !blocked);
|
||||
return write_ec_cmd(priv->priv->adev->handle, opcode, !blocked);
|
||||
}
|
||||
|
||||
static struct rfkill_ops ideapad_rfk_ops = {
|
||||
|
@ -837,6 +838,13 @@ static const struct dmi_system_id no_hw_rfkill_list[] = {
|
|||
DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G40-30"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Lenovo G50-30",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G50-30"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Lenovo Yoga 2 11 / 13 / Pro",
|
||||
.matches = {
|
||||
|
|
|
@ -92,13 +92,13 @@ pvpanic_walk_resources(struct acpi_resource *res, void *context)
|
|||
|
||||
static int pvpanic_add(struct acpi_device *device)
|
||||
{
|
||||
acpi_status status;
|
||||
u64 ret;
|
||||
int ret;
|
||||
|
||||
status = acpi_evaluate_integer(device->handle, "_STA", NULL,
|
||||
&ret);
|
||||
ret = acpi_bus_get_status(device);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (ACPI_FAILURE(status) || (ret & 0x0B) != 0x0B)
|
||||
if (!device->status.enabled || !device->status.functional)
|
||||
return -ENODEV;
|
||||
|
||||
acpi_walk_resources(device->handle, METHOD_NAME__CRS,
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#define TOSHIBA_ACPI_VERSION "0.21"
|
||||
#define TOSHIBA_ACPI_VERSION "0.22"
|
||||
#define PROC_INTERFACE_VERSION 1
|
||||
|
||||
#include <linux/kernel.h>
|
||||
|
@ -41,7 +41,6 @@
|
|||
#include <linux/proc_fs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/backlight.h>
|
||||
#include <linux/rfkill.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/input/sparse-keymap.h>
|
||||
#include <linux/leds.h>
|
||||
|
@ -82,7 +81,7 @@ MODULE_LICENSE("GPL");
|
|||
|
||||
#define TCI_WORDS 6
|
||||
|
||||
/* operations */
|
||||
/* Operations */
|
||||
#define HCI_SET 0xff00
|
||||
#define HCI_GET 0xfe00
|
||||
#define SCI_OPEN 0xf100
|
||||
|
@ -90,7 +89,7 @@ MODULE_LICENSE("GPL");
|
|||
#define SCI_GET 0xf300
|
||||
#define SCI_SET 0xf400
|
||||
|
||||
/* return codes */
|
||||
/* Return codes */
|
||||
#define TOS_SUCCESS 0x0000
|
||||
#define TOS_OPEN_CLOSE_OK 0x0044
|
||||
#define TOS_FAILURE 0x1000
|
||||
|
@ -105,7 +104,7 @@ MODULE_LICENSE("GPL");
|
|||
#define TOS_NOT_INITIALIZED 0x8d50
|
||||
#define TOS_NOT_INSTALLED 0x8e00
|
||||
|
||||
/* registers */
|
||||
/* Registers */
|
||||
#define HCI_FAN 0x0004
|
||||
#define HCI_TR_BACKLIGHT 0x0005
|
||||
#define HCI_SYSTEM_EVENT 0x0016
|
||||
|
@ -127,7 +126,7 @@ MODULE_LICENSE("GPL");
|
|||
#define SCI_TOUCHPAD 0x050e
|
||||
#define SCI_KBD_FUNCTION_KEYS 0x0522
|
||||
|
||||
/* field definitions */
|
||||
/* Field definitions */
|
||||
#define HCI_ACCEL_MASK 0x7fff
|
||||
#define HCI_HOTKEY_DISABLE 0x0b
|
||||
#define HCI_HOTKEY_ENABLE 0x09
|
||||
|
@ -165,7 +164,6 @@ MODULE_LICENSE("GPL");
|
|||
struct toshiba_acpi_dev {
|
||||
struct acpi_device *acpi_dev;
|
||||
const char *method_hci;
|
||||
struct rfkill *bt_rfk;
|
||||
struct input_dev *hotkey_dev;
|
||||
struct work_struct hotkey_work;
|
||||
struct backlight_device *backlight_dev;
|
||||
|
@ -202,8 +200,6 @@ struct toshiba_acpi_dev {
|
|||
unsigned int panel_power_on_supported:1;
|
||||
unsigned int usb_three_supported:1;
|
||||
unsigned int sysfs_created:1;
|
||||
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
||||
static struct toshiba_acpi_dev *toshiba_acpi;
|
||||
|
@ -330,13 +326,13 @@ static acpi_status tci_raw(struct toshiba_acpi_dev *dev,
|
|||
}
|
||||
|
||||
/*
|
||||
* Common hci tasks (get or set one or two value)
|
||||
* Common hci tasks
|
||||
*
|
||||
* In addition to the ACPI status, the HCI system returns a result which
|
||||
* may be useful (such as "not supported").
|
||||
*/
|
||||
|
||||
static u32 hci_write1(struct toshiba_acpi_dev *dev, u32 reg, u32 in1)
|
||||
static u32 hci_write(struct toshiba_acpi_dev *dev, u32 reg, u32 in1)
|
||||
{
|
||||
u32 in[TCI_WORDS] = { HCI_SET, reg, in1, 0, 0, 0 };
|
||||
u32 out[TCI_WORDS];
|
||||
|
@ -345,7 +341,7 @@ static u32 hci_write1(struct toshiba_acpi_dev *dev, u32 reg, u32 in1)
|
|||
return ACPI_SUCCESS(status) ? out[0] : TOS_FAILURE;
|
||||
}
|
||||
|
||||
static u32 hci_read1(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1)
|
||||
static u32 hci_read(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1)
|
||||
{
|
||||
u32 in[TCI_WORDS] = { HCI_GET, reg, 0, 0, 0, 0 };
|
||||
u32 out[TCI_WORDS];
|
||||
|
@ -359,31 +355,6 @@ static u32 hci_read1(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1)
|
|||
return out[0];
|
||||
}
|
||||
|
||||
static u32 hci_write2(struct toshiba_acpi_dev *dev, u32 reg, u32 in1, u32 in2)
|
||||
{
|
||||
u32 in[TCI_WORDS] = { HCI_SET, reg, in1, in2, 0, 0 };
|
||||
u32 out[TCI_WORDS];
|
||||
acpi_status status = tci_raw(dev, in, out);
|
||||
|
||||
return ACPI_SUCCESS(status) ? out[0] : TOS_FAILURE;
|
||||
}
|
||||
|
||||
static u32 hci_read2(struct toshiba_acpi_dev *dev,
|
||||
u32 reg, u32 *out1, u32 *out2)
|
||||
{
|
||||
u32 in[TCI_WORDS] = { HCI_GET, reg, *out1, *out2, 0, 0 };
|
||||
u32 out[TCI_WORDS];
|
||||
acpi_status status = tci_raw(dev, in, out);
|
||||
|
||||
if (ACPI_FAILURE(status))
|
||||
return TOS_FAILURE;
|
||||
|
||||
*out1 = out[2];
|
||||
*out2 = out[3];
|
||||
|
||||
return out[0];
|
||||
}
|
||||
|
||||
/*
|
||||
* Common sci tasks
|
||||
*/
|
||||
|
@ -395,7 +366,7 @@ static int sci_open(struct toshiba_acpi_dev *dev)
|
|||
acpi_status status;
|
||||
|
||||
status = tci_raw(dev, in, out);
|
||||
if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) {
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_err("ACPI call to open SCI failed\n");
|
||||
return 0;
|
||||
}
|
||||
|
@ -433,7 +404,7 @@ static void sci_close(struct toshiba_acpi_dev *dev)
|
|||
acpi_status status;
|
||||
|
||||
status = tci_raw(dev, in, out);
|
||||
if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) {
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_err("ACPI call to close SCI failed\n");
|
||||
return;
|
||||
}
|
||||
|
@ -481,7 +452,7 @@ static int toshiba_illumination_available(struct toshiba_acpi_dev *dev)
|
|||
|
||||
status = tci_raw(dev, in, out);
|
||||
sci_close(dev);
|
||||
if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) {
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_err("ACPI call to query Illumination support failed\n");
|
||||
return 0;
|
||||
} else if (out[0] == TOS_NOT_SUPPORTED) {
|
||||
|
@ -522,7 +493,7 @@ static enum led_brightness toshiba_illumination_get(struct led_classdev *cdev)
|
|||
struct toshiba_acpi_dev, led_dev);
|
||||
u32 state, result;
|
||||
|
||||
/* First request : initialize communication. */
|
||||
/* First request : initialize communication. */
|
||||
if (!sci_open(dev))
|
||||
return LED_OFF;
|
||||
|
||||
|
@ -625,7 +596,7 @@ static enum led_brightness toshiba_kbd_backlight_get(struct led_classdev *cdev)
|
|||
u32 state, result;
|
||||
|
||||
/* Check the keyboard backlight state */
|
||||
result = hci_read1(dev, HCI_KBD_ILLUMINATION, &state);
|
||||
result = hci_read(dev, HCI_KBD_ILLUMINATION, &state);
|
||||
if (result == TOS_FAILURE || result == TOS_INPUT_DATA_ERROR) {
|
||||
pr_err("ACPI call to get the keyboard backlight failed\n");
|
||||
return LED_OFF;
|
||||
|
@ -646,7 +617,7 @@ static void toshiba_kbd_backlight_set(struct led_classdev *cdev,
|
|||
|
||||
/* Set the keyboard backlight state */
|
||||
state = brightness ? 1 : 0;
|
||||
result = hci_write1(dev, HCI_KBD_ILLUMINATION, state);
|
||||
result = hci_write(dev, HCI_KBD_ILLUMINATION, state);
|
||||
if (result == TOS_FAILURE || result == TOS_INPUT_DATA_ERROR) {
|
||||
pr_err("ACPI call to set KBD Illumination mode failed\n");
|
||||
return;
|
||||
|
@ -703,7 +674,7 @@ static int toshiba_eco_mode_available(struct toshiba_acpi_dev *dev)
|
|||
u32 out[TCI_WORDS];
|
||||
|
||||
status = tci_raw(dev, in, out);
|
||||
if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) {
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_err("ACPI call to get ECO led failed\n");
|
||||
} else if (out[0] == TOS_NOT_INSTALLED) {
|
||||
pr_info("ECO led not installed");
|
||||
|
@ -825,7 +796,7 @@ static void toshiba_usb_sleep_charge_available(struct toshiba_acpi_dev *dev)
|
|||
return;
|
||||
|
||||
status = tci_raw(dev, in, out);
|
||||
if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) {
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_err("ACPI call to get USB Sleep and Charge mode failed\n");
|
||||
sci_close(dev);
|
||||
return;
|
||||
|
@ -839,7 +810,7 @@ static void toshiba_usb_sleep_charge_available(struct toshiba_acpi_dev *dev)
|
|||
|
||||
in[5] = SCI_USB_CHARGE_BAT_LVL;
|
||||
status = tci_raw(dev, in, out);
|
||||
if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) {
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_err("ACPI call to get USB Sleep and Charge mode failed\n");
|
||||
sci_close(dev);
|
||||
return;
|
||||
|
@ -919,7 +890,7 @@ static int toshiba_sleep_functions_status_get(struct toshiba_acpi_dev *dev,
|
|||
in[5] = SCI_USB_CHARGE_BAT_LVL;
|
||||
status = tci_raw(dev, in, out);
|
||||
sci_close(dev);
|
||||
if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) {
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_err("ACPI call to get USB S&C battery level failed\n");
|
||||
return -EIO;
|
||||
} else if (out[0] == TOS_NOT_SUPPORTED) {
|
||||
|
@ -948,7 +919,7 @@ static int toshiba_sleep_functions_status_set(struct toshiba_acpi_dev *dev,
|
|||
in[5] = SCI_USB_CHARGE_BAT_LVL;
|
||||
status = tci_raw(dev, in, out);
|
||||
sci_close(dev);
|
||||
if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) {
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_err("ACPI call to set USB S&C battery level failed\n");
|
||||
return -EIO;
|
||||
} else if (out[0] == TOS_NOT_SUPPORTED) {
|
||||
|
@ -974,7 +945,7 @@ static int toshiba_usb_rapid_charge_get(struct toshiba_acpi_dev *dev,
|
|||
in[5] = SCI_USB_CHARGE_RAPID_DSP;
|
||||
status = tci_raw(dev, in, out);
|
||||
sci_close(dev);
|
||||
if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) {
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_err("ACPI call to get USB Rapid Charge failed\n");
|
||||
return -EIO;
|
||||
} else if (out[0] == TOS_NOT_SUPPORTED ||
|
||||
|
@ -1002,7 +973,7 @@ static int toshiba_usb_rapid_charge_set(struct toshiba_acpi_dev *dev,
|
|||
in[5] = SCI_USB_CHARGE_RAPID_DSP;
|
||||
status = tci_raw(dev, in, out);
|
||||
sci_close(dev);
|
||||
if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) {
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_err("ACPI call to set USB Rapid Charge failed\n");
|
||||
return -EIO;
|
||||
} else if (out[0] == TOS_NOT_SUPPORTED) {
|
||||
|
@ -1194,121 +1165,31 @@ static int toshiba_usb_three_set(struct toshiba_acpi_dev *dev, u32 state)
|
|||
static int toshiba_hotkey_event_type_get(struct toshiba_acpi_dev *dev,
|
||||
u32 *type)
|
||||
{
|
||||
u32 val1 = 0x03;
|
||||
u32 val2 = 0;
|
||||
u32 result;
|
||||
u32 in[TCI_WORDS] = { HCI_GET, HCI_SYSTEM_INFO, 0x03, 0, 0, 0 };
|
||||
u32 out[TCI_WORDS];
|
||||
acpi_status status;
|
||||
|
||||
result = hci_read2(dev, HCI_SYSTEM_INFO, &val1, &val2);
|
||||
if (result == TOS_FAILURE) {
|
||||
status = tci_raw(dev, in, out);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_err("ACPI call to get System type failed\n");
|
||||
return -EIO;
|
||||
} else if (result == TOS_NOT_SUPPORTED) {
|
||||
} else if (out[0] == TOS_NOT_SUPPORTED) {
|
||||
pr_info("System type not supported\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
*type = val2;
|
||||
*type = out[3];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Bluetooth rfkill handlers */
|
||||
|
||||
static u32 hci_get_bt_present(struct toshiba_acpi_dev *dev, bool *present)
|
||||
{
|
||||
u32 hci_result;
|
||||
u32 value, value2;
|
||||
|
||||
value = 0;
|
||||
value2 = 0;
|
||||
hci_result = hci_read2(dev, HCI_WIRELESS, &value, &value2);
|
||||
if (hci_result == TOS_SUCCESS)
|
||||
*present = (value & HCI_WIRELESS_BT_PRESENT) ? true : false;
|
||||
|
||||
return hci_result;
|
||||
}
|
||||
|
||||
static u32 hci_get_radio_state(struct toshiba_acpi_dev *dev, bool *radio_state)
|
||||
{
|
||||
u32 hci_result;
|
||||
u32 value, value2;
|
||||
|
||||
value = 0;
|
||||
value2 = 0x0001;
|
||||
hci_result = hci_read2(dev, HCI_WIRELESS, &value, &value2);
|
||||
|
||||
*radio_state = value & HCI_WIRELESS_KILL_SWITCH;
|
||||
return hci_result;
|
||||
}
|
||||
|
||||
static int bt_rfkill_set_block(void *data, bool blocked)
|
||||
{
|
||||
struct toshiba_acpi_dev *dev = data;
|
||||
u32 result1, result2;
|
||||
u32 value;
|
||||
int err;
|
||||
bool radio_state;
|
||||
|
||||
value = (blocked == false);
|
||||
|
||||
mutex_lock(&dev->mutex);
|
||||
if (hci_get_radio_state(dev, &radio_state) != TOS_SUCCESS) {
|
||||
err = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!radio_state) {
|
||||
err = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
result1 = hci_write2(dev, HCI_WIRELESS, value, HCI_WIRELESS_BT_POWER);
|
||||
result2 = hci_write2(dev, HCI_WIRELESS, value, HCI_WIRELESS_BT_ATTACH);
|
||||
|
||||
if (result1 != TOS_SUCCESS || result2 != TOS_SUCCESS)
|
||||
err = -EIO;
|
||||
else
|
||||
err = 0;
|
||||
out:
|
||||
mutex_unlock(&dev->mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void bt_rfkill_poll(struct rfkill *rfkill, void *data)
|
||||
{
|
||||
bool new_rfk_state;
|
||||
bool value;
|
||||
u32 hci_result;
|
||||
struct toshiba_acpi_dev *dev = data;
|
||||
|
||||
mutex_lock(&dev->mutex);
|
||||
|
||||
hci_result = hci_get_radio_state(dev, &value);
|
||||
if (hci_result != TOS_SUCCESS) {
|
||||
/* Can't do anything useful */
|
||||
mutex_unlock(&dev->mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
new_rfk_state = value;
|
||||
|
||||
mutex_unlock(&dev->mutex);
|
||||
|
||||
if (rfkill_set_hw_state(rfkill, !new_rfk_state))
|
||||
bt_rfkill_set_block(data, true);
|
||||
}
|
||||
|
||||
static const struct rfkill_ops toshiba_rfk_ops = {
|
||||
.set_block = bt_rfkill_set_block,
|
||||
.poll = bt_rfkill_poll,
|
||||
};
|
||||
|
||||
/* Transflective Backlight */
|
||||
static int get_tr_backlight_status(struct toshiba_acpi_dev *dev, bool *enabled)
|
||||
{
|
||||
u32 hci_result;
|
||||
u32 status;
|
||||
|
||||
hci_result = hci_read1(dev, HCI_TR_BACKLIGHT, &status);
|
||||
hci_result = hci_read(dev, HCI_TR_BACKLIGHT, &status);
|
||||
*enabled = !status;
|
||||
return hci_result == TOS_SUCCESS ? 0 : -EIO;
|
||||
}
|
||||
|
@ -1318,12 +1199,13 @@ static int set_tr_backlight_status(struct toshiba_acpi_dev *dev, bool enable)
|
|||
u32 hci_result;
|
||||
u32 value = !enable;
|
||||
|
||||
hci_result = hci_write1(dev, HCI_TR_BACKLIGHT, value);
|
||||
hci_result = hci_write(dev, HCI_TR_BACKLIGHT, value);
|
||||
return hci_result == TOS_SUCCESS ? 0 : -EIO;
|
||||
}
|
||||
|
||||
static struct proc_dir_entry *toshiba_proc_dir /*= 0*/;
|
||||
static struct proc_dir_entry *toshiba_proc_dir;
|
||||
|
||||
/* LCD Brightness */
|
||||
static int __get_lcd_brightness(struct toshiba_acpi_dev *dev)
|
||||
{
|
||||
u32 hci_result;
|
||||
|
@ -1341,7 +1223,7 @@ static int __get_lcd_brightness(struct toshiba_acpi_dev *dev)
|
|||
brightness++;
|
||||
}
|
||||
|
||||
hci_result = hci_read1(dev, HCI_LCD_BRIGHTNESS, &value);
|
||||
hci_result = hci_read(dev, HCI_LCD_BRIGHTNESS, &value);
|
||||
if (hci_result == TOS_SUCCESS)
|
||||
return brightness + (value >> HCI_LCD_BRIGHTNESS_SHIFT);
|
||||
|
||||
|
@ -1396,7 +1278,7 @@ static int set_lcd_brightness(struct toshiba_acpi_dev *dev, int value)
|
|||
}
|
||||
|
||||
value = value << HCI_LCD_BRIGHTNESS_SHIFT;
|
||||
hci_result = hci_write1(dev, HCI_LCD_BRIGHTNESS, value);
|
||||
hci_result = hci_write(dev, HCI_LCD_BRIGHTNESS, value);
|
||||
return hci_result == TOS_SUCCESS ? 0 : -EIO;
|
||||
}
|
||||
|
||||
|
@ -1446,7 +1328,7 @@ static int get_video_status(struct toshiba_acpi_dev *dev, u32 *status)
|
|||
{
|
||||
u32 hci_result;
|
||||
|
||||
hci_result = hci_read1(dev, HCI_VIDEO_OUT, status);
|
||||
hci_result = hci_read(dev, HCI_VIDEO_OUT, status);
|
||||
return hci_result == TOS_SUCCESS ? 0 : -EIO;
|
||||
}
|
||||
|
||||
|
@ -1531,7 +1413,8 @@ static ssize_t video_proc_write(struct file *file, const char __user *buf,
|
|||
_set_bit(&new_video_out, HCI_VIDEO_OUT_TV, tv_out);
|
||||
/*
|
||||
* To avoid unnecessary video disruption, only write the new
|
||||
* video setting if something changed. */
|
||||
* video setting if something changed.
|
||||
*/
|
||||
if (new_video_out != video_out)
|
||||
ret = write_acpi_int(METHOD_VIDEO_OUT, new_video_out);
|
||||
}
|
||||
|
@ -1552,7 +1435,7 @@ static int get_fan_status(struct toshiba_acpi_dev *dev, u32 *status)
|
|||
{
|
||||
u32 hci_result;
|
||||
|
||||
hci_result = hci_read1(dev, HCI_FAN, status);
|
||||
hci_result = hci_read(dev, HCI_FAN, status);
|
||||
return hci_result == TOS_SUCCESS ? 0 : -EIO;
|
||||
}
|
||||
|
||||
|
@ -1592,7 +1475,7 @@ static ssize_t fan_proc_write(struct file *file, const char __user *buf,
|
|||
|
||||
if (sscanf(cmd, " force_on : %i", &value) == 1 &&
|
||||
value >= 0 && value <= 1) {
|
||||
hci_result = hci_write1(dev, HCI_FAN, value);
|
||||
hci_result = hci_write(dev, HCI_FAN, value);
|
||||
if (hci_result == TOS_SUCCESS)
|
||||
dev->force_fan = value;
|
||||
else
|
||||
|
@ -1620,7 +1503,7 @@ static int keys_proc_show(struct seq_file *m, void *v)
|
|||
u32 value;
|
||||
|
||||
if (!dev->key_event_valid && dev->system_event_supported) {
|
||||
hci_result = hci_read1(dev, HCI_SYSTEM_EVENT, &value);
|
||||
hci_result = hci_read(dev, HCI_SYSTEM_EVENT, &value);
|
||||
if (hci_result == TOS_SUCCESS) {
|
||||
dev->key_event_valid = 1;
|
||||
dev->last_key_event = value;
|
||||
|
@ -1632,7 +1515,7 @@ static int keys_proc_show(struct seq_file *m, void *v)
|
|||
* some machines where system events sporadically
|
||||
* become disabled.
|
||||
*/
|
||||
hci_result = hci_write1(dev, HCI_SYSTEM_EVENT, 1);
|
||||
hci_result = hci_write(dev, HCI_SYSTEM_EVENT, 1);
|
||||
pr_notice("Re-enabled hotkeys\n");
|
||||
} else {
|
||||
pr_err("Error reading hotkey status\n");
|
||||
|
@ -1769,7 +1652,7 @@ static ssize_t fan_store(struct device *dev,
|
|||
if (state != 0 && state != 1)
|
||||
return -EINVAL;
|
||||
|
||||
result = hci_write1(toshiba, HCI_FAN, state);
|
||||
result = hci_write(toshiba, HCI_FAN, state);
|
||||
if (result == TOS_FAILURE)
|
||||
return -EIO;
|
||||
else if (result == TOS_NOT_SUPPORTED)
|
||||
|
@ -2391,7 +2274,7 @@ static int toshiba_acpi_enable_hotkeys(struct toshiba_acpi_dev *dev)
|
|||
if (ACPI_FAILURE(status))
|
||||
return -ENODEV;
|
||||
|
||||
result = hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE);
|
||||
result = hci_write(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE);
|
||||
if (result == TOS_FAILURE)
|
||||
return -EIO;
|
||||
else if (result == TOS_NOT_SUPPORTED)
|
||||
|
@ -2408,8 +2291,8 @@ static void toshiba_acpi_enable_special_functions(struct toshiba_acpi_dev *dev)
|
|||
* 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);
|
||||
result = hci_write(dev, HCI_HOTKEY_EVENT,
|
||||
HCI_HOTKEY_SPECIAL_FUNCTIONS);
|
||||
if (result != TOS_SUCCESS)
|
||||
pr_err("Could not enable the Special Function mode\n");
|
||||
}
|
||||
|
@ -2490,7 +2373,7 @@ static void toshiba_acpi_process_hotkeys(struct toshiba_acpi_dev *dev)
|
|||
toshiba_acpi_report_hotkey(dev, scancode);
|
||||
} else if (dev->system_event_supported) {
|
||||
do {
|
||||
hci_result = hci_read1(dev, HCI_SYSTEM_EVENT, &value);
|
||||
hci_result = hci_read(dev, HCI_SYSTEM_EVENT, &value);
|
||||
switch (hci_result) {
|
||||
case TOS_SUCCESS:
|
||||
toshiba_acpi_report_hotkey(dev, (int)value);
|
||||
|
@ -2502,7 +2385,7 @@ static void toshiba_acpi_process_hotkeys(struct toshiba_acpi_dev *dev)
|
|||
* sporadically become disabled.
|
||||
*/
|
||||
hci_result =
|
||||
hci_write1(dev, HCI_SYSTEM_EVENT, 1);
|
||||
hci_write(dev, HCI_SYSTEM_EVENT, 1);
|
||||
pr_notice("Re-enabled hotkeys\n");
|
||||
/* Fall through */
|
||||
default:
|
||||
|
@ -2579,7 +2462,7 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev)
|
|||
if (acpi_has_method(dev->acpi_dev->handle, "INFO"))
|
||||
dev->info_supported = 1;
|
||||
else {
|
||||
hci_result = hci_write1(dev, HCI_SYSTEM_EVENT, 1);
|
||||
hci_result = hci_write(dev, HCI_SYSTEM_EVENT, 1);
|
||||
if (hci_result == TOS_SUCCESS)
|
||||
dev->system_event_supported = 1;
|
||||
}
|
||||
|
@ -2689,11 +2572,6 @@ static int toshiba_acpi_remove(struct acpi_device *acpi_dev)
|
|||
sparse_keymap_free(dev->hotkey_dev);
|
||||
}
|
||||
|
||||
if (dev->bt_rfk) {
|
||||
rfkill_unregister(dev->bt_rfk);
|
||||
rfkill_destroy(dev->bt_rfk);
|
||||
}
|
||||
|
||||
backlight_device_unregister(dev->backlight_dev);
|
||||
|
||||
if (dev->illumination_supported)
|
||||
|
@ -2730,7 +2608,6 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
|
|||
const char *hci_method;
|
||||
u32 special_functions;
|
||||
u32 dummy;
|
||||
bool bt_present;
|
||||
int ret = 0;
|
||||
|
||||
if (toshiba_acpi)
|
||||
|
@ -2766,33 +2643,10 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
|
|||
if (toshiba_acpi_setup_keyboard(dev))
|
||||
pr_info("Unable to activate hotkeys\n");
|
||||
|
||||
mutex_init(&dev->mutex);
|
||||
|
||||
ret = toshiba_acpi_setup_backlight(dev);
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
/* Register rfkill switch for Bluetooth */
|
||||
if (hci_get_bt_present(dev, &bt_present) == TOS_SUCCESS && bt_present) {
|
||||
dev->bt_rfk = rfkill_alloc("Toshiba Bluetooth",
|
||||
&acpi_dev->dev,
|
||||
RFKILL_TYPE_BLUETOOTH,
|
||||
&toshiba_rfk_ops,
|
||||
dev);
|
||||
if (!dev->bt_rfk) {
|
||||
pr_err("unable to allocate rfkill device\n");
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = rfkill_register(dev->bt_rfk);
|
||||
if (ret) {
|
||||
pr_err("unable to register rfkill device\n");
|
||||
rfkill_destroy(dev->bt_rfk);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if (toshiba_illumination_available(dev)) {
|
||||
dev->led_dev.name = "toshiba::illumination";
|
||||
dev->led_dev.max_brightness = 1;
|
||||
|
@ -2930,7 +2784,7 @@ static int toshiba_acpi_suspend(struct device *device)
|
|||
u32 result;
|
||||
|
||||
if (dev->hotkey_dev)
|
||||
result = hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_DISABLE);
|
||||
result = hci_write(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_DISABLE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -10,12 +10,6 @@
|
|||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Note the Toshiba Bluetooth RFKill switch seems to be a strange
|
||||
* fish. It only provides a BT event when the switch is flipped to
|
||||
* the 'on' position. When flipping it to 'off', the USB device is
|
||||
* simply pulled away underneath us, without any BT event being
|
||||
* delivered.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
@ -25,6 +19,7 @@
|
|||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/rfkill.h>
|
||||
|
||||
#define BT_KILLSWITCH_MASK 0x01
|
||||
#define BT_PLUGGED_MASK 0x40
|
||||
|
@ -34,6 +29,15 @@ MODULE_AUTHOR("Jes Sorensen <Jes.Sorensen@gmail.com>");
|
|||
MODULE_DESCRIPTION("Toshiba Laptop ACPI Bluetooth Enable Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
struct toshiba_bluetooth_dev {
|
||||
struct acpi_device *acpi_dev;
|
||||
struct rfkill *rfk;
|
||||
|
||||
bool killswitch;
|
||||
bool plugged;
|
||||
bool powered;
|
||||
};
|
||||
|
||||
static int toshiba_bt_rfkill_add(struct acpi_device *device);
|
||||
static int toshiba_bt_rfkill_remove(struct acpi_device *device);
|
||||
static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event);
|
||||
|
@ -95,41 +99,12 @@ static int toshiba_bluetooth_status(acpi_handle handle)
|
|||
return -ENXIO;
|
||||
}
|
||||
|
||||
pr_info("Bluetooth status %llu\n", status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int toshiba_bluetooth_enable(acpi_handle handle)
|
||||
{
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
|
||||
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)) {
|
||||
|
@ -165,20 +140,102 @@ static int toshiba_bluetooth_disable(acpi_handle handle)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Helper function */
|
||||
static int toshiba_bluetooth_sync_status(struct toshiba_bluetooth_dev *bt_dev)
|
||||
{
|
||||
int status;
|
||||
|
||||
status = toshiba_bluetooth_status(bt_dev->acpi_dev->handle);
|
||||
if (status < 0) {
|
||||
pr_err("Could not sync bluetooth device status\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
bt_dev->killswitch = (status & BT_KILLSWITCH_MASK) ? true : false;
|
||||
bt_dev->plugged = (status & BT_PLUGGED_MASK) ? true : false;
|
||||
bt_dev->powered = (status & BT_POWER_MASK) ? true : false;
|
||||
|
||||
pr_debug("Bluetooth status %d killswitch %d plugged %d powered %d\n",
|
||||
status, bt_dev->killswitch, bt_dev->plugged, bt_dev->powered);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* RFKill handlers */
|
||||
static int bt_rfkill_set_block(void *data, bool blocked)
|
||||
{
|
||||
struct toshiba_bluetooth_dev *bt_dev = data;
|
||||
int ret;
|
||||
|
||||
ret = toshiba_bluetooth_sync_status(bt_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!bt_dev->killswitch)
|
||||
return 0;
|
||||
|
||||
if (blocked)
|
||||
ret = toshiba_bluetooth_disable(bt_dev->acpi_dev->handle);
|
||||
else
|
||||
ret = toshiba_bluetooth_enable(bt_dev->acpi_dev->handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void bt_rfkill_poll(struct rfkill *rfkill, void *data)
|
||||
{
|
||||
struct toshiba_bluetooth_dev *bt_dev = data;
|
||||
|
||||
if (toshiba_bluetooth_sync_status(bt_dev))
|
||||
return;
|
||||
|
||||
/*
|
||||
* Note the Toshiba Bluetooth RFKill switch seems to be a strange
|
||||
* fish. It only provides a BT event when the switch is flipped to
|
||||
* the 'on' position. When flipping it to 'off', the USB device is
|
||||
* simply pulled away underneath us, without any BT event being
|
||||
* delivered.
|
||||
*/
|
||||
rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch);
|
||||
}
|
||||
|
||||
static const struct rfkill_ops rfk_ops = {
|
||||
.set_block = bt_rfkill_set_block,
|
||||
.poll = bt_rfkill_poll,
|
||||
};
|
||||
|
||||
/* ACPI driver functions */
|
||||
static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event)
|
||||
{
|
||||
toshiba_bluetooth_enable(device->handle);
|
||||
struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(device);
|
||||
|
||||
if (toshiba_bluetooth_sync_status(bt_dev))
|
||||
return;
|
||||
|
||||
rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int toshiba_bt_resume(struct device *dev)
|
||||
{
|
||||
return toshiba_bluetooth_enable(to_acpi_device(dev)->handle);
|
||||
struct toshiba_bluetooth_dev *bt_dev;
|
||||
int ret;
|
||||
|
||||
bt_dev = acpi_driver_data(to_acpi_device(dev));
|
||||
|
||||
ret = toshiba_bluetooth_sync_status(bt_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int toshiba_bt_rfkill_add(struct acpi_device *device)
|
||||
{
|
||||
struct toshiba_bluetooth_dev *bt_dev;
|
||||
int result;
|
||||
|
||||
result = toshiba_bluetooth_present(device->handle);
|
||||
|
@ -187,17 +244,54 @@ static int toshiba_bt_rfkill_add(struct acpi_device *device)
|
|||
|
||||
pr_info("Toshiba ACPI Bluetooth device driver\n");
|
||||
|
||||
/* Enable the BT device */
|
||||
result = toshiba_bluetooth_enable(device->handle);
|
||||
if (result)
|
||||
bt_dev = kzalloc(sizeof(*bt_dev), GFP_KERNEL);
|
||||
if (!bt_dev)
|
||||
return -ENOMEM;
|
||||
bt_dev->acpi_dev = device;
|
||||
device->driver_data = bt_dev;
|
||||
dev_set_drvdata(&device->dev, bt_dev);
|
||||
|
||||
result = toshiba_bluetooth_sync_status(bt_dev);
|
||||
if (result) {
|
||||
kfree(bt_dev);
|
||||
return result;
|
||||
}
|
||||
|
||||
bt_dev->rfk = rfkill_alloc("Toshiba Bluetooth",
|
||||
&device->dev,
|
||||
RFKILL_TYPE_BLUETOOTH,
|
||||
&rfk_ops,
|
||||
bt_dev);
|
||||
if (!bt_dev->rfk) {
|
||||
pr_err("Unable to allocate rfkill device\n");
|
||||
kfree(bt_dev);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch);
|
||||
|
||||
result = rfkill_register(bt_dev->rfk);
|
||||
if (result) {
|
||||
pr_err("Unable to register rfkill device\n");
|
||||
rfkill_destroy(bt_dev->rfk);
|
||||
kfree(bt_dev);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int toshiba_bt_rfkill_remove(struct acpi_device *device)
|
||||
{
|
||||
struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(device);
|
||||
|
||||
/* clean up */
|
||||
if (bt_dev->rfk) {
|
||||
rfkill_unregister(bt_dev->rfk);
|
||||
rfkill_destroy(bt_dev->rfk);
|
||||
}
|
||||
|
||||
kfree(bt_dev);
|
||||
|
||||
return toshiba_bluetooth_disable(device->handle);
|
||||
}
|
||||
|
||||
|
|
|
@ -78,15 +78,20 @@ static ssize_t protection_level_store(struct device *dev,
|
|||
const char *buf, size_t count)
|
||||
{
|
||||
struct toshiba_haps_dev *haps = dev_get_drvdata(dev);
|
||||
int level, ret;
|
||||
int level;
|
||||
int ret;
|
||||
|
||||
if (sscanf(buf, "%d", &level) != 1 || level < 0 || level > 3)
|
||||
return -EINVAL;
|
||||
|
||||
/* Set the sensor level.
|
||||
* Acceptable levels are:
|
||||
ret = kstrtoint(buf, 0, &level);
|
||||
if (ret)
|
||||
return ret;
|
||||
/*
|
||||
* Check for supported levels, which can be:
|
||||
* 0 - Disabled | 1 - Low | 2 - Medium | 3 - High
|
||||
*/
|
||||
if (level < 0 || level > 3)
|
||||
return -EINVAL;
|
||||
|
||||
/* Set the sensor level */
|
||||
ret = toshiba_haps_protection_level(haps->acpi_dev->handle, level);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
@ -95,15 +100,21 @@ static ssize_t protection_level_store(struct device *dev,
|
|||
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_RW(protection_level);
|
||||
|
||||
static ssize_t reset_protection_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct toshiba_haps_dev *haps = dev_get_drvdata(dev);
|
||||
int reset, ret;
|
||||
int reset;
|
||||
int ret;
|
||||
|
||||
if (sscanf(buf, "%d", &reset) != 1 || reset != 1)
|
||||
ret = kstrtoint(buf, 0, &reset);
|
||||
if (ret)
|
||||
return ret;
|
||||
/* The only accepted value is 1 */
|
||||
if (reset != 1)
|
||||
return -EINVAL;
|
||||
|
||||
/* Reset the protection interface */
|
||||
|
@ -113,10 +124,7 @@ static ssize_t reset_protection_store(struct device *dev,
|
|||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(protection_level, S_IRUGO | S_IWUSR,
|
||||
protection_level_show, protection_level_store);
|
||||
static DEVICE_ATTR(reset_protection, S_IWUSR, NULL, reset_protection_store);
|
||||
static DEVICE_ATTR_WO(reset_protection);
|
||||
|
||||
static struct attribute *haps_attributes[] = {
|
||||
&dev_attr_protection_level.attr,
|
||||
|
|
Loading…
Reference in a new issue