ACPI: thinkpad-acpi: add bluetooth and WWAN rfkill support
authorHenrique de Moraes Holschuh <hmh@hmh.eng.br>
Mon, 21 Jul 2008 12:15:51 +0000 (09:15 -0300)
committerHenrique de Moraes Holschuh <hmh@hmh.eng.br>
Mon, 21 Jul 2008 12:15:51 +0000 (09:15 -0300)
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>
Documentation/laptops/thinkpad-acpi.txt
drivers/misc/Kconfig
drivers/misc/thinkpad_acpi.c

index 64b3f146e4b09aa4d2cd312e97689f9eba503347..1c1c0217ebd1375ec8855dc7c14e7734ea35a857 100644 (file)
@@ -621,7 +621,8 @@ 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
 Bluetooth device in the internal ThinkPad CDC slot.
@@ -643,8 +644,12 @@ Sysfs notes:
                0: disables Bluetooth / Bluetooth is disabled
                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
 --------------------------------------------
@@ -1374,7 +1379,8 @@ EXPERIMENTAL: 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
 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
                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
 ------------------------------------
index 1921b8dbb2427f94d1403c24e0330c6dbad7d70b..b27ca91fd15e657e72da70aaeab539df8646eed5 100644 (file)
@@ -279,6 +279,8 @@ config THINKPAD_ACPI
        select INPUT
        select NEW_LEDS
        select LEDS_CLASS
+       select NET
+       select RFKILL
        ---help---
          This is a driver for the IBM and Lenovo ThinkPad laptops. It adds
          support for Fn-Fx key combinations, Bluetooth control, video
index 202d63e1b391899f2228d4e9f2e0abb695862c34..dc8d00a45701d48b809cf559a4c0dab5055ede31 100644 (file)
@@ -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;
        }