ACPI: thinkpad-acpi: add bluetooth and WWAN rfkill support

Add a read/write rfkill interface to the bluetooth radio switch on the
bluetooth submodule, and one for the wireless wan radio switch to the wan
submodule.

Since rfkill does care for when a switch changes state, use WLSW
notifications to also check if the WWAN or Bluetooth switches did not
change state (due to them being slaves of WLSW in firmware/hardware, but
that reality not being always properly exported by the thinkpad firmware).
Signed-off-by: default avatarHenrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Ivo van Doorn <IvDoorn@gmail.com>
Cc: John W. Linville <linville@tuxdriver.com>
parent 133ec3bd
...@@ -621,7 +621,8 @@ Bluetooth ...@@ -621,7 +621,8 @@ Bluetooth
--------- ---------
procfs: /proc/acpi/ibm/bluetooth procfs: /proc/acpi/ibm/bluetooth
sysfs device attribute: bluetooth_enable sysfs device attribute: bluetooth_enable (deprecated)
sysfs rfkill class: switch "tpacpi_bluetooth_sw"
This feature shows the presence and current state of a ThinkPad This feature shows the presence and current state of a ThinkPad
Bluetooth device in the internal ThinkPad CDC slot. Bluetooth device in the internal ThinkPad CDC slot.
...@@ -643,8 +644,12 @@ Sysfs notes: ...@@ -643,8 +644,12 @@ Sysfs notes:
0: disables Bluetooth / Bluetooth is disabled 0: disables Bluetooth / Bluetooth is disabled
1: enables Bluetooth / Bluetooth is enabled. 1: enables Bluetooth / Bluetooth is enabled.
Note: this interface will be probably be superseded by the Note: this interface has been superseded by the generic rfkill
generic rfkill class, so it is NOT to be considered stable yet. class. It has been deprecated, and it will be removed in year
2010.
rfkill controller switch "tpacpi_bluetooth_sw": refer to
Documentation/rfkill.txt for details.
Video output control -- /proc/acpi/ibm/video Video output control -- /proc/acpi/ibm/video
-------------------------------------------- --------------------------------------------
...@@ -1374,7 +1379,8 @@ EXPERIMENTAL: WAN ...@@ -1374,7 +1379,8 @@ EXPERIMENTAL: WAN
----------------- -----------------
procfs: /proc/acpi/ibm/wan procfs: /proc/acpi/ibm/wan
sysfs device attribute: wwan_enable sysfs device attribute: wwan_enable (deprecated)
sysfs rfkill class: switch "tpacpi_wwan_sw"
This feature is marked EXPERIMENTAL because the implementation This feature is marked EXPERIMENTAL because the implementation
directly accesses hardware registers and may not work as expected. USE directly accesses hardware registers and may not work as expected. USE
...@@ -1404,8 +1410,12 @@ Sysfs notes: ...@@ -1404,8 +1410,12 @@ Sysfs notes:
0: disables WWAN card / WWAN card is disabled 0: disables WWAN card / WWAN card is disabled
1: enables WWAN card / WWAN card is enabled. 1: enables WWAN card / WWAN card is enabled.
Note: this interface will be probably be superseded by the Note: this interface has been superseded by the generic rfkill
generic rfkill class, so it is NOT to be considered stable yet. class. It has been deprecated, and it will be removed in year
2010.
rfkill controller switch "tpacpi_wwan_sw": refer to
Documentation/rfkill.txt for details.
Multiple Commands, Module Parameters Multiple Commands, Module Parameters
------------------------------------ ------------------------------------
......
...@@ -279,6 +279,8 @@ config THINKPAD_ACPI ...@@ -279,6 +279,8 @@ config THINKPAD_ACPI
select INPUT select INPUT
select NEW_LEDS select NEW_LEDS
select LEDS_CLASS select LEDS_CLASS
select NET
select RFKILL
---help--- ---help---
This is a driver for the IBM and Lenovo ThinkPad laptops. It adds This is a driver for the IBM and Lenovo ThinkPad laptops. It adds
support for Fn-Fx key combinations, Bluetooth control, video support for Fn-Fx key combinations, Bluetooth control, video
......
...@@ -68,6 +68,7 @@ ...@@ -68,6 +68,7 @@
#include <linux/hwmon-sysfs.h> #include <linux/hwmon-sysfs.h>
#include <linux/input.h> #include <linux/input.h>
#include <linux/leds.h> #include <linux/leds.h>
#include <linux/rfkill.h>
#include <asm/uaccess.h> #include <asm/uaccess.h>
#include <linux/dmi.h> #include <linux/dmi.h>
...@@ -144,6 +145,12 @@ enum { ...@@ -144,6 +145,12 @@ enum {
#define TPACPI_MAX_ACPI_ARGS 3 #define TPACPI_MAX_ACPI_ARGS 3
/* rfkill switches */
enum {
TPACPI_RFK_BLUETOOTH_SW_ID = 0,
TPACPI_RFK_WWAN_SW_ID,
};
/* Debugging */ /* Debugging */
#define TPACPI_LOG TPACPI_FILE ": " #define TPACPI_LOG TPACPI_FILE ": "
#define TPACPI_ERR KERN_ERR TPACPI_LOG #define TPACPI_ERR KERN_ERR TPACPI_LOG
...@@ -905,6 +912,43 @@ static int __init tpacpi_check_std_acpi_brightness_support(void) ...@@ -905,6 +912,43 @@ static int __init tpacpi_check_std_acpi_brightness_support(void)
return 0; return 0;
} }
static int __init tpacpi_new_rfkill(const unsigned int id,
struct rfkill **rfk,
const enum rfkill_type rfktype,
const char *name,
int (*toggle_radio)(void *, enum rfkill_state),
int (*get_state)(void *, enum rfkill_state *))
{
int res;
enum rfkill_state initial_state;
*rfk = rfkill_allocate(&tpacpi_pdev->dev, rfktype);
if (!*rfk) {
printk(TPACPI_ERR
"failed to allocate memory for rfkill class\n");
return -ENOMEM;
}
(*rfk)->name = name;
(*rfk)->get_state = get_state;
(*rfk)->toggle_radio = toggle_radio;
if (!get_state(NULL, &initial_state))
(*rfk)->state = initial_state;
res = rfkill_register(*rfk);
if (res < 0) {
printk(TPACPI_ERR
"failed to register %s rfkill switch: %d\n",
name, res);
rfkill_free(*rfk);
*rfk = NULL;
return res;
}
return 0;
}
/************************************************************************* /*************************************************************************
* thinkpad-acpi driver attributes * thinkpad-acpi driver attributes
*/ */
...@@ -1906,10 +1950,18 @@ static struct attribute *hotkey_mask_attributes[] __initdata = { ...@@ -1906,10 +1950,18 @@ static struct attribute *hotkey_mask_attributes[] __initdata = {
&dev_attr_hotkey_wakeup_hotunplug_complete.attr, &dev_attr_hotkey_wakeup_hotunplug_complete.attr,
}; };
static void bluetooth_update_rfk(void);
static void wan_update_rfk(void);
static void tpacpi_send_radiosw_update(void) static void tpacpi_send_radiosw_update(void)
{ {
int wlsw; int wlsw;
/* Sync these BEFORE sending any rfkill events */
if (tp_features.bluetooth)
bluetooth_update_rfk();
if (tp_features.wan)
wan_update_rfk();
if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&wlsw)) { if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&wlsw)) {
mutex_lock(&tpacpi_inputdev_send_mutex); mutex_lock(&tpacpi_inputdev_send_mutex);
...@@ -2581,6 +2633,8 @@ enum { ...@@ -2581,6 +2633,8 @@ enum {
TP_ACPI_BLUETOOTH_UNK = 0x04, /* unknown function */ TP_ACPI_BLUETOOTH_UNK = 0x04, /* unknown function */
}; };
static struct rfkill *tpacpi_bluetooth_rfkill;
static int bluetooth_get_radiosw(void) static int bluetooth_get_radiosw(void)
{ {
int status; int status;
...@@ -2590,15 +2644,29 @@ static int bluetooth_get_radiosw(void) ...@@ -2590,15 +2644,29 @@ static int bluetooth_get_radiosw(void)
/* WLSW overrides bluetooth in firmware/hardware, reflect that */ /* WLSW overrides bluetooth in firmware/hardware, reflect that */
if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status) if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status)
return 0; return RFKILL_STATE_HARD_BLOCKED;
if (!acpi_evalf(hkey_handle, &status, "GBDC", "d")) if (!acpi_evalf(hkey_handle, &status, "GBDC", "d"))
return -EIO; return -EIO;
return (status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0; return ((status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0) ?
RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED;
}
static void bluetooth_update_rfk(void)
{
int status;
if (!tpacpi_bluetooth_rfkill)
return;
status = bluetooth_get_radiosw();
if (status < 0)
return;
rfkill_force_state(tpacpi_bluetooth_rfkill, status);
} }
static int bluetooth_set_radiosw(int radio_on) static int bluetooth_set_radiosw(int radio_on, int update_rfk)
{ {
int status; int status;
...@@ -2620,6 +2688,9 @@ static int bluetooth_set_radiosw(int radio_on) ...@@ -2620,6 +2688,9 @@ static int bluetooth_set_radiosw(int radio_on)
if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status)) if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status))
return -EIO; return -EIO;
if (update_rfk)
bluetooth_update_rfk();
return 0; return 0;
} }
...@@ -2634,7 +2705,8 @@ static ssize_t bluetooth_enable_show(struct device *dev, ...@@ -2634,7 +2705,8 @@ static ssize_t bluetooth_enable_show(struct device *dev,
if (status < 0) if (status < 0)
return status; return status;
return snprintf(buf, PAGE_SIZE, "%d\n", status ? 1 : 0); return snprintf(buf, PAGE_SIZE, "%d\n",
(status == RFKILL_STATE_UNBLOCKED) ? 1 : 0);
} }
static ssize_t bluetooth_enable_store(struct device *dev, static ssize_t bluetooth_enable_store(struct device *dev,
...@@ -2647,7 +2719,7 @@ static ssize_t bluetooth_enable_store(struct device *dev, ...@@ -2647,7 +2719,7 @@ static ssize_t bluetooth_enable_store(struct device *dev,
if (parse_strtoul(buf, 1, &t)) if (parse_strtoul(buf, 1, &t))
return -EINVAL; return -EINVAL;
res = bluetooth_set_radiosw(t); res = bluetooth_set_radiosw(t, 1);
return (res) ? res : count; return (res) ? res : count;
} }
...@@ -2667,8 +2739,27 @@ static const struct attribute_group bluetooth_attr_group = { ...@@ -2667,8 +2739,27 @@ static const struct attribute_group bluetooth_attr_group = {
.attrs = bluetooth_attributes, .attrs = bluetooth_attributes,
}; };
static int tpacpi_bluetooth_rfk_get(void *data, enum rfkill_state *state)
{
int bts = bluetooth_get_radiosw();
if (bts < 0)
return bts;
*state = bts;
return 0;
}
static int tpacpi_bluetooth_rfk_set(void *data, enum rfkill_state state)
{
return bluetooth_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0);
}
static void bluetooth_exit(void) static void bluetooth_exit(void)
{ {
if (tpacpi_bluetooth_rfkill)
rfkill_unregister(tpacpi_bluetooth_rfkill);
sysfs_remove_group(&tpacpi_pdev->dev.kobj, sysfs_remove_group(&tpacpi_pdev->dev.kobj,
&bluetooth_attr_group); &bluetooth_attr_group);
} }
...@@ -2699,14 +2790,26 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm) ...@@ -2699,14 +2790,26 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm)
"bluetooth hardware not installed\n"); "bluetooth hardware not installed\n");
} }
if (tp_features.bluetooth) { if (!tp_features.bluetooth)
return 1;
res = sysfs_create_group(&tpacpi_pdev->dev.kobj, res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
&bluetooth_attr_group); &bluetooth_attr_group);
if (res) if (res)
return res; return res;
res = tpacpi_new_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID,
&tpacpi_bluetooth_rfkill,
RFKILL_TYPE_BLUETOOTH,
"tpacpi_bluetooth_sw",
tpacpi_bluetooth_rfk_set,
tpacpi_bluetooth_rfk_get);
if (res) {
bluetooth_exit();
return res;
} }
return (tp_features.bluetooth)? 0 : 1; return 0;
} }
/* procfs -------------------------------------------------------------- */ /* procfs -------------------------------------------------------------- */
...@@ -2719,7 +2822,8 @@ static int bluetooth_read(char *p) ...@@ -2719,7 +2822,8 @@ static int bluetooth_read(char *p)
len += sprintf(p + len, "status:\t\tnot supported\n"); len += sprintf(p + len, "status:\t\tnot supported\n");
else { else {
len += sprintf(p + len, "status:\t\t%s\n", len += sprintf(p + len, "status:\t\t%s\n",
(status)? "enabled" : "disabled"); (status == RFKILL_STATE_UNBLOCKED) ?
"enabled" : "disabled");
len += sprintf(p + len, "commands:\tenable, disable\n"); len += sprintf(p + len, "commands:\tenable, disable\n");
} }
...@@ -2735,9 +2839,9 @@ static int bluetooth_write(char *buf) ...@@ -2735,9 +2839,9 @@ static int bluetooth_write(char *buf)
while ((cmd = next_cmd(&buf))) { while ((cmd = next_cmd(&buf))) {
if (strlencmp(cmd, "enable") == 0) { if (strlencmp(cmd, "enable") == 0) {
bluetooth_set_radiosw(1); bluetooth_set_radiosw(1, 1);
} else if (strlencmp(cmd, "disable") == 0) { } else if (strlencmp(cmd, "disable") == 0) {
bluetooth_set_radiosw(0); bluetooth_set_radiosw(0, 1);
} else } else
return -EINVAL; return -EINVAL;
} }
...@@ -2763,6 +2867,8 @@ enum { ...@@ -2763,6 +2867,8 @@ enum {
TP_ACPI_WANCARD_UNK = 0x04, /* unknown function */ TP_ACPI_WANCARD_UNK = 0x04, /* unknown function */
}; };
static struct rfkill *tpacpi_wan_rfkill;
static int wan_get_radiosw(void) static int wan_get_radiosw(void)
{ {
int status; int status;
...@@ -2772,15 +2878,29 @@ static int wan_get_radiosw(void) ...@@ -2772,15 +2878,29 @@ static int wan_get_radiosw(void)
/* WLSW overrides WWAN in firmware/hardware, reflect that */ /* WLSW overrides WWAN in firmware/hardware, reflect that */
if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status) if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status)
return 0; return RFKILL_STATE_HARD_BLOCKED;
if (!acpi_evalf(hkey_handle, &status, "GWAN", "d")) if (!acpi_evalf(hkey_handle, &status, "GWAN", "d"))
return -EIO; return -EIO;
return (status & TP_ACPI_WANCARD_RADIOSSW) != 0; return ((status & TP_ACPI_WANCARD_RADIOSSW) != 0) ?
RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED;
}
static void wan_update_rfk(void)
{
int status;
if (!tpacpi_wan_rfkill)
return;
status = wan_get_radiosw();
if (status < 0)
return;
rfkill_force_state(tpacpi_wan_rfkill, status);
} }
static int wan_set_radiosw(int radio_on) static int wan_set_radiosw(int radio_on, int update_rfk)
{ {
int status; int status;
...@@ -2802,6 +2922,9 @@ static int wan_set_radiosw(int radio_on) ...@@ -2802,6 +2922,9 @@ static int wan_set_radiosw(int radio_on)
if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status))
return -EIO; return -EIO;
if (update_rfk)
wan_update_rfk();
return 0; return 0;
} }
...@@ -2816,7 +2939,8 @@ static ssize_t wan_enable_show(struct device *dev, ...@@ -2816,7 +2939,8 @@ static ssize_t wan_enable_show(struct device *dev,
if (status < 0) if (status < 0)
return status; return status;
return snprintf(buf, PAGE_SIZE, "%d\n", status ? 1 : 0); return snprintf(buf, PAGE_SIZE, "%d\n",
(status == RFKILL_STATE_UNBLOCKED) ? 1 : 0);
} }
static ssize_t wan_enable_store(struct device *dev, static ssize_t wan_enable_store(struct device *dev,
...@@ -2829,7 +2953,7 @@ static ssize_t wan_enable_store(struct device *dev, ...@@ -2829,7 +2953,7 @@ static ssize_t wan_enable_store(struct device *dev,
if (parse_strtoul(buf, 1, &t)) if (parse_strtoul(buf, 1, &t))
return -EINVAL; return -EINVAL;
res = wan_set_radiosw(t); res = wan_set_radiosw(t, 1);
return (res) ? res : count; return (res) ? res : count;
} }
...@@ -2849,8 +2973,27 @@ static const struct attribute_group wan_attr_group = { ...@@ -2849,8 +2973,27 @@ static const struct attribute_group wan_attr_group = {
.attrs = wan_attributes, .attrs = wan_attributes,
}; };
static int tpacpi_wan_rfk_get(void *data, enum rfkill_state *state)
{
int wans = wan_get_radiosw();
if (wans < 0)
return wans;
*state = wans;
return 0;
}
static int tpacpi_wan_rfk_set(void *data, enum rfkill_state state)
{
return wan_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0);
}
static void wan_exit(void) static void wan_exit(void)
{ {
if (tpacpi_wan_rfkill)
rfkill_unregister(tpacpi_wan_rfkill);
sysfs_remove_group(&tpacpi_pdev->dev.kobj, sysfs_remove_group(&tpacpi_pdev->dev.kobj,
&wan_attr_group); &wan_attr_group);
} }
...@@ -2879,14 +3022,26 @@ static int __init wan_init(struct ibm_init_struct *iibm) ...@@ -2879,14 +3022,26 @@ static int __init wan_init(struct ibm_init_struct *iibm)
"wan hardware not installed\n"); "wan hardware not installed\n");
} }
if (tp_features.wan) { if (!tp_features.wan)
return 1;
res = sysfs_create_group(&tpacpi_pdev->dev.kobj, res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
&wan_attr_group); &wan_attr_group);
if (res) if (res)
return res; return res;
res = tpacpi_new_rfkill(TPACPI_RFK_WWAN_SW_ID,
&tpacpi_wan_rfkill,
RFKILL_TYPE_WWAN,
"tpacpi_wwan_sw",
tpacpi_wan_rfk_set,
tpacpi_wan_rfk_get);
if (res) {
wan_exit();
return res;
} }
return (tp_features.wan)? 0 : 1; return 0;
} }
/* procfs -------------------------------------------------------------- */ /* procfs -------------------------------------------------------------- */
...@@ -2899,7 +3054,8 @@ static int wan_read(char *p) ...@@ -2899,7 +3054,8 @@ static int wan_read(char *p)
len += sprintf(p + len, "status:\t\tnot supported\n"); len += sprintf(p + len, "status:\t\tnot supported\n");
else { else {
len += sprintf(p + len, "status:\t\t%s\n", len += sprintf(p + len, "status:\t\t%s\n",
(status)? "enabled" : "disabled"); (status == RFKILL_STATE_UNBLOCKED) ?
"enabled" : "disabled");
len += sprintf(p + len, "commands:\tenable, disable\n"); len += sprintf(p + len, "commands:\tenable, disable\n");
} }
...@@ -2915,9 +3071,9 @@ static int wan_write(char *buf) ...@@ -2915,9 +3071,9 @@ static int wan_write(char *buf)
while ((cmd = next_cmd(&buf))) { while ((cmd = next_cmd(&buf))) {
if (strlencmp(cmd, "enable") == 0) { if (strlencmp(cmd, "enable") == 0) {
wan_set_radiosw(1); wan_set_radiosw(1, 1);
} else if (strlencmp(cmd, "disable") == 0) { } else if (strlencmp(cmd, "disable") == 0) {
wan_set_radiosw(0); wan_set_radiosw(0, 1);
} else } else
return -EINVAL; return -EINVAL;
} }
......
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