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:
Henrique de Moraes Holschuh
2009-01-11 03:01:03 -02:00
committed by Len Brown
parent 90d9d3c79c
commit 0045c0aa7d
2 changed files with 223 additions and 1 deletions

View File

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