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: Henrique de Moraes Holschuh <hmh@hmh.eng.br> Cc: Ivo van Doorn <IvDoorn@gmail.com> Signed-off-by: Len Brown <len.brown@intel.com>
This commit is contained in:
parent
90d9d3c79c
commit
0045c0aa7d
|
@ -1413,6 +1413,24 @@ Sysfs notes:
|
||||||
rfkill controller switch "tpacpi_wwan_sw": refer to
|
rfkill controller switch "tpacpi_wwan_sw": refer to
|
||||||
Documentation/rfkill.txt for details.
|
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
|
Multiple Commands, Module Parameters
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -169,6 +169,7 @@ enum {
|
||||||
enum {
|
enum {
|
||||||
TPACPI_RFK_BLUETOOTH_SW_ID = 0,
|
TPACPI_RFK_BLUETOOTH_SW_ID = 0,
|
||||||
TPACPI_RFK_WWAN_SW_ID,
|
TPACPI_RFK_WWAN_SW_ID,
|
||||||
|
TPACPI_RFK_UWB_SW_ID,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Debugging */
|
/* Debugging */
|
||||||
|
@ -261,6 +262,7 @@ static struct {
|
||||||
u32 bright_16levels:1;
|
u32 bright_16levels:1;
|
||||||
u32 bright_acpimode:1;
|
u32 bright_acpimode:1;
|
||||||
u32 wan:1;
|
u32 wan:1;
|
||||||
|
u32 uwb:1;
|
||||||
u32 fan_ctrl_status_undef:1;
|
u32 fan_ctrl_status_undef:1;
|
||||||
u32 input_device_registered:1;
|
u32 input_device_registered:1;
|
||||||
u32 platform_drv_registered:1;
|
u32 platform_drv_registered:1;
|
||||||
|
@ -317,6 +319,8 @@ static int dbg_bluetoothemul;
|
||||||
static int tpacpi_bluetooth_emulstate;
|
static int tpacpi_bluetooth_emulstate;
|
||||||
static int dbg_wwanemul;
|
static int dbg_wwanemul;
|
||||||
static int tpacpi_wwan_emulstate;
|
static int tpacpi_wwan_emulstate;
|
||||||
|
static int dbg_uwbemul;
|
||||||
|
static int tpacpi_uwb_emulstate;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
@ -967,6 +971,7 @@ static int __init tpacpi_new_rfkill(const unsigned int id,
|
||||||
struct rfkill **rfk,
|
struct rfkill **rfk,
|
||||||
const enum rfkill_type rfktype,
|
const enum rfkill_type rfktype,
|
||||||
const char *name,
|
const char *name,
|
||||||
|
const bool set_default,
|
||||||
int (*toggle_radio)(void *, enum rfkill_state),
|
int (*toggle_radio)(void *, enum rfkill_state),
|
||||||
int (*get_state)(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
|
printk(TPACPI_ERR
|
||||||
"failed to read initial state for %s, error %d; "
|
"failed to read initial state for %s, error %d; "
|
||||||
"will turn radio off\n", name, res);
|
"will turn radio off\n", name, res);
|
||||||
} else {
|
} else if (set_default) {
|
||||||
/* try to set the initial state as the default for the rfkill
|
/* try to set the initial state as the default for the rfkill
|
||||||
* type, since we ask the firmware to preserve it across S5 in
|
* type, since we ask the firmware to preserve it across S5 in
|
||||||
* NVRAM */
|
* NVRAM */
|
||||||
|
@ -1148,6 +1153,31 @@ static DRIVER_ATTR(wwan_emulstate, S_IWUSR | S_IRUGO,
|
||||||
tpacpi_driver_wwan_emulstate_show,
|
tpacpi_driver_wwan_emulstate_show,
|
||||||
tpacpi_driver_wwan_emulstate_store);
|
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
|
#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);
|
res = driver_create_file(drv, &driver_attr_bluetooth_emulstate);
|
||||||
if (!res && dbg_wwanemul)
|
if (!res && dbg_wwanemul)
|
||||||
res = driver_create_file(drv, &driver_attr_wwan_emulstate);
|
res = driver_create_file(drv, &driver_attr_wwan_emulstate);
|
||||||
|
if (!res && dbg_uwbemul)
|
||||||
|
res = driver_create_file(drv, &driver_attr_uwb_emulstate);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return res;
|
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_wlsw_emulstate);
|
||||||
driver_remove_file(drv, &driver_attr_bluetooth_emulstate);
|
driver_remove_file(drv, &driver_attr_bluetooth_emulstate);
|
||||||
driver_remove_file(drv, &driver_attr_wwan_emulstate);
|
driver_remove_file(drv, &driver_attr_wwan_emulstate);
|
||||||
|
driver_remove_file(drv, &driver_attr_uwb_emulstate);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2125,6 +2158,7 @@ static struct attribute *hotkey_mask_attributes[] __initdata = {
|
||||||
|
|
||||||
static void bluetooth_update_rfk(void);
|
static void bluetooth_update_rfk(void);
|
||||||
static void wan_update_rfk(void);
|
static void wan_update_rfk(void);
|
||||||
|
static void uwb_update_rfk(void);
|
||||||
static void tpacpi_send_radiosw_update(void)
|
static void tpacpi_send_radiosw_update(void)
|
||||||
{
|
{
|
||||||
int wlsw;
|
int wlsw;
|
||||||
|
@ -2134,6 +2168,8 @@ static void tpacpi_send_radiosw_update(void)
|
||||||
bluetooth_update_rfk();
|
bluetooth_update_rfk();
|
||||||
if (tp_features.wan)
|
if (tp_features.wan)
|
||||||
wan_update_rfk();
|
wan_update_rfk();
|
||||||
|
if (tp_features.uwb)
|
||||||
|
uwb_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);
|
||||||
|
@ -3035,6 +3071,7 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm)
|
||||||
&tpacpi_bluetooth_rfkill,
|
&tpacpi_bluetooth_rfkill,
|
||||||
RFKILL_TYPE_BLUETOOTH,
|
RFKILL_TYPE_BLUETOOTH,
|
||||||
"tpacpi_bluetooth_sw",
|
"tpacpi_bluetooth_sw",
|
||||||
|
true,
|
||||||
tpacpi_bluetooth_rfk_set,
|
tpacpi_bluetooth_rfk_set,
|
||||||
tpacpi_bluetooth_rfk_get);
|
tpacpi_bluetooth_rfk_get);
|
||||||
if (res) {
|
if (res) {
|
||||||
|
@ -3309,6 +3346,7 @@ static int __init wan_init(struct ibm_init_struct *iibm)
|
||||||
&tpacpi_wan_rfkill,
|
&tpacpi_wan_rfkill,
|
||||||
RFKILL_TYPE_WWAN,
|
RFKILL_TYPE_WWAN,
|
||||||
"tpacpi_wwan_sw",
|
"tpacpi_wwan_sw",
|
||||||
|
true,
|
||||||
tpacpi_wan_rfk_set,
|
tpacpi_wan_rfk_set,
|
||||||
tpacpi_wan_rfk_get);
|
tpacpi_wan_rfk_get);
|
||||||
if (res) {
|
if (res) {
|
||||||
|
@ -3365,6 +3403,162 @@ static struct ibm_struct wan_driver_data = {
|
||||||
.shutdown = wan_shutdown,
|
.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
|
* Video subdriver
|
||||||
*/
|
*/
|
||||||
|
@ -6830,6 +7024,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {
|
||||||
.init = wan_init,
|
.init = wan_init,
|
||||||
.data = &wan_driver_data,
|
.data = &wan_driver_data,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.init = uwb_init,
|
||||||
|
.data = &uwb_driver_data,
|
||||||
|
},
|
||||||
#ifdef CONFIG_THINKPAD_ACPI_VIDEO
|
#ifdef CONFIG_THINKPAD_ACPI_VIDEO
|
||||||
{
|
{
|
||||||
.init = video_init,
|
.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_param_named(wwan_state, tpacpi_wwan_emulstate, bool, 0);
|
||||||
MODULE_PARM_DESC(wwan_state,
|
MODULE_PARM_DESC(wwan_state,
|
||||||
"Initial state of the emulated WWAN switch");
|
"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
|
#endif
|
||||||
|
|
||||||
static void thinkpad_acpi_module_exit(void)
|
static void thinkpad_acpi_module_exit(void)
|
||||||
|
|
Loading…
Reference in New Issue