Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0e74dc26 authored by Henrique de Moraes Holschuh's avatar Henrique de Moraes Holschuh
Browse files

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
Loading
Loading
Loading
Loading
+16 −6
Original line number Diff line number Diff line
@@ -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
------------------------------------
+2 −0
Original line number Diff line number Diff line
@@ -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
+182 −26
Original line number Diff line number Diff line
@@ -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 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;

@@ -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) {
	if (!tp_features.bluetooth)
		return 1;

	res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
				&bluetooth_attr_group);
	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 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;

@@ -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) {
	if (!tp_features.wan)
		return 1;

	res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
				&wan_attr_group);
	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;
	}