Commit 0045c0aa authored by Henrique de Moraes Holschuh's avatar Henrique de Moraes Holschuh Committed by Len Brown

ACPI: thinkpad-acpi: add UWB radio support

Add rfkill support for USB UWB radio devices on very recent ThinkPad
laptop models.

The new subdriver is moslty a trimmed down copy of the wwan subdriver.
Signed-off-by: default avatarHenrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Ivo van Doorn <IvDoorn@gmail.com>
Signed-off-by: default avatarLen Brown <len.brown@intel.com>
parent 90d9d3c7
......@@ -1413,6 +1413,24 @@ Sysfs notes:
rfkill controller switch "tpacpi_wwan_sw": refer to
Documentation/rfkill.txt for details.
EXPERIMENTAL: UWB
-----------------
This feature is marked EXPERIMENTAL because it has not been extensively
tested and validated in various ThinkPad models yet. The feature may not
work as expected. USE WITH CAUTION! To use this feature, you need to supply
the experimental=1 parameter when loading the module.
sysfs rfkill class: switch "tpacpi_uwb_sw"
This feature exports an rfkill controller for the UWB device, if one is
present and enabled in the BIOS.
Sysfs notes:
rfkill controller switch "tpacpi_uwb_sw": refer to
Documentation/rfkill.txt for details.
Multiple Commands, Module Parameters
------------------------------------
......
......@@ -169,6 +169,7 @@ enum {
enum {
TPACPI_RFK_BLUETOOTH_SW_ID = 0,
TPACPI_RFK_WWAN_SW_ID,
TPACPI_RFK_UWB_SW_ID,
};
/* Debugging */
......@@ -261,6 +262,7 @@ static struct {
u32 bright_16levels:1;
u32 bright_acpimode:1;
u32 wan:1;
u32 uwb:1;
u32 fan_ctrl_status_undef:1;
u32 input_device_registered:1;
u32 platform_drv_registered:1;
......@@ -317,6 +319,8 @@ static int dbg_bluetoothemul;
static int tpacpi_bluetooth_emulstate;
static int dbg_wwanemul;
static int tpacpi_wwan_emulstate;
static int dbg_uwbemul;
static int tpacpi_uwb_emulstate;
#endif
......@@ -967,6 +971,7 @@ static int __init tpacpi_new_rfkill(const unsigned int id,
struct rfkill **rfk,
const enum rfkill_type rfktype,
const char *name,
const bool set_default,
int (*toggle_radio)(void *, enum rfkill_state),
int (*get_state)(void *, enum rfkill_state *))
{
......@@ -978,7 +983,7 @@ static int __init tpacpi_new_rfkill(const unsigned int id,
printk(TPACPI_ERR
"failed to read initial state for %s, error %d; "
"will turn radio off\n", name, res);
} else {
} else if (set_default) {
/* try to set the initial state as the default for the rfkill
* type, since we ask the firmware to preserve it across S5 in
* NVRAM */
......@@ -1148,6 +1153,31 @@ static DRIVER_ATTR(wwan_emulstate, S_IWUSR | S_IRUGO,
tpacpi_driver_wwan_emulstate_show,
tpacpi_driver_wwan_emulstate_store);
/* uwb_emulstate ------------------------------------------------- */
static ssize_t tpacpi_driver_uwb_emulstate_show(
struct device_driver *drv,
char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", !!tpacpi_uwb_emulstate);
}
static ssize_t tpacpi_driver_uwb_emulstate_store(
struct device_driver *drv,
const char *buf, size_t count)
{
unsigned long t;
if (parse_strtoul(buf, 1, &t))
return -EINVAL;
tpacpi_uwb_emulstate = !!t;
return count;
}
static DRIVER_ATTR(uwb_emulstate, S_IWUSR | S_IRUGO,
tpacpi_driver_uwb_emulstate_show,
tpacpi_driver_uwb_emulstate_store);
#endif
/* --------------------------------------------------------------------- */
......@@ -1175,6 +1205,8 @@ static int __init tpacpi_create_driver_attributes(struct device_driver *drv)
res = driver_create_file(drv, &driver_attr_bluetooth_emulstate);
if (!res && dbg_wwanemul)
res = driver_create_file(drv, &driver_attr_wwan_emulstate);
if (!res && dbg_uwbemul)
res = driver_create_file(drv, &driver_attr_uwb_emulstate);
#endif
return res;
......@@ -1191,6 +1223,7 @@ static void tpacpi_remove_driver_attributes(struct device_driver *drv)
driver_remove_file(drv, &driver_attr_wlsw_emulstate);
driver_remove_file(drv, &driver_attr_bluetooth_emulstate);
driver_remove_file(drv, &driver_attr_wwan_emulstate);
driver_remove_file(drv, &driver_attr_uwb_emulstate);
#endif
}
......@@ -2125,6 +2158,7 @@ static struct attribute *hotkey_mask_attributes[] __initdata = {
static void bluetooth_update_rfk(void);
static void wan_update_rfk(void);
static void uwb_update_rfk(void);
static void tpacpi_send_radiosw_update(void)
{
int wlsw;
......@@ -2134,6 +2168,8 @@ static void tpacpi_send_radiosw_update(void)
bluetooth_update_rfk();
if (tp_features.wan)
wan_update_rfk();
if (tp_features.uwb)
uwb_update_rfk();
if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&wlsw)) {
mutex_lock(&tpacpi_inputdev_send_mutex);
......@@ -3035,6 +3071,7 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm)
&tpacpi_bluetooth_rfkill,
RFKILL_TYPE_BLUETOOTH,
"tpacpi_bluetooth_sw",
true,
tpacpi_bluetooth_rfk_set,
tpacpi_bluetooth_rfk_get);
if (res) {
......@@ -3309,6 +3346,7 @@ static int __init wan_init(struct ibm_init_struct *iibm)
&tpacpi_wan_rfkill,
RFKILL_TYPE_WWAN,
"tpacpi_wwan_sw",
true,
tpacpi_wan_rfk_set,
tpacpi_wan_rfk_get);
if (res) {
......@@ -3365,6 +3403,162 @@ static struct ibm_struct wan_driver_data = {
.shutdown = wan_shutdown,
};
/*************************************************************************
* UWB subdriver
*/
enum {
/* ACPI GUWB/SUWB bits */
TP_ACPI_UWB_HWPRESENT = 0x01, /* UWB hw available */
TP_ACPI_UWB_RADIOSSW = 0x02, /* UWB radio enabled */
};
static struct rfkill *tpacpi_uwb_rfkill;
static int uwb_get_radiosw(void)
{
int status;
if (!tp_features.uwb)
return -ENODEV;
/* WLSW overrides UWB in firmware/hardware, reflect that */
if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status)
return RFKILL_STATE_HARD_BLOCKED;
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
if (dbg_uwbemul)
return (tpacpi_uwb_emulstate) ?
RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED;
#endif
if (!acpi_evalf(hkey_handle, &status, "GUWB", "d"))
return -EIO;
return ((status & TP_ACPI_UWB_RADIOSSW) != 0) ?
RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED;
}
static void uwb_update_rfk(void)
{
int status;
if (!tpacpi_uwb_rfkill)
return;
status = uwb_get_radiosw();
if (status < 0)
return;
rfkill_force_state(tpacpi_uwb_rfkill, status);
}
static int uwb_set_radiosw(int radio_on, int update_rfk)
{
int status;
if (!tp_features.uwb)
return -ENODEV;
/* WLSW overrides UWB in firmware/hardware, but there is no
* reason to risk weird behaviour. */
if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status
&& radio_on)
return -EPERM;
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
if (dbg_uwbemul) {
tpacpi_uwb_emulstate = !!radio_on;
if (update_rfk)
uwb_update_rfk();
return 0;
}
#endif
status = (radio_on) ? TP_ACPI_UWB_RADIOSSW : 0;
if (!acpi_evalf(hkey_handle, NULL, "SUWB", "vd", status))
return -EIO;
if (update_rfk)
uwb_update_rfk();
return 0;
}
/* --------------------------------------------------------------------- */
static int tpacpi_uwb_rfk_get(void *data, enum rfkill_state *state)
{
int uwbs = uwb_get_radiosw();
if (uwbs < 0)
return uwbs;
*state = uwbs;
return 0;
}
static int tpacpi_uwb_rfk_set(void *data, enum rfkill_state state)
{
return uwb_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0);
}
static void uwb_exit(void)
{
if (tpacpi_uwb_rfkill)
rfkill_unregister(tpacpi_uwb_rfkill);
}
static int __init uwb_init(struct ibm_init_struct *iibm)
{
int res;
int status = 0;
vdbg_printk(TPACPI_DBG_INIT, "initializing uwb subdriver\n");
TPACPI_ACPIHANDLE_INIT(hkey);
tp_features.uwb = hkey_handle &&
acpi_evalf(hkey_handle, &status, "GUWB", "qd");
vdbg_printk(TPACPI_DBG_INIT, "uwb is %s, status 0x%02x\n",
str_supported(tp_features.uwb),
status);
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
if (dbg_uwbemul) {
tp_features.uwb = 1;
printk(TPACPI_INFO
"uwb switch emulation enabled\n");
} else
#endif
if (tp_features.uwb &&
!(status & TP_ACPI_UWB_HWPRESENT)) {
/* no uwb hardware present in system */
tp_features.uwb = 0;
dbg_printk(TPACPI_DBG_INIT,
"uwb hardware not installed\n");
}
if (!tp_features.uwb)
return 1;
res = tpacpi_new_rfkill(TPACPI_RFK_UWB_SW_ID,
&tpacpi_uwb_rfkill,
RFKILL_TYPE_UWB,
"tpacpi_uwb_sw",
false,
tpacpi_uwb_rfk_set,
tpacpi_uwb_rfk_get);
return res;
}
static struct ibm_struct uwb_driver_data = {
.name = "uwb",
.exit = uwb_exit,
.flags.experimental = 1,
};
/*************************************************************************
* Video subdriver
*/
......@@ -6830,6 +7024,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {
.init = wan_init,
.data = &wan_driver_data,
},
{
.init = uwb_init,
.data = &uwb_driver_data,
},
#ifdef CONFIG_THINKPAD_ACPI_VIDEO
{
.init = video_init,
......@@ -6986,6 +7184,12 @@ MODULE_PARM_DESC(dbg_wwanemul, "Enables WWAN switch emulation");
module_param_named(wwan_state, tpacpi_wwan_emulstate, bool, 0);
MODULE_PARM_DESC(wwan_state,
"Initial state of the emulated WWAN switch");
module_param(dbg_uwbemul, uint, 0);
MODULE_PARM_DESC(dbg_uwbemul, "Enables UWB switch emulation");
module_param_named(uwb_state, tpacpi_uwb_emulstate, bool, 0);
MODULE_PARM_DESC(uwb_state,
"Initial state of the emulated UWB switch");
#endif
static void thinkpad_acpi_module_exit(void)
......
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