Commit 05fde26a authored by Linus Torvalds's avatar Linus Torvalds

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

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
  ...
parents 2d01eedf 5ee7041e
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 @@ static int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1,
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 @@ static int asus_wmi_rfkill_init(struct asus_wmi *asus)
/*
* Hwmon device
*/
static ssize_t asus_hwmon_pwm1(struct device *dev,
struct device_attribute *attr,
char *buf)
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);
u32 value;
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;
err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_CTRL, &value);
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 err;
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 != 0) {
pr_err("Unknown fan speed %#x\n", value);
value = -1;
*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);
int value;
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 @@ static int asus_wmi_debugfs_init(struct asus_wmi *asus)
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 @@ static int __init dell_setup_rfkill(void)
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>");
......
/*
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 = &param;
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");
/*
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;
if (sscanf(buf, "%d", &level) != 1 || level < 0 || level > 3)
return -EINVAL;
int level;
int ret;
/* 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,
......
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