diff options
| -rw-r--r-- | Documentation/laptops/thinkpad-acpi.txt | 18 | ||||
| -rw-r--r-- | drivers/platform/x86/thinkpad_acpi.c | 206 |
2 files changed, 223 insertions, 1 deletions
diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt index ddc371e0a1a6..91c00010b15c 100644 --- a/Documentation/laptops/thinkpad-acpi.txt +++ b/Documentation/laptops/thinkpad-acpi.txt | |||
| @@ -1413,6 +1413,24 @@ Sysfs notes: | |||
| 1413 | rfkill controller switch "tpacpi_wwan_sw": refer to | 1413 | rfkill controller switch "tpacpi_wwan_sw": refer to |
| 1414 | Documentation/rfkill.txt for details. | 1414 | Documentation/rfkill.txt for details. |
| 1415 | 1415 | ||
| 1416 | EXPERIMENTAL: UWB | ||
| 1417 | ----------------- | ||
| 1418 | |||
| 1419 | This feature is marked EXPERIMENTAL because it has not been extensively | ||
| 1420 | tested and validated in various ThinkPad models yet. The feature may not | ||
| 1421 | work as expected. USE WITH CAUTION! To use this feature, you need to supply | ||
| 1422 | the experimental=1 parameter when loading the module. | ||
| 1423 | |||
| 1424 | sysfs rfkill class: switch "tpacpi_uwb_sw" | ||
| 1425 | |||
| 1426 | This feature exports an rfkill controller for the UWB device, if one is | ||
| 1427 | present and enabled in the BIOS. | ||
| 1428 | |||
| 1429 | Sysfs notes: | ||
| 1430 | |||
| 1431 | rfkill controller switch "tpacpi_uwb_sw": refer to | ||
| 1432 | Documentation/rfkill.txt for details. | ||
| 1433 | |||
| 1416 | Multiple Commands, Module Parameters | 1434 | Multiple Commands, Module Parameters |
| 1417 | ------------------------------------ | 1435 | ------------------------------------ |
| 1418 | 1436 | ||
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 27d709bac98f..c1d40410ad79 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c | |||
| @@ -169,6 +169,7 @@ enum { | |||
| 169 | enum { | 169 | enum { |
| 170 | TPACPI_RFK_BLUETOOTH_SW_ID = 0, | 170 | TPACPI_RFK_BLUETOOTH_SW_ID = 0, |
| 171 | TPACPI_RFK_WWAN_SW_ID, | 171 | TPACPI_RFK_WWAN_SW_ID, |
| 172 | TPACPI_RFK_UWB_SW_ID, | ||
| 172 | }; | 173 | }; |
| 173 | 174 | ||
| 174 | /* Debugging */ | 175 | /* Debugging */ |
| @@ -261,6 +262,7 @@ static struct { | |||
| 261 | u32 bright_16levels:1; | 262 | u32 bright_16levels:1; |
| 262 | u32 bright_acpimode:1; | 263 | u32 bright_acpimode:1; |
| 263 | u32 wan:1; | 264 | u32 wan:1; |
| 265 | u32 uwb:1; | ||
| 264 | u32 fan_ctrl_status_undef:1; | 266 | u32 fan_ctrl_status_undef:1; |
| 265 | u32 input_device_registered:1; | 267 | u32 input_device_registered:1; |
| 266 | u32 platform_drv_registered:1; | 268 | u32 platform_drv_registered:1; |
| @@ -317,6 +319,8 @@ static int dbg_bluetoothemul; | |||
| 317 | static int tpacpi_bluetooth_emulstate; | 319 | static int tpacpi_bluetooth_emulstate; |
| 318 | static int dbg_wwanemul; | 320 | static int dbg_wwanemul; |
| 319 | static int tpacpi_wwan_emulstate; | 321 | static int tpacpi_wwan_emulstate; |
| 322 | static int dbg_uwbemul; | ||
| 323 | static int tpacpi_uwb_emulstate; | ||
| 320 | #endif | 324 | #endif |
| 321 | 325 | ||
| 322 | 326 | ||
| @@ -967,6 +971,7 @@ static int __init tpacpi_new_rfkill(const unsigned int id, | |||
| 967 | struct rfkill **rfk, | 971 | struct rfkill **rfk, |
| 968 | const enum rfkill_type rfktype, | 972 | const enum rfkill_type rfktype, |
| 969 | const char *name, | 973 | const char *name, |
| 974 | const bool set_default, | ||
| 970 | int (*toggle_radio)(void *, enum rfkill_state), | 975 | int (*toggle_radio)(void *, enum rfkill_state), |
| 971 | int (*get_state)(void *, enum rfkill_state *)) | 976 | int (*get_state)(void *, enum rfkill_state *)) |
| 972 | { | 977 | { |
| @@ -978,7 +983,7 @@ static int __init tpacpi_new_rfkill(const unsigned int id, | |||
| 978 | printk(TPACPI_ERR | 983 | printk(TPACPI_ERR |
| 979 | "failed to read initial state for %s, error %d; " | 984 | "failed to read initial state for %s, error %d; " |
| 980 | "will turn radio off\n", name, res); | 985 | "will turn radio off\n", name, res); |
| 981 | } else { | 986 | } else if (set_default) { |
| 982 | /* try to set the initial state as the default for the rfkill | 987 | /* try to set the initial state as the default for the rfkill |
| 983 | * type, since we ask the firmware to preserve it across S5 in | 988 | * type, since we ask the firmware to preserve it across S5 in |
| 984 | * NVRAM */ | 989 | * NVRAM */ |
| @@ -1148,6 +1153,31 @@ static DRIVER_ATTR(wwan_emulstate, S_IWUSR | S_IRUGO, | |||
| 1148 | tpacpi_driver_wwan_emulstate_show, | 1153 | tpacpi_driver_wwan_emulstate_show, |
| 1149 | tpacpi_driver_wwan_emulstate_store); | 1154 | tpacpi_driver_wwan_emulstate_store); |
| 1150 | 1155 | ||
| 1156 | /* uwb_emulstate ------------------------------------------------- */ | ||
| 1157 | static ssize_t tpacpi_driver_uwb_emulstate_show( | ||
| 1158 | struct device_driver *drv, | ||
| 1159 | char *buf) | ||
| 1160 | { | ||
| 1161 | return snprintf(buf, PAGE_SIZE, "%d\n", !!tpacpi_uwb_emulstate); | ||
| 1162 | } | ||
| 1163 | |||
| 1164 | static ssize_t tpacpi_driver_uwb_emulstate_store( | ||
| 1165 | struct device_driver *drv, | ||
| 1166 | const char *buf, size_t count) | ||
| 1167 | { | ||
| 1168 | unsigned long t; | ||
| 1169 | |||
| 1170 | if (parse_strtoul(buf, 1, &t)) | ||
| 1171 | return -EINVAL; | ||
| 1172 | |||
| 1173 | tpacpi_uwb_emulstate = !!t; | ||
| 1174 | |||
| 1175 | return count; | ||
| 1176 | } | ||
| 1177 | |||
| 1178 | static DRIVER_ATTR(uwb_emulstate, S_IWUSR | S_IRUGO, | ||
| 1179 | tpacpi_driver_uwb_emulstate_show, | ||
| 1180 | tpacpi_driver_uwb_emulstate_store); | ||
| 1151 | #endif | 1181 | #endif |
| 1152 | 1182 | ||
| 1153 | /* --------------------------------------------------------------------- */ | 1183 | /* --------------------------------------------------------------------- */ |
| @@ -1175,6 +1205,8 @@ static int __init tpacpi_create_driver_attributes(struct device_driver *drv) | |||
| 1175 | res = driver_create_file(drv, &driver_attr_bluetooth_emulstate); | 1205 | res = driver_create_file(drv, &driver_attr_bluetooth_emulstate); |
| 1176 | if (!res && dbg_wwanemul) | 1206 | if (!res && dbg_wwanemul) |
| 1177 | res = driver_create_file(drv, &driver_attr_wwan_emulstate); | 1207 | res = driver_create_file(drv, &driver_attr_wwan_emulstate); |
| 1208 | if (!res && dbg_uwbemul) | ||
| 1209 | res = driver_create_file(drv, &driver_attr_uwb_emulstate); | ||
| 1178 | #endif | 1210 | #endif |
| 1179 | 1211 | ||
| 1180 | return res; | 1212 | return res; |
| @@ -1191,6 +1223,7 @@ static void tpacpi_remove_driver_attributes(struct device_driver *drv) | |||
| 1191 | driver_remove_file(drv, &driver_attr_wlsw_emulstate); | 1223 | driver_remove_file(drv, &driver_attr_wlsw_emulstate); |
| 1192 | driver_remove_file(drv, &driver_attr_bluetooth_emulstate); | 1224 | driver_remove_file(drv, &driver_attr_bluetooth_emulstate); |
| 1193 | driver_remove_file(drv, &driver_attr_wwan_emulstate); | 1225 | driver_remove_file(drv, &driver_attr_wwan_emulstate); |
| 1226 | driver_remove_file(drv, &driver_attr_uwb_emulstate); | ||
| 1194 | #endif | 1227 | #endif |
| 1195 | } | 1228 | } |
| 1196 | 1229 | ||
| @@ -2125,6 +2158,7 @@ static struct attribute *hotkey_mask_attributes[] __initdata = { | |||
| 2125 | 2158 | ||
| 2126 | static void bluetooth_update_rfk(void); | 2159 | static void bluetooth_update_rfk(void); |
| 2127 | static void wan_update_rfk(void); | 2160 | static void wan_update_rfk(void); |
| 2161 | static void uwb_update_rfk(void); | ||
| 2128 | static void tpacpi_send_radiosw_update(void) | 2162 | static void tpacpi_send_radiosw_update(void) |
| 2129 | { | 2163 | { |
| 2130 | int wlsw; | 2164 | int wlsw; |
| @@ -2134,6 +2168,8 @@ static void tpacpi_send_radiosw_update(void) | |||
| 2134 | bluetooth_update_rfk(); | 2168 | bluetooth_update_rfk(); |
| 2135 | if (tp_features.wan) | 2169 | if (tp_features.wan) |
| 2136 | wan_update_rfk(); | 2170 | wan_update_rfk(); |
| 2171 | if (tp_features.uwb) | ||
| 2172 | uwb_update_rfk(); | ||
| 2137 | 2173 | ||
| 2138 | if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&wlsw)) { | 2174 | if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&wlsw)) { |
| 2139 | mutex_lock(&tpacpi_inputdev_send_mutex); | 2175 | mutex_lock(&tpacpi_inputdev_send_mutex); |
| @@ -3035,6 +3071,7 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm) | |||
| 3035 | &tpacpi_bluetooth_rfkill, | 3071 | &tpacpi_bluetooth_rfkill, |
| 3036 | RFKILL_TYPE_BLUETOOTH, | 3072 | RFKILL_TYPE_BLUETOOTH, |
| 3037 | "tpacpi_bluetooth_sw", | 3073 | "tpacpi_bluetooth_sw", |
| 3074 | true, | ||
| 3038 | tpacpi_bluetooth_rfk_set, | 3075 | tpacpi_bluetooth_rfk_set, |
| 3039 | tpacpi_bluetooth_rfk_get); | 3076 | tpacpi_bluetooth_rfk_get); |
| 3040 | if (res) { | 3077 | if (res) { |
| @@ -3309,6 +3346,7 @@ static int __init wan_init(struct ibm_init_struct *iibm) | |||
| 3309 | &tpacpi_wan_rfkill, | 3346 | &tpacpi_wan_rfkill, |
| 3310 | RFKILL_TYPE_WWAN, | 3347 | RFKILL_TYPE_WWAN, |
| 3311 | "tpacpi_wwan_sw", | 3348 | "tpacpi_wwan_sw", |
| 3349 | true, | ||
| 3312 | tpacpi_wan_rfk_set, | 3350 | tpacpi_wan_rfk_set, |
| 3313 | tpacpi_wan_rfk_get); | 3351 | tpacpi_wan_rfk_get); |
| 3314 | if (res) { | 3352 | if (res) { |
| @@ -3366,6 +3404,162 @@ static struct ibm_struct wan_driver_data = { | |||
| 3366 | }; | 3404 | }; |
| 3367 | 3405 | ||
| 3368 | /************************************************************************* | 3406 | /************************************************************************* |
| 3407 | * UWB subdriver | ||
| 3408 | */ | ||
| 3409 | |||
| 3410 | enum { | ||
| 3411 | /* ACPI GUWB/SUWB bits */ | ||
| 3412 | TP_ACPI_UWB_HWPRESENT = 0x01, /* UWB hw available */ | ||
| 3413 | TP_ACPI_UWB_RADIOSSW = 0x02, /* UWB radio enabled */ | ||
| 3414 | }; | ||
| 3415 | |||
| 3416 | static struct rfkill *tpacpi_uwb_rfkill; | ||
| 3417 | |||
| 3418 | static int uwb_get_radiosw(void) | ||
| 3419 | { | ||
| 3420 | int status; | ||
| 3421 | |||
| 3422 | if (!tp_features.uwb) | ||
| 3423 | return -ENODEV; | ||
| 3424 | |||
| 3425 | /* WLSW overrides UWB in firmware/hardware, reflect that */ | ||
| 3426 | if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status) | ||
| 3427 | return RFKILL_STATE_HARD_BLOCKED; | ||
| 3428 | |||
| 3429 | #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES | ||
| 3430 | if (dbg_uwbemul) | ||
| 3431 | return (tpacpi_uwb_emulstate) ? | ||
| 3432 | RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED; | ||
| 3433 | #endif | ||
| 3434 | |||
| 3435 | if (!acpi_evalf(hkey_handle, &status, "GUWB", "d")) | ||
| 3436 | return -EIO; | ||
| 3437 | |||
| 3438 | return ((status & TP_ACPI_UWB_RADIOSSW) != 0) ? | ||
| 3439 | RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED; | ||
| 3440 | } | ||
| 3441 | |||
| 3442 | static void uwb_update_rfk(void) | ||
| 3443 | { | ||
| 3444 | int status; | ||
| 3445 | |||
| 3446 | if (!tpacpi_uwb_rfkill) | ||
| 3447 | return; | ||
| 3448 | |||
| 3449 | status = uwb_get_radiosw(); | ||
| 3450 | if (status < 0) | ||
| 3451 | return; | ||
| 3452 | rfkill_force_state(tpacpi_uwb_rfkill, status); | ||
| 3453 | } | ||
| 3454 | |||
| 3455 | static int uwb_set_radiosw(int radio_on, int update_rfk) | ||
| 3456 | { | ||
| 3457 | int status; | ||
| 3458 | |||
| 3459 | if (!tp_features.uwb) | ||
| 3460 | return -ENODEV; | ||
| 3461 | |||
| 3462 | /* WLSW overrides UWB in firmware/hardware, but there is no | ||
| 3463 | * reason to risk weird behaviour. */ | ||
| 3464 | if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status | ||
| 3465 | && radio_on) | ||
| 3466 | return -EPERM; | ||
| 3467 | |||
| 3468 | #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES | ||
| 3469 | if (dbg_uwbemul) { | ||
| 3470 | tpacpi_uwb_emulstate = !!radio_on; | ||
| 3471 | if (update_rfk) | ||
| 3472 | uwb_update_rfk(); | ||
| 3473 | return 0; | ||
| 3474 | } | ||
| 3475 | #endif | ||
| 3476 | |||
| 3477 | status = (radio_on) ? TP_ACPI_UWB_RADIOSSW : 0; | ||
| 3478 | if (!acpi_evalf(hkey_handle, NULL, "SUWB", "vd", status)) | ||
| 3479 | return -EIO; | ||
| 3480 | |||
| 3481 | if (update_rfk) | ||
| 3482 | uwb_update_rfk(); | ||
| 3483 | |||
| 3484 | return 0; | ||
| 3485 | } | ||
| 3486 | |||
| 3487 | /* --------------------------------------------------------------------- */ | ||
| 3488 | |||
| 3489 | static int tpacpi_uwb_rfk_get(void *data, enum rfkill_state *state) | ||
| 3490 | { | ||
| 3491 | int uwbs = uwb_get_radiosw(); | ||
| 3492 | |||
| 3493 | if (uwbs < 0) | ||
| 3494 | return uwbs; | ||
| 3495 | |||
| 3496 | *state = uwbs; | ||
| 3497 | return 0; | ||
| 3498 | } | ||
| 3499 | |||
| 3500 | static int tpacpi_uwb_rfk_set(void *data, enum rfkill_state state) | ||
| 3501 | { | ||
| 3502 | return uwb_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0); | ||
| 3503 | } | ||
| 3504 | |||
| 3505 | static void uwb_exit(void) | ||
| 3506 | { | ||
| 3507 | if (tpacpi_uwb_rfkill) | ||
| 3508 | rfkill_unregister(tpacpi_uwb_rfkill); | ||
| 3509 | } | ||
| 3510 | |||
| 3511 | static int __init uwb_init(struct ibm_init_struct *iibm) | ||
| 3512 | { | ||
| 3513 | int res; | ||
| 3514 | int status = 0; | ||
| 3515 | |||
| 3516 | vdbg_printk(TPACPI_DBG_INIT, "initializing uwb subdriver\n"); | ||
| 3517 | |||
| 3518 | TPACPI_ACPIHANDLE_INIT(hkey); | ||
| 3519 | |||
| 3520 | tp_features.uwb = hkey_handle && | ||
| 3521 | acpi_evalf(hkey_handle, &status, "GUWB", "qd"); | ||
| 3522 | |||
| 3523 | vdbg_printk(TPACPI_DBG_INIT, "uwb is %s, status 0x%02x\n", | ||
| 3524 | str_supported(tp_features.uwb), | ||
| 3525 | status); | ||
| 3526 | |||
| 3527 | #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES | ||
| 3528 | if (dbg_uwbemul) { | ||
| 3529 | tp_features.uwb = 1; | ||
| 3530 | printk(TPACPI_INFO | ||
| 3531 | "uwb switch emulation enabled\n"); | ||
| 3532 | } else | ||
| 3533 | #endif | ||
| 3534 | if (tp_features.uwb && | ||
| 3535 | !(status & TP_ACPI_UWB_HWPRESENT)) { | ||
| 3536 | /* no uwb hardware present in system */ | ||
| 3537 | tp_features.uwb = 0; | ||
| 3538 | dbg_printk(TPACPI_DBG_INIT, | ||
| 3539 | "uwb hardware not installed\n"); | ||
| 3540 | } | ||
| 3541 | |||
| 3542 | if (!tp_features.uwb) | ||
| 3543 | return 1; | ||
| 3544 | |||
| 3545 | res = tpacpi_new_rfkill(TPACPI_RFK_UWB_SW_ID, | ||
| 3546 | &tpacpi_uwb_rfkill, | ||
| 3547 | RFKILL_TYPE_UWB, | ||
| 3548 | "tpacpi_uwb_sw", | ||
| 3549 | false, | ||
| 3550 | tpacpi_uwb_rfk_set, | ||
| 3551 | tpacpi_uwb_rfk_get); | ||
| 3552 | |||
| 3553 | return res; | ||
| 3554 | } | ||
| 3555 | |||
| 3556 | static struct ibm_struct uwb_driver_data = { | ||
| 3557 | .name = "uwb", | ||
| 3558 | .exit = uwb_exit, | ||
| 3559 | .flags.experimental = 1, | ||
| 3560 | }; | ||
| 3561 | |||
| 3562 | /************************************************************************* | ||
| 3369 | * Video subdriver | 3563 | * Video subdriver |
| 3370 | */ | 3564 | */ |
| 3371 | 3565 | ||
| @@ -6830,6 +7024,10 @@ static struct ibm_init_struct ibms_init[] __initdata = { | |||
| 6830 | .init = wan_init, | 7024 | .init = wan_init, |
| 6831 | .data = &wan_driver_data, | 7025 | .data = &wan_driver_data, |
| 6832 | }, | 7026 | }, |
| 7027 | { | ||
| 7028 | .init = uwb_init, | ||
| 7029 | .data = &uwb_driver_data, | ||
| 7030 | }, | ||
| 6833 | #ifdef CONFIG_THINKPAD_ACPI_VIDEO | 7031 | #ifdef CONFIG_THINKPAD_ACPI_VIDEO |
| 6834 | { | 7032 | { |
| 6835 | .init = video_init, | 7033 | .init = video_init, |
| @@ -6986,6 +7184,12 @@ MODULE_PARM_DESC(dbg_wwanemul, "Enables WWAN switch emulation"); | |||
| 6986 | module_param_named(wwan_state, tpacpi_wwan_emulstate, bool, 0); | 7184 | module_param_named(wwan_state, tpacpi_wwan_emulstate, bool, 0); |
| 6987 | MODULE_PARM_DESC(wwan_state, | 7185 | MODULE_PARM_DESC(wwan_state, |
| 6988 | "Initial state of the emulated WWAN switch"); | 7186 | "Initial state of the emulated WWAN switch"); |
| 7187 | |||
| 7188 | module_param(dbg_uwbemul, uint, 0); | ||
| 7189 | MODULE_PARM_DESC(dbg_uwbemul, "Enables UWB switch emulation"); | ||
| 7190 | module_param_named(uwb_state, tpacpi_uwb_emulstate, bool, 0); | ||
| 7191 | MODULE_PARM_DESC(uwb_state, | ||
| 7192 | "Initial state of the emulated UWB switch"); | ||
| 6989 | #endif | 7193 | #endif |
| 6990 | 7194 | ||
| 6991 | static void thinkpad_acpi_module_exit(void) | 7195 | static void thinkpad_acpi_module_exit(void) |
