summaryrefslogtreecommitdiff
path: root/drivers/misc/thinkpad_acpi.c
diff options
context:
space:
mode:
authorHenrique de Moraes Holschuh <hmh@hmh.eng.br>2008-07-21 16:15:51 +0400
committerHenrique de Moraes Holschuh <hmh@hmh.eng.br>2008-07-21 16:15:51 +0400
commit0e74dc2646db04b644faa8ea10ff4f408d55cf90 (patch)
treed1729fca9b925ec972d1ad3c40295cc7740a31dd /drivers/misc/thinkpad_acpi.c
parent133ec3bd3ae409895eacdce326cdc8d73c249e8a (diff)
downloadlinux-0e74dc2646db04b644faa8ea10ff4f408d55cf90.tar.xz
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: Henrique de Moraes Holschuh <hmh@hmh.eng.br> Cc: Ivo van Doorn <IvDoorn@gmail.com> Cc: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'drivers/misc/thinkpad_acpi.c')
-rw-r--r--drivers/misc/thinkpad_acpi.c208
1 files changed, 182 insertions, 26 deletions
diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c
index 202d63e1b391..dc8d00a45701 100644
--- a/drivers/misc/thinkpad_acpi.c
+++ b/drivers/misc/thinkpad_acpi.c
@@ -68,6 +68,7 @@
#include <linux/hwmon-sysfs.h>
#include <linux/input.h>
#include <linux/leds.h>
+#include <linux/rfkill.h>
#include <asm/uaccess.h>
#include <linux/dmi.h>
@@ -144,6 +145,12 @@ enum {
#define TPACPI_MAX_ACPI_ARGS 3
+/* rfkill switches */
+enum {
+ TPACPI_RFK_BLUETOOTH_SW_ID = 0,
+ TPACPI_RFK_WWAN_SW_ID,
+};
+
/* Debugging */
#define TPACPI_LOG TPACPI_FILE ": "
#define TPACPI_ERR KERN_ERR TPACPI_LOG
@@ -905,6 +912,43 @@ static int __init tpacpi_check_std_acpi_brightness_support(void)
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
*/
@@ -1906,10 +1950,18 @@ static struct attribute *hotkey_mask_attributes[] __initdata = {
&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)
{
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)) {
mutex_lock(&tpacpi_inputdev_send_mutex);
@@ -2581,6 +2633,8 @@ enum {
TP_ACPI_BLUETOOTH_UNK = 0x04, /* unknown function */
};
+static struct rfkill *tpacpi_bluetooth_rfkill;
+
static int bluetooth_get_radiosw(void)
{
int status;
@@ -2590,15 +2644,29 @@ static int bluetooth_get_radiosw(void)
/* WLSW overrides bluetooth in firmware/hardware, reflect that */
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"))
return -EIO;
- return (status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0;
+ return ((status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0) ?
+ RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED;
}
-static int bluetooth_set_radiosw(int radio_on)
+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, int update_rfk)
{
int status;
@@ -2620,6 +2688,9 @@ static int bluetooth_set_radiosw(int radio_on)
if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status))
return -EIO;
+ if (update_rfk)
+ bluetooth_update_rfk();
+
return 0;
}
@@ -2634,7 +2705,8 @@ static ssize_t bluetooth_enable_show(struct device *dev,
if (status < 0)
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,
@@ -2647,7 +2719,7 @@ static ssize_t bluetooth_enable_store(struct device *dev,
if (parse_strtoul(buf, 1, &t))
return -EINVAL;
- res = bluetooth_set_radiosw(t);
+ res = bluetooth_set_radiosw(t, 1);
return (res) ? res : count;
}
@@ -2667,8 +2739,27 @@ static const struct attribute_group bluetooth_attr_group = {
.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)
{
+ if (tpacpi_bluetooth_rfkill)
+ rfkill_unregister(tpacpi_bluetooth_rfkill);
+
sysfs_remove_group(&tpacpi_pdev->dev.kobj,
&bluetooth_attr_group);
}
@@ -2699,14 +2790,26 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm)
"bluetooth hardware not installed\n");
}
- if (tp_features.bluetooth) {
- res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+ if (!tp_features.bluetooth)
+ return 1;
+
+ res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
&bluetooth_attr_group);
- if (res)
- return res;
+ if (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 -------------------------------------------------------------- */
@@ -2719,7 +2822,8 @@ static int bluetooth_read(char *p)
len += sprintf(p + len, "status:\t\tnot supported\n");
else {
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");
}
@@ -2735,9 +2839,9 @@ static int bluetooth_write(char *buf)
while ((cmd = next_cmd(&buf))) {
if (strlencmp(cmd, "enable") == 0) {
- bluetooth_set_radiosw(1);
+ bluetooth_set_radiosw(1, 1);
} else if (strlencmp(cmd, "disable") == 0) {
- bluetooth_set_radiosw(0);
+ bluetooth_set_radiosw(0, 1);
} else
return -EINVAL;
}
@@ -2763,6 +2867,8 @@ enum {
TP_ACPI_WANCARD_UNK = 0x04, /* unknown function */
};
+static struct rfkill *tpacpi_wan_rfkill;
+
static int wan_get_radiosw(void)
{
int status;
@@ -2772,15 +2878,29 @@ static int wan_get_radiosw(void)
/* WLSW overrides WWAN in firmware/hardware, reflect that */
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"))
return -EIO;
- return (status & TP_ACPI_WANCARD_RADIOSSW) != 0;
+ return ((status & TP_ACPI_WANCARD_RADIOSSW) != 0) ?
+ RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED;
}
-static int wan_set_radiosw(int radio_on)
+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, int update_rfk)
{
int status;
@@ -2802,6 +2922,9 @@ static int wan_set_radiosw(int radio_on)
if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status))
return -EIO;
+ if (update_rfk)
+ wan_update_rfk();
+
return 0;
}
@@ -2816,7 +2939,8 @@ static ssize_t wan_enable_show(struct device *dev,
if (status < 0)
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,
@@ -2829,7 +2953,7 @@ static ssize_t wan_enable_store(struct device *dev,
if (parse_strtoul(buf, 1, &t))
return -EINVAL;
- res = wan_set_radiosw(t);
+ res = wan_set_radiosw(t, 1);
return (res) ? res : count;
}
@@ -2849,8 +2973,27 @@ static const struct attribute_group wan_attr_group = {
.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)
{
+ if (tpacpi_wan_rfkill)
+ rfkill_unregister(tpacpi_wan_rfkill);
+
sysfs_remove_group(&tpacpi_pdev->dev.kobj,
&wan_attr_group);
}
@@ -2879,14 +3022,26 @@ static int __init wan_init(struct ibm_init_struct *iibm)
"wan hardware not installed\n");
}
- if (tp_features.wan) {
- res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+ if (!tp_features.wan)
+ return 1;
+
+ res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
&wan_attr_group);
- if (res)
- return res;
+ if (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 -------------------------------------------------------------- */
@@ -2899,7 +3054,8 @@ static int wan_read(char *p)
len += sprintf(p + len, "status:\t\tnot supported\n");
else {
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");
}
@@ -2915,9 +3071,9 @@ static int wan_write(char *buf)
while ((cmd = next_cmd(&buf))) {
if (strlencmp(cmd, "enable") == 0) {
- wan_set_radiosw(1);
+ wan_set_radiosw(1, 1);
} else if (strlencmp(cmd, "disable") == 0) {
- wan_set_radiosw(0);
+ wan_set_radiosw(0, 1);
} else
return -EINVAL;
}