浏览代码

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>
Henrique de Moraes Holschuh 17 年之前
父节点
当前提交
0e74dc2646
共有 3 个文件被更改,包括 200 次插入32 次删除
  1. 16 6
      Documentation/laptops/thinkpad-acpi.txt
  2. 2 0
      drivers/misc/Kconfig
  3. 182 26
      drivers/misc/thinkpad_acpi.c

+ 16 - 6
Documentation/laptops/thinkpad-acpi.txt

@@ -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:
 		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
-	generic rfkill class, so it is NOT to be considered stable yet.
+	Note: this interface has been superseded by the	generic rfkill
+	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
 -----------------
 -----------------
 
 
 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:
 		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
-	generic rfkill class, so it is NOT to be considered stable yet.
+	Note: this interface has been superseded by the	generic rfkill
+	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
 ------------------------------------
 ------------------------------------

+ 2 - 0
drivers/misc/Kconfig

@@ -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

+ 182 - 26
drivers/misc/thinkpad_acpi.c

@@ -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 {
 
 
 #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)
 	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 = {
 	&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 {
 	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)
 
 
 	/* 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 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;
 	int status;
 
 
@@ -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,
 	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,
 	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 = {
 	.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)
 			   "bluetooth hardware not installed\n");
 			   "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);
 				&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 -------------------------------------------------------------- */
 /* procfs -------------------------------------------------------------- */
@@ -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)
 
 
 	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 {
 	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)
 
 
 	/* 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 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;
 	int status;
 
 
@@ -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,
 	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,
 	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 = {
 	.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)
 			   "wan hardware not installed\n");
 			   "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);
 				&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 -------------------------------------------------------------- */
 /* procfs -------------------------------------------------------------- */
@@ -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)
 
 
 	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;
 	}
 	}