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
......
This diff is collapsed.
......@@ -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,
......
This diff is collapsed.
......@@ -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