/* * Changelog: * 2007-10-20 changelog trimmed down * * 2007-03-27 0.14 renamed to thinkpad_acpi and moved to * drivers/misc. * * 2006-11-22 0.13 new maintainer * changelog now lives in git commit history, and will * not be updated further in-file. * * 2005-03-17 0.11 support for 600e, 770x * thanks to Jamie Lentin <lentinj@dial.pipex.com> * * 2005-01-16 0.9 use MODULE_VERSION * thanks to Henrik Brix Andersen <brix@gentoo.org> * fix parameter passing on module loading * thanks to Rusty Russell <rusty@rustcorp.com.au> * thanks to Jim Radford <radford@blackbean.org> * 2004-11-08 0.8 fix init error case, don't return from a macro * thanks to Chris Wright <chrisw@osdl.org>
*/
/* Misc bay events */
TP_HKEY_EV_OPTDRV_EJ = 0x3006, /* opt. drive tray ejected */
TP_HKEY_EV_HOTPLUG_DOCK = 0x4010, /* docked into hotplug dock
or port replicator */
TP_HKEY_EV_HOTPLUG_UNDOCK = 0x4011, /* undocked from hotplug
dock or port replicator */ /* * Thinkpad X1 Tablet series devices emit 0x4012 and 0x4013 * when keyboard cover is attached, detached or folded onto the back
*/
TP_HKEY_EV_KBD_COVER_ATTACH = 0x4012, /* keyboard cover attached */
TP_HKEY_EV_KBD_COVER_DETACH = 0x4013, /* keyboard cover detached or folded back */
#define dbg_printk(a_dbg_level, format, arg...) \ do { \ if (dbg_level & (a_dbg_level)) \
printk(KERN_DEBUG pr_fmt("%s: " format), \
__func__, ##arg); \
} while (0)
#ifdef CONFIG_THINKPAD_ACPI_DEBUG #define vdbg_printk dbg_printk staticconstchar *str_supported(int is_supported); #else staticinlineconstchar *str_supported(int is_supported) { return""; } #define vdbg_printk(a_dbg_level, format, arg...) \ do { if (0) no_printk(format, ##arg); } while (0) #endif
staticvoid tpacpi_log_usertask(constchar * const what)
{
printk(KERN_DEBUG pr_fmt("%s: access by process with PID %d\n"),
what, task_tgid_vnr(current));
}
#define tpacpi_disclose_usertask(what, format, arg...) \ do { \ if (unlikely((dbg_level & TPACPI_DBG_DISCLOSETASK) && \
(tpacpi_lifecycle == TPACPI_LIFE_RUNNING))) { \
printk(KERN_DEBUG pr_fmt("%s: PID %d: " format), \
what, task_tgid_vnr(current), ## arg); \
} \
} while (0)
/* * Quirk handling helpers * * ThinkPad IDs and versions seen in the field so far are * two or three characters from the set [0-9A-Z], i.e. base 36. * * We use values well outside that range as specials.
*/
/** * tpacpi_check_quirks() - search BIOS/EC version on a list * @qlist: array of &struct tpacpi_quirk * @qlist_size: number of elements in @qlist * * Iterates over a quirks list until one is found that matches the * ThinkPad's vendor, BIOS and EC model. * * Returns: %0 if nothing matches, otherwise returns the quirks field of * the matching &struct tpacpi_quirk entry. * * The match criteria is: vendor, ec and bios must match.
*/ staticunsignedlong __init tpacpi_check_quirks( conststruct tpacpi_quirk *qlist, unsignedint qlist_size)
{ while (qlist_size) { if ((qlist->vendor == thinkpad_id.vendor ||
qlist->vendor == TPACPI_MATCH_ANY) &&
(qlist->bios == thinkpad_id.bios_model ||
qlist->bios == TPACPI_MATCH_ANY) &&
(qlist->ec == thinkpad_id.ec_model ||
qlist->ec == TPACPI_MATCH_ANY)) return qlist->quirks;
vdbg_printk(TPACPI_DBG_INIT, "trying to locate ACPI handle for %s\n",
name);
for (i = 0; i < num_paths; i++) {
status = acpi_get_handle(parent, paths[i], handle); if (ACPI_SUCCESS(status)) {
dbg_printk(TPACPI_DBG_INIT, "Found ACPI handle %s for %s\n",
paths[i], name); return;
}
}
vdbg_printk(TPACPI_DBG_INIT, "ACPI handle for %s not found\n",
name);
*handle = NULL;
}
staticvoid tpacpi_disable_brightness_delay(void)
{ if (acpi_evalf(hkey_handle, NULL, "PWMS", "qvd", 0))
pr_notice("ACPI backlight control delay disabled\n");
}
staticvoid printk_deprecated_attribute(constchar * const what, constchar * const details)
{
tpacpi_log_usertask("deprecated sysfs attribute");
pr_warn("WARNING: sysfs attribute %s is deprecated and will be removed. %s\n",
what, details);
}
/************************************************************************* * rfkill and radio control support helpers
*/
/* * ThinkPad-ACPI firmware handling model: * * WLSW (master wireless switch) is event-driven, and is common to all * firmware-controlled radios. It cannot be controlled, just monitored, * as expected. It overrides all radio state in firmware * * The kernel, a masked-off hotkey, and WLSW can change the radio state * (TODO: verify how WLSW interacts with the returned radio state). * * The only time there are shadow radio state changes, is when * masked-off hotkeys are used.
*/
/* * Internal driver API for radio state: * * int: < 0 = error, otherwise enum tpacpi_rfkill_state * bool: true means radio blocked (off)
*/ enum tpacpi_rfkill_state {
TPACPI_RFK_RADIO_OFF = 0,
TPACPI_RFK_RADIO_ON
};
/* * Sync the HW-blocking state of all rfkill switches, * do notice it causes the rfkill core to schedule uevents
*/ staticvoid tpacpi_rfk_update_hwblock_state(bool blocked)
{ unsignedint i; struct tpacpi_rfk *tp_rfk;
for (i = 0; i < TPACPI_RFK_SW_MAX; i++) {
tp_rfk = tpacpi_rfkill_switches[i]; if (tp_rfk) { if (rfkill_set_hw_state(tp_rfk->rfkill,
blocked)) { /* ignore -- we track sw block */
}
}
}
}
/* Call to get the WLSW state from the firmware */ staticint hotkey_get_wlsw(void);
/* Call to query WLSW state and update all rfkill switches */ staticbool tpacpi_rfk_check_hwblock_state(void)
{ int res = hotkey_get_wlsw(); int hw_blocked;
/* When unknown or unsupported, we have to assume it is unblocked */ if (res < 0) returnfalse;
atp_rfk = kzalloc(sizeof(struct tpacpi_rfk), GFP_KERNEL); if (atp_rfk)
atp_rfk->rfkill = rfkill_alloc(name,
&tpacpi_pdev->dev,
rfktype,
&tpacpi_rfk_rfkill_ops,
atp_rfk); if (!atp_rfk || !atp_rfk->rfkill) {
pr_err("failed to allocate memory for rfkill class\n");
kfree(atp_rfk); return -ENOMEM;
}
atp_rfk->id = id;
atp_rfk->ops = tp_rfkops;
sw_status = (tp_rfkops->get_status)(); if (sw_status < 0) {
pr_err("failed to read initial state for %s, error %d\n",
name, sw_status);
} else {
sw_state = (sw_status == TPACPI_RFK_RADIO_OFF); if (set_default) { /* try to keep the initial state, since we ask the
* firmware to preserve it across S5 in NVRAM */
rfkill_init_sw_state(atp_rfk->rfkill, sw_state);
}
}
hw_state = tpacpi_rfk_check_hwblock_state();
rfkill_set_hw_state(atp_rfk->rfkill, hw_state);
res = rfkill_register(atp_rfk->rfkill); if (res < 0) {
pr_err("failed to register %s rfkill switch: %d\n", name, res);
rfkill_destroy(atp_rfk->rfkill);
kfree(atp_rfk); return res;
}
tpacpi_rfkill_switches[id] = atp_rfk;
pr_info("rfkill switch %s: radio is %sblocked\n",
name, (sw_state || hw_state) ? "" : "un"); return 0;
}
/* This is in the ABI... */ if (tpacpi_rfk_check_hwblock_state()) {
status = TPACPI_RFK_RADIO_OFF;
} else {
status = tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]); if (status < 0) return status;
}
tpacpi_disclose_usertask(attr->attr.name, "set to %ld\n", t);
/* This is in the ABI... */ if (tpacpi_rfk_check_hwblock_state() && !!t) return -EPERM;
res = tpacpi_rfkill_switches[id]->ops->set_status((!!t) ?
TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF);
tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]);
return (res < 0) ? res : count;
}
/* procfs -------------------------------------------------------------- */ staticint tpacpi_rfk_procfs_read(constenum tpacpi_rfk_id id, struct seq_file *m)
{ if (id >= TPACPI_RFK_SW_MAX)
seq_printf(m, "status:\t\tnot supported\n"); else { int status;
/* This is in the ABI... */ if (tpacpi_rfk_check_hwblock_state()) {
status = TPACPI_RFK_RADIO_OFF;
} else {
status = tpacpi_rfk_update_swstate(
tpacpi_rfkill_switches[id]); if (status < 0) return status;
}
/************************************************************************* * Firmware Data
*/
/* * Table of recommended minimum BIOS versions * * Reasons for listing: * 1. Stable BIOS, listed because the unknown amount of * bugs and bad ACPI behaviour on older versions * * 2. BIOS or EC fw with known bugs that trigger on Linux * * 3. BIOS with known reduced functionality in older versions * * We recommend the latest BIOS and EC version. * We only support the latest BIOS and EC fw version as a rule. * * Sources: IBM ThinkPad Public Web Documents (update changelogs), * Information from users in ThinkWiki * * WARNING: we use this table also to detect that the machine is * a ThinkPad in some cases, so don't remove entries lightly.
*/
/* note that unknown versions are set to 0x0000 and we use that */ if ((bios_version > thinkpad_id.bios_release) ||
(ec_version > thinkpad_id.ec_release &&
ec_version != TPACPI_MATCH_ANY_VERSION)) { /* * The changelogs would let us track down the exact * reason, but it is just too much of a pain to track * it. We only list BIOSes that are either really * broken, or really stable to begin with, so it is * best if the user upgrades the firmware anyway.
*/
pr_warn("WARNING: Outdated ThinkPad BIOS/EC firmware\n");
pr_warn("WARNING: This firmware may be missing critical bug fixes and/or important features\n");
}
}
/* * ThinkPad firmware event model * * The ThinkPad firmware has two main event interfaces: normal ACPI * notifications (which follow the ACPI standard), and a private event * interface. * * The private event interface also issues events for the hotkeys. As * the driver gained features, the event handling code ended up being * built around the hotkey subdriver. This will need to be refactored * to a more formal event API eventually. * * Some "hotkeys" are actually supposed to be used as event reports, * such as "brightness has changed", "volume has changed", depending on * the ThinkPad model and how the firmware is operating. * * Unlike other classes, hotkey-class events have mask/unmask control on * non-ancient firmware. However, how it behaves changes a lot with the * firmware model and version.
*/
/* kthread for the hotkey poller */ staticstruct task_struct *tpacpi_hotkey_task;
/* * Acquire mutex to write poller control variables as an * atomic block. * * Increment hotkey_config_change when changing them if you * want the kthread to forget old state. * * See HOTKEY_CONFIG_CRITICAL_START/HOTKEY_CONFIG_CRITICAL_END
*/ staticstruct mutex hotkey_thread_data_mutex; staticunsignedint hotkey_config_change;
/* * hotkey poller control variables * * Must be atomic or readers will also need to acquire mutex * * HOTKEY_CONFIG_CRITICAL_START/HOTKEY_CONFIG_CRITICAL_END * should be used only when the changes need to be taken as * a block, OR when one needs to force the kthread to forget * old state.
*/ static u32 hotkey_source_mask; /* bit mask 0=ACPI,1=NVRAM */ staticunsignedint hotkey_poll_freq = 10; /* Hz */
#define HOTKEY_CONFIG_CRITICAL_START \ do { \
mutex_lock(&hotkey_thread_data_mutex); \
hotkey_config_change++; \
} while (0); #define HOTKEY_CONFIG_CRITICAL_END \
mutex_unlock(&hotkey_thread_data_mutex);
enum { /* The following modes are considered tablet mode for the purpose of * reporting the status to userspace. i.e. in all these modes it makes * sense to disable the laptop input devices such as touchpad and * keyboard.
*/
TP_ACPI_MULTI_MODE_TABLET_LIKE = TP_ACPI_MULTI_MODE_TABLET |
TP_ACPI_MULTI_MODE_STAND |
TP_ACPI_MULTI_MODE_TENT |
TP_ACPI_MULTI_MODE_STAND_TENT,
};
staticint hotkey_gmms_get_tablet_mode(int s, int *has_tablet_mode)
{ int type = (s >> 16) & 0xffff; int value = s & 0xffff; int mode = TP_ACPI_MULTI_MODE_INVALID; int valid_modes = 0;
if (has_tablet_mode)
*has_tablet_mode = 0;
switch (type) { case 1:
valid_modes = TP_ACPI_MULTI_MODE_LAPTOP |
TP_ACPI_MULTI_MODE_TABLET |
TP_ACPI_MULTI_MODE_STAND_TENT; break; case 2:
valid_modes = TP_ACPI_MULTI_MODE_LAPTOP |
TP_ACPI_MULTI_MODE_FLAT |
TP_ACPI_MULTI_MODE_TABLET |
TP_ACPI_MULTI_MODE_STAND |
TP_ACPI_MULTI_MODE_TENT; break; case 3:
valid_modes = TP_ACPI_MULTI_MODE_LAPTOP |
TP_ACPI_MULTI_MODE_FLAT; break; case 4: case 5: /* In mode 4, FLAT is not specified as a valid mode. However, * it can be seen at least on the X1 Yoga 2nd Generation.
*/
valid_modes = TP_ACPI_MULTI_MODE_LAPTOP |
TP_ACPI_MULTI_MODE_FLAT |
TP_ACPI_MULTI_MODE_TABLET |
TP_ACPI_MULTI_MODE_STAND |
TP_ACPI_MULTI_MODE_TENT; break; default:
pr_err("Unknown multi mode status type %d with value 0x%04X, please report this to %s\n",
type, value, TPACPI_MAIL); return 0;
}
if (has_tablet_mode && (valid_modes & TP_ACPI_MULTI_MODE_TABLET_LIKE))
*has_tablet_mode = 1;
switch (value) { case 1:
mode = TP_ACPI_MULTI_MODE_LAPTOP; break; case 2:
mode = TP_ACPI_MULTI_MODE_FLAT; break; case 3:
mode = TP_ACPI_MULTI_MODE_TABLET; break; case 4: if (type == 1)
mode = TP_ACPI_MULTI_MODE_STAND_TENT; else
mode = TP_ACPI_MULTI_MODE_STAND; break; case 5:
mode = TP_ACPI_MULTI_MODE_TENT; break; default: if (type == 5 && value == 0xffff) {
pr_warn("Multi mode status is undetected, assuming laptop\n"); return 0;
}
}
if (!(mode & valid_modes)) {
pr_err("Unknown/reserved multi mode value 0x%04X for type %d, please report this to %s\n",
value, type, TPACPI_MAIL); return 0;
}
/* * Reads current event mask from firmware, and updates * hotkey_acpi_mask accordingly. Also resets any bits * from hotkey_user_mask that are unavailable to be * delivered (shadow requirement of the userspace ABI).
*/ staticint hotkey_mask_get(void)
{
lockdep_assert_held(&hotkey_mutex);
if (tp_features.hotkey_mask) {
u32 m = 0;
if (!acpi_evalf(hkey_handle, &m, "DHKN", "d")) return -EIO;
hotkey_acpi_mask = m;
} else { /* no mask support doesn't mean no event support... */
hotkey_acpi_mask = hotkey_all_mask;
}
staticvoid hotkey_mask_warn_incomplete_mask(void)
{ /* log only what the user can fix... */ const u32 wantedmask = hotkey_driver_mask &
~(hotkey_acpi_mask | hotkey_source_mask) &
(hotkey_all_mask | TPACPI_HKEY_NVRAM_KNOWN_MASK);
if (wantedmask)
pr_notice("required events 0x%08x not enabled!\n", wantedmask);
}
/* * Set the firmware mask when supported * * Also calls hotkey_mask_get to update hotkey_acpi_mask. * * NOTE: does not set bits in hotkey_user_mask, but may reset them.
*/ staticint hotkey_mask_set(u32 mask)
{ int i; int rc = 0;
const u32 fwmask = mask & ~hotkey_source_mask;
lockdep_assert_held(&hotkey_mutex);
if (tp_features.hotkey_mask) { for (i = 0; i < 32; i++) { if (!acpi_evalf(hkey_handle,
NULL, "MHKM", "vdd", i + 1,
!!(mask & (1 << i)))) {
rc = -EIO; break;
}
}
}
/* * We *must* make an inconditional call to hotkey_mask_get to * refresh hotkey_acpi_mask and update hotkey_user_mask * * Take the opportunity to also log when we cannot _enable_ * a given event.
*/ if (!hotkey_mask_get() && !rc && (fwmask & ~hotkey_acpi_mask)) {
pr_notice("asked for hotkey mask 0x%08x, but firmware forced it to 0x%08x\n",
fwmask, hotkey_acpi_mask);
}
if (tpacpi_lifecycle != TPACPI_LIFE_EXITING)
hotkey_mask_warn_incomplete_mask();
return rc;
}
/* * Sets hotkey_user_mask and tries to set the firmware mask
*/ staticint hotkey_user_mask_set(const u32 mask)
{ int rc;
lockdep_assert_held(&hotkey_mutex);
/* Give people a chance to notice they are doing something that
* is bound to go boom on their users sooner or later */ if (!tp_warned.hotkey_mask_ff &&
(mask == 0xffff || mask == 0xffffff ||
mask == 0xffffffff)) {
tp_warned.hotkey_mask_ff = 1;
pr_notice("setting the hotkey mask to 0x%08x is likely not the best way to go about it\n",
mask);
pr_notice("please consider using the driver defaults, and refer to up-to-date thinkpad-acpi documentation\n");
}
/* Try to enable what the user asked for, plus whatever we need.
* this syncs everything but won't enable bits in hotkey_user_mask */
rc = hotkey_mask_set((mask | hotkey_driver_mask) & ~hotkey_source_mask);
/* Enable the available bits in hotkey_user_mask */
hotkey_user_mask = mask & (hotkey_acpi_mask | hotkey_source_mask);
return rc;
}
/* * Sets the driver hotkey mask. * * Can be called even if the hotkey subdriver is inactive
*/ staticint tpacpi_hotkey_driver_mask_set(const u32 mask)
{ int rc;
/* Do the right thing if hotkey_init has not been called yet */ if (!tp_features.hotkey) {
hotkey_driver_mask = mask; return 0;
}
/* * Before the conversion to using the sparse-keymap helpers the driver used to * map the hkey event codes to 0x00 - 0x4d scancodes so that a straight scancode * indexed array could be used to map scancodes to keycodes: * * 0x1001 - 0x1020 -> 0x00 - 0x1f (Original ThinkPad events) * 0x1103 - 0x1116 -> 0x20 - 0x33 (Adaptive keyboard, 2014 X1 Carbon) * 0x1300 - 0x1319 -> 0x34 - 0x4d (Additional keys send in 2017+ models) * * The sparse-keymap tables still use these scancodes for these ranges to * preserve userspace API compatibility (e.g. hwdb keymappings).
*/ if (hkey >= TP_HKEY_EV_ORIG_KEY_START &&
hkey <= TP_HKEY_EV_ORIG_KEY_END) {
scancode = hkey - TP_HKEY_EV_ORIG_KEY_START; if (!(hotkey_user_mask & (1 << scancode))) returntrue; /* Not reported but still a known code */
} elseif (hkey >= TP_HKEY_EV_ADAPTIVE_KEY_START &&
hkey <= TP_HKEY_EV_ADAPTIVE_KEY_END) {
scancode = hkey - TP_HKEY_EV_ADAPTIVE_KEY_START +
TP_ACPI_HOTKEYSCAN_ADAPTIVE_START;
} elseif (hkey >= TP_HKEY_EV_EXTENDED_KEY_START &&
hkey <= TP_HKEY_EV_EXTENDED_KEY_END) {
scancode = hkey - TP_HKEY_EV_EXTENDED_KEY_START +
TP_ACPI_HOTKEYSCAN_EXTENDED_START;
} else { /* * Do not send ACPI netlink events for unknown hotkeys, to * avoid userspace starting to rely on them. Instead these * should be added to the keymap to send evdev events.
*/ if (send_acpi_ev)
*send_acpi_ev = false;
/* Do NOT call without validating scancode first */ staticvoid tpacpi_hotkey_send_key(unsignedint scancode)
{
tpacpi_input_send_key(TP_HKEY_EV_ORIG_KEY_START + scancode, NULL);
}
staticvoid hotkey_read_nvram(struct tp_nvram_state *n, const u32 m)
{
u8 d;
while (i > newvol) {
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
i--;
} while (i < newvol) {
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
i++;
}
}
while (i > newbrt) {
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
i--;
} while (i < newbrt) {
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
i++;
}
}
/* * Handle volume * * This code is supposed to duplicate the IBM firmware behaviour: * - Pressing MUTE issues mute hotkey message, even when already mute * - Pressing Volume up/down issues volume up/down hotkey messages, * even when already at maximum or minimum volume * - The act of unmuting issues volume up/down notification, * depending which key was used to unmute * * We are constrained to what the NVRAM can tell us, which is not much * and certainly not enough if more than one volume hotkey was pressed * since the last poll cycle. * * Just to make our life interesting, some newer Lenovo ThinkPads have * bugs in the BIOS and may fail to update volume_toggle properly.
*/ if (newn->mute) { /* muted */ if (!oldn->mute ||
oldn->volume_toggle != newn->volume_toggle ||
oldn->volume_level != newn->volume_level) { /* recently muted, or repeated mute keypress, or
* multiple presses ending in mute */
issue_volchange(oldn->volume_level, newn->volume_level,
event_mask);
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
}
} else { /* unmute */ if (oldn->mute) { /* recently unmuted, issue 'unmute' keypress */
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
} if (oldn->volume_level != newn->volume_level) {
issue_volchange(oldn->volume_level, newn->volume_level,
event_mask);
} elseif (oldn->volume_toggle != newn->volume_toggle) { /* repeated vol up/down keypress at end of scale ? */ if (newn->volume_level == 0)
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN); elseif (newn->volume_level >= TP_NVRAM_LEVEL_VOLUME_MAX)
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
}
}
/* handle brightness */ if (oldn->brightness_level != newn->brightness_level) {
issue_brightnesschange(oldn->brightness_level,
newn->brightness_level, event_mask);
} elseif (oldn->brightness_toggle != newn->brightness_toggle) { /* repeated key presses that didn't change state */ if (newn->brightness_level == 0)
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND); elseif (newn->brightness_level >= bright_maxlvl
&& !tp_features.bright_unkfw)
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
}
/* * Polling driver * * We track all events in hotkey_source_mask all the time, since * most of them are edge-based. We only issue those requested by * hotkey_user_mask or hotkey_driver_mask, though.
*/ staticint hotkey_kthread(void *data)
{ struct tp_nvram_state s[2] = { 0 };
u32 poll_mask, event_mask; unsignedint si, so; unsignedlong t; unsignedint change_detector; unsignedint poll_freq; bool was_frozen;
if (tpacpi_lifecycle == TPACPI_LIFE_EXITING) gotoexit;
while (!kthread_should_stop()) { if (t == 0) { if (likely(poll_freq))
t = 1000/poll_freq; else
t = 100; /* should never happen... */
}
t = msleep_interruptible(t); if (unlikely(kthread_freezable_should_stop(&was_frozen))) break;
if (t > 0 && !was_frozen) continue;
mutex_lock(&hotkey_thread_data_mutex); if (was_frozen || hotkey_config_change != change_detector) { /* forget old state on thaw or config change */
si = so;
t = 0;
change_detector = hotkey_config_change;
}
poll_mask = hotkey_source_mask;
event_mask = hotkey_source_mask &
(hotkey_driver_mask | hotkey_user_mask);
poll_freq = hotkey_poll_freq;
mutex_unlock(&hotkey_thread_data_mutex);
if (likely(poll_mask)) {
hotkey_read_nvram(&s[si], poll_mask); if (likely(si != so)) {
hotkey_compare_and_issue_event(&s[so], &s[si],
event_mask);
}
}
/* * Sync both the hw and sw blocking state of all switches
*/ staticvoid tpacpi_send_radiosw_update(void)
{ int wlsw;
/* * We must sync all rfkill controllers *before* issuing any * rfkill input events, or we will race the rfkill core input * handler. * * tpacpi_inputdev_send_mutex works as a synchronization point * for the above. * * We optimize to avoid numerous calls to hotkey_get_wlsw.
*/
wlsw = hotkey_get_wlsw();
/* Sync hw blocking state first if it is hw-blocked */ if (wlsw == TPACPI_RFK_RADIO_OFF)
tpacpi_rfk_update_hwblock_state(true);
/* Sync hw blocking state last if it is hw-unblocked */ if (wlsw == TPACPI_RFK_RADIO_ON)
tpacpi_rfk_update_hwblock_state(false);
/* Issue rfkill input event for WLSW switch */ if (!(wlsw < 0)) {
mutex_lock(&tpacpi_inputdev_send_mutex);
/* * this can be unconditional, as we will poll state again * if userspace uses the notify to read data
*/
hotkey_radio_sw_notify_change();
}
staticvoid hotkey_exit(void)
{
mutex_lock(&hotkey_mutex);
hotkey_poll_stop_sync();
dbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_HKEY, "restoring original HKEY status and mask\n"); /* yes, there is a bitwise or below, we want the
* functions to be called even if one of them fail */ if (((tp_features.hotkey_mask &&
hotkey_mask_set(hotkey_orig_mask)) |
hotkey_status_set(false)) != 0)
pr_err("failed to restore hot key mask to BIOS defaults\n");
if (acpi_evalf(hkey_handle, &res, "GMMS", "qdd", 0)) { int has_tablet_mode;
in_tablet_mode = hotkey_gmms_get_tablet_mode(res,
&has_tablet_mode); /* * The Yoga 11e series has 2 accelerometers described by a * BOSC0200 ACPI node. This setup relies on a Windows service * which calls special ACPI methods on this node to report * the laptop/tent/tablet mode to the EC. The bmc150 iio driver * does not support this, so skip the hotkey on these models.
*/ if (has_tablet_mode && !dual_accel_detect())
tp_features.hotkey_tablet = TP_HOTKEY_TABLET_USES_GMMS;
type = "GMMS";
} elseif (acpi_evalf(hkey_handle, &res, "MHKG", "qd")) { /* For X41t, X60t, X61t Tablets... */
tp_features.hotkey_tablet = TP_HOTKEY_TABLET_USES_MHKG;
in_tablet_mode = !!(res & TP_HOTKEY_TABLET_MASK);
type = "MHKG";
}
if (!tp_features.hotkey_tablet) return 0;
pr_info("Tablet mode switch found (type: %s), currently in %s mode\n",
type, in_tablet_mode ? "tablet" : "laptop");
/* mask not supported on 600e/x, 770e, 770x, A21e, A2xm/p, A30, R30, R31, T20-22, X20-21, X22-24. Detected by checking
for HKEY interface version 0x100 */ if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) {
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, "firmware HKEY interface version: 0x%x\n",
hkeyv);
switch (hkeyv >> 8) { case 1: /* * MHKV 0x100 in A31, R40, R40e, * T4x, X31, and later
*/
/* Paranoia check AND init hotkey_all_mask */ if (!acpi_evalf(hkey_handle, &hotkey_all_mask, "MHKA", "qd")) {
pr_err("missing MHKA handler, please report this to %s\n",
TPACPI_MAIL); /* Fallback: pre-init for FN+F3,F4,F12 */
hotkey_all_mask = 0x080cU;
} else {
tp_features.hotkey_mask = 1;
} break;
case 2: /* * MHKV 0x200 in X1, T460s, X260, T560, X1 Tablet (2016)
*/
/* Paranoia check AND init hotkey_all_mask */ if (!acpi_evalf(hkey_handle, &hotkey_all_mask, "MHKA", "dd", 1)) {
pr_err("missing MHKA handler, please report this to %s\n",
TPACPI_MAIL); /* Fallback: pre-init for FN+F3,F4,F12 */
hotkey_all_mask = 0x080cU;
} else {
tp_features.hotkey_mask = 1;
}
/* * Check if we have an adaptive keyboard, like on the * Lenovo Carbon X1 2014 (2nd Gen).
*/ if (acpi_evalf(hkey_handle, &hotkey_adaptive_all_mask, "MHKA", "dd", 2)) { if (hotkey_adaptive_all_mask != 0)
tp_features.has_adaptive_kbd = true;
} else {
tp_features.has_adaptive_kbd = false;
hotkey_adaptive_all_mask = 0x0U;
} break;
default:
pr_err("unknown version of the HKEY interface: 0x%x\n",
hkeyv);
pr_err("please report this to %s\n", TPACPI_MAIL); break;
}
}
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, "hotkey masks are %s\n",
str_supported(tp_features.hotkey_mask));
/* Init hotkey_all_mask if not initialized yet */ if (!tp_features.hotkey_mask && !hotkey_all_mask &&
(quirks & TPACPI_HK_Q_INIMASK))
hotkey_all_mask = 0x080cU; /* FN+F12, FN+F4, FN+F3 */
/* Init hotkey_acpi_mask and hotkey_orig_mask */ if (tp_features.hotkey_mask) { /* hotkey_source_mask *must* be zero for
* the first hotkey_mask_get to return hotkey_orig_mask */
mutex_lock(&hotkey_mutex);
res = hotkey_mask_get();
mutex_unlock(&hotkey_mutex); if (res) return res;
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES if (dbg_wlswemul) {
tp_features.hotkey_wlsw = 1;
radiosw_state = !!tpacpi_wlsw_emulstate;
pr_info("radio switch emulation enabled\n");
} else #endif /* Not all thinkpads have a hardware radio switch */ if (acpi_evalf(hkey_handle, &status, "WLSW", "qd")) {
tp_features.hotkey_wlsw = 1;
radiosw_state = !!status;
pr_info("radio switch found; radios are %s\n", str_enabled_disabled(status & BIT(0)));
}
tabletsw_state = hotkey_init_tablet_mode();
/* Set up key map */
keymap_id = tpacpi_check_quirks(tpacpi_keymap_qtable,
ARRAY_SIZE(tpacpi_keymap_qtable));
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, "using keymap number %lu\n", keymap_id);
/* Keys which should be reserved on both IBM and Lenovo models */
hotkey_reserved_mask = TP_ACPI_HKEY_KBD_LIGHT_MASK |
TP_ACPI_HKEY_VOLUP_MASK |
TP_ACPI_HKEY_VOLDWN_MASK |
TP_ACPI_HKEY_MUTE_MASK; /* * Reserve brightness up/down unconditionally on IBM models, on Lenovo * models these are disabled based on acpi_video_get_backlight_type().
*/ if (keymap_id == TPACPI_KEYMAP_IBM_GENERIC) {
hotkey_reserved_mask |= TP_ACPI_HKEY_BRGHTUP_MASK |
TP_ACPI_HKEY_BRGHTDWN_MASK;
keymap = keymap_ibm;
} else {
keymap = keymap_lenovo;
}
res = sparse_keymap_setup(tpacpi_inputdev, keymap, NULL); if (res) return res;
if (tp_features.hotkey_wlsw) {
input_set_capability(tpacpi_inputdev, EV_SW, SW_RFKILL_ALL);
input_report_switch(tpacpi_inputdev,
SW_RFKILL_ALL, radiosw_state);
} if (tp_features.hotkey_tablet) {
input_set_capability(tpacpi_inputdev, EV_SW, SW_TABLET_MODE);
input_report_switch(tpacpi_inputdev,
SW_TABLET_MODE, tabletsw_state);
}
/* Do not issue duplicate brightness change events to * userspace. tpacpi_detect_brightness_capabilities() must have
* been called before this point */ if (acpi_video_get_backlight_type() != acpi_backlight_vendor) {
pr_info("This ThinkPad has standard ACPI backlight brightness control, supported by the ACPI video driver\n");
pr_notice("Disabling thinkpad-acpi brightness events by default...\n");
/* Disable brightness up/down on Lenovo thinkpads when * ACPI is handling them, otherwise it is plain impossible
* for userspace to do something even remotely sane */
hotkey_reserved_mask |= TP_ACPI_HKEY_BRGHTUP_MASK |
TP_ACPI_HKEY_BRGHTDWN_MASK;
}
/* Enable doubletap by default */
tp_features.trackpoint_doubletap = 1;
return 0;
}
/* Thinkpad X1 Carbon support 5 modes including Home mode, Web browser * mode, Web conference mode, Function mode and Lay-flat mode. * We support Home mode and Function mode currently. * * Will consider support rest of modes in future. *
*/ staticconstint adaptive_keyboard_modes[] = {
HOME_MODE, /* WEB_BROWSER_MODE = 2,
WEB_CONFERENCE_MODE = 3, */
FUNCTION_MODE
};
/* press Fn key a while second, it will switch to Function Mode. Then * release Fn key, previous mode be restored.
*/ staticbool adaptive_keyboard_mode_is_saved; staticint adaptive_keyboard_prev_mode;
staticint adaptive_keyboard_get_mode(void)
{ int mode = 0;
/* 0x1000-0x1FFF: key presses */ staticbool hotkey_notify_hotkey(const u32 hkey, bool *send_acpi_ev)
{ /* Never send ACPI netlink events for original hotkeys (hkey: 0x1001 - 0x1020) */ if (hkey >= TP_HKEY_EV_ORIG_KEY_START && hkey <= TP_HKEY_EV_ORIG_KEY_END) {
*send_acpi_ev = false;
/* Original hotkeys may be polled from NVRAM instead */ unsignedint scancode = hkey - TP_HKEY_EV_ORIG_KEY_START; if (hotkey_source_mask & (1 << scancode)) returntrue;
}
case TP_HKEY_EV_WKUP_S3_BAYEJ: /* suspend, bay eject */ case TP_HKEY_EV_WKUP_S4_BAYEJ: /* hibernation, bay eject */
hotkey_wakeup_reason = TP_ACPI_WAKEUP_BAYEJ;
*send_acpi_ev = false; break;
case TP_HKEY_EV_WKUP_S3_BATLOW: /* Battery on critical low level/S3 */ case TP_HKEY_EV_WKUP_S4_BATLOW: /* Battery on critical low level/S4 */
pr_alert("EMERGENCY WAKEUP: battery almost empty\n"); /* how to auto-heal: */ /* 2313: woke up from S3, go to S4/S5 */ /* 2413: woke up from S4, go to S5 */ break;
default: returnfalse;
}
if (hotkey_wakeup_reason != TP_ACPI_WAKEUP_NONE) {
pr_info("woke up due to a hot-unplug request...\n");
hotkey_wakeup_reason_notify_change();
} returntrue;
}
case TP_HKEY_EV_HOTPLUG_DOCK: /* docked to port replicator */
pr_info("docked into hotplug port replicator\n"); returntrue; case TP_HKEY_EV_HOTPLUG_UNDOCK: /* undocked from port replicator */
pr_info("undocked from hotplug port replicator\n"); returntrue;
/* * Deliberately ignore attaching and detaching the keybord cover to avoid * duplicates from intel-vbtn, which already emits SW_TABLET_MODE events * to userspace. * * Please refer to the following thread for more information and a preliminary * implementation using the GTOP ("Get Tablet OPtions") interface that could be * extended to other attachment options of the ThinkPad X1 Tablet series, such as * the Pico cartridge dock module: * https://lore.kernel.org/platform-driver-x86/38cb8265-1e30-d547-9e12-b4ae290be737@a-kobel.de/
*/ case TP_HKEY_EV_KBD_COVER_ATTACH: case TP_HKEY_EV_KBD_COVER_DETACH:
*send_acpi_ev = false; returntrue;
default: returnfalse;
}
}
/* 0x5000-0x5FFF: human interface helpers */ staticbool hotkey_notify_usrevent(const u32 hkey, bool *send_acpi_ev)
{ switch (hkey) { case TP_HKEY_EV_PEN_INSERTED: /* X61t: tablet pen inserted into bay */ case TP_HKEY_EV_PEN_REMOVED: /* X61t: tablet pen removed from bay */ returntrue;
case TP_HKEY_EV_TABLET_TABLET: /* X41t-X61t: tablet mode */ case TP_HKEY_EV_TABLET_NOTEBOOK: /* X41t-X61t: normal mode */
tpacpi_input_send_tabletsw();
hotkey_tablet_mode_notify_change();
*send_acpi_ev = false; returntrue;
case TP_HKEY_EV_LID_CLOSE: /* Lid closed */ case TP_HKEY_EV_LID_OPEN: /* Lid opened */ case TP_HKEY_EV_BRGHT_CHANGED: /* brightness changed */ /* do not propagate these events */
*send_acpi_ev = false; returntrue;
/* 0x6000-0x6FFF: thermal alarms/notices and keyboard events */ staticbool hotkey_notify_6xxx(const u32 hkey, bool *send_acpi_ev)
{ switch (hkey) { case TP_HKEY_EV_THM_TABLE_CHANGED:
pr_debug("EC reports: Thermal Table has changed\n"); /* recommended action: do nothing, we don't have
* Lenovo ATM information */ returntrue; case TP_HKEY_EV_THM_CSM_COMPLETED:
pr_debug("EC reports: Thermal Control Command set completed (DYTC)\n"); /* Thermal event - pass on to event handler */
tpacpi_driver_event(hkey); returntrue; case TP_HKEY_EV_THM_TRANSFM_CHANGED:
pr_debug("EC reports: Thermal Transformation changed (GMTS)\n"); /* recommended action: do nothing, we don't have
* Lenovo ATM information */ returntrue; case TP_HKEY_EV_ALARM_BAT_HOT:
pr_crit("THERMAL ALARM: battery is too hot!\n"); /* recommended action: warn user through gui */ break; case TP_HKEY_EV_ALARM_BAT_XHOT:
pr_alert("THERMAL EMERGENCY: battery is extremely hot!\n"); /* recommended action: immediate sleep/hibernate */ break; case TP_HKEY_EV_ALARM_BAT_LIM_CHANGE:
pr_debug("Battery Info: battery charge threshold changed\n"); /* User changed charging threshold. No action needed */ returntrue; case TP_HKEY_EV_ALARM_SENSOR_HOT:
pr_crit("THERMAL ALARM: a sensor reports something is too hot!\n"); /* recommended action: warn user through gui, that */ /* some internal component is too hot */ break; case TP_HKEY_EV_ALARM_SENSOR_XHOT:
pr_alert("THERMAL EMERGENCY: a sensor reports something is extremely hot!\n"); /* recommended action: immediate sleep/hibernate */ break; case TP_HKEY_EV_AC_CHANGED: /* X120e, X121e, X220, X220i, X220t, X230, T420, T420s, W520: * AC status changed; can be triggered by plugging or
* unplugging AC adapter, docking or undocking. */
fallthrough;
case TP_HKEY_EV_KEY_NUMLOCK: case TP_HKEY_EV_KEY_FN: /* key press events, we just ignore them as long as the EC
* is still reporting them in the normal keyboard stream */
*send_acpi_ev = false; returntrue;
case TP_HKEY_EV_KEY_FN_ESC: /* Get the media key status to force the status LED to update */
acpi_evalf(hkey_handle, NULL, "GMKS", "v");
*send_acpi_ev = false; returntrue;
case TP_HKEY_EV_TABLET_CHANGED:
tpacpi_input_send_tabletsw();
hotkey_tablet_mode_notify_change();
*send_acpi_ev = false; returntrue;
case TP_HKEY_EV_PALM_DETECTED: case TP_HKEY_EV_PALM_UNDETECTED: /* palm detected - pass on to event handler */
palmsensor_refresh(); returntrue;
default: /* report simply as unknown, no sensor dump */ returnfalse;
}
thermal_dump_all_sensors(); returntrue;
}
staticbool hotkey_notify_8xxx(const u32 hkey, bool *send_acpi_ev)
{ switch (hkey) { case TP_HKEY_EV_TRACK_DOUBLETAP: if (tp_features.trackpoint_doubletap)
tpacpi_input_send_key(hkey, send_acpi_ev);
if (event != 0x80) {
pr_err("unknown HKEY notification event %d\n", event); /* forward it to userspace, maybe it knows how to handle it */
acpi_bus_generate_netlink_event(
ibm->acpi->device->pnp.device_class,
dev_name(&ibm->acpi->device->dev),
event, 0); return;
}
while (1) { if (!acpi_evalf(hkey_handle, &hkey, "MHKP", "d")) {
pr_err("failed to retrieve HKEY event\n"); return;
}
if (hkey == 0) { /* queue empty */ return;
}
send_acpi_ev = true;
known_ev = false;
switch (hkey >> 12) { case 1: /* 0x1000-0x1FFF: key presses */
known_ev = hotkey_notify_hotkey(hkey, &send_acpi_ev); break; case 2: /* 0x2000-0x2FFF: Wakeup reason */
known_ev = hotkey_notify_wakeup(hkey, &send_acpi_ev); break; case 3: /* 0x3000-0x3FFF: bay-related wakeups */ switch (hkey) { case TP_HKEY_EV_BAYEJ_ACK:
hotkey_autosleep_ack = 1;
pr_info("bay ejected\n");
hotkey_wakeup_hotunplug_complete_notify_change();
known_ev = true; break; case TP_HKEY_EV_OPTDRV_EJ: /* FIXME: kick libata if SATA link offline */
known_ev = true; break;
} break; case 4: /* 0x4000-0x4FFF: dock-related events */
known_ev = hotkey_notify_dockevent(hkey, &send_acpi_ev); break; case 5: /* 0x5000-0x5FFF: human interface helpers */
known_ev = hotkey_notify_usrevent(hkey, &send_acpi_ev); break; case 6: /* 0x6000-0x6FFF: thermal alarms/notices and
* keyboard events */
known_ev = hotkey_notify_6xxx(hkey, &send_acpi_ev); break; case 7: /* 0x7000-0x7FFF: misc */ if (tp_features.hotkey_wlsw &&
hkey == TP_HKEY_EV_RFKILL_CHANGED) {
tpacpi_send_radiosw_update();
send_acpi_ev = false;
known_ev = true;
} break; case 8: /* 0x8000-0x8FFF: misc2 */
known_ev = hotkey_notify_8xxx(hkey, &send_acpi_ev); break;
} if (!known_ev) {
pr_notice("unhandled HKEY event 0x%04x\n", hkey);
pr_notice("please report the conditions when this event happened to %s\n",
TPACPI_MAIL);
}
staticvoid hotkey_suspend(void)
{ /* Do these on suspend, we get the events on early resume! */
hotkey_wakeup_reason = TP_ACPI_WAKEUP_NONE;
hotkey_autosleep_ack = 0;
/* save previous mode of adaptive keyboard of X1 Carbon */ if (tp_features.has_adaptive_kbd) { if (!acpi_evalf(hkey_handle, &adaptive_keyboard_prev_mode, "GTRW", "dd", 0)) {
pr_err("Cannot read adaptive keyboard mode.\n");
}
}
}
mutex_lock(&hotkey_mutex); if (hotkey_status_set(true) < 0 ||
hotkey_mask_set(hotkey_acpi_mask) < 0)
pr_err("error while attempting to reset the event firmware interface\n");
mutex_unlock(&hotkey_mutex);
if (!tp_features.hotkey) {
seq_printf(m, "status:\t\tnot supported\n"); return 0;
}
if (mutex_lock_killable(&hotkey_mutex)) return -ERESTARTSYS;
res = hotkey_status_get(&status); if (!res)
res = hotkey_mask_get();
mutex_unlock(&hotkey_mutex); if (res) return res;
staticvoid hotkey_enabledisable_warn(bool enable)
{
tpacpi_log_usertask("procfs hotkey enable/disable"); if (!WARN((tpacpi_lifecycle == TPACPI_LIFE_RUNNING || !enable),
pr_fmt("hotkey enable/disable functionality has been removed from the driver. Hotkeys are always enabled.\n")))
pr_err("Please remove the hotkey=enable module parameter, it is deprecated. Hotkeys are always enabled.\n");
}
staticint hotkey_write(char *buf)
{ int res;
u32 mask; char *cmd;
if (!tp_features.hotkey) return -ENODEV;
if (mutex_lock_killable(&hotkey_mutex)) return -ERESTARTSYS;
mask = hotkey_user_mask;
res = 0; while ((cmd = strsep(&buf, ","))) { if (strstarts(cmd, "enable")) {
hotkey_enabledisable_warn(1);
} elseif (strstarts(cmd, "disable")) {
hotkey_enabledisable_warn(0);
res = -EPERM;
} elseif (strstarts(cmd, "reset")) {
mask = (hotkey_all_mask | hotkey_source_mask)
& ~hotkey_reserved_mask;
} elseif (sscanf(cmd, "0x%x", &mask) == 1) { /* mask set */
} elseif (sscanf(cmd, "%x", &mask) == 1) { /* mask set */
} else {
res = -EINVAL; goto errexit;
}
}
if (!res) {
tpacpi_disclose_usertask("procfs hotkey", "set mask to 0x%08x\n", mask);
res = hotkey_user_mask_set(mask);
}
staticvoid bluetooth_shutdown(void)
{ /* Order firmware to save current state to NVRAM */ if (!acpi_evalf(NULL, NULL, "\\BLTH", "vd",
TP_ACPI_BLTH_SAVE_STATE))
pr_notice("failed to save bluetooth state to NVRAM\n"); else
vdbg_printk(TPACPI_DBG_RFKILL, "bluetooth state saved to NVRAM\n");
}
staticint __init have_bt_fwbug(void)
{ /* * Some AMD based ThinkPads have a firmware bug that calling * "GBDC" will cause bluetooth on Intel wireless cards blocked
*/ if (tp_features.quirks && tp_features.quirks->btusb_bug &&
pci_dev_present(fwbug_cards_ids)) {
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
FW_BUG "disable bluetooth subdriver for Intel cards\n"); return 1;
} else return 0;
}
staticint __init bluetooth_init(struct ibm_init_struct *iibm)
{ int res; int status = 0;
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL, "initializing bluetooth subdriver\n");
TPACPI_ACPIHANDLE_INIT(hkey);
/* bluetooth not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
G4x, R30, R31, R40e, R50e, T20-22, X20-21 */
tp_features.bluetooth = !have_bt_fwbug() && hkey_handle &&
acpi_evalf(hkey_handle, &status, "GBDC", "qd");
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL, "bluetooth is %s, status 0x%02x\n",
str_supported(tp_features.bluetooth),
status);
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES if (dbg_bluetoothemul) {
tp_features.bluetooth = 1;
pr_info("bluetooth switch emulation enabled\n");
} else #endif if (tp_features.bluetooth &&
!(status & TP_ACPI_BLUETOOTH_HWPRESENT)) { /* no bluetooth hardware present in system */
tp_features.bluetooth = 0;
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL, "bluetooth hardware not installed\n");
}
if (!tp_features.bluetooth) return -ENODEV;
res = tpacpi_new_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID,
&bluetooth_tprfk_ops,
RFKILL_TYPE_BLUETOOTH,
TPACPI_RFK_BLUETOOTH_SW_NAME, true); return res;
}
staticvoid wan_shutdown(void)
{ /* Order firmware to save current state to NVRAM */ if (!acpi_evalf(NULL, NULL, "\\WGSV", "vd",
TP_ACPI_WGSV_SAVE_STATE))
pr_notice("failed to save WWAN state to NVRAM\n"); else
vdbg_printk(TPACPI_DBG_RFKILL, "WWAN state saved to NVRAM\n");
}
staticvoid video_exit(void)
{
dbg_printk(TPACPI_DBG_EXIT, "restoring original video autoswitch mode\n"); if (video_autosw_set(video_orig_autosw))
pr_err("error while trying to restore original video autoswitch mode\n");
}
staticint video_outputsw_get(void)
{ int status = 0; int i;
switch (video_supported) { case TPACPI_VIDEO_570: if (!acpi_evalf(NULL, &i, "\\_SB.PHS", "dd",
TP_ACPI_VIDEO_570_PHSCMD)) return -EIO;
status = i & TP_ACPI_VIDEO_570_PHSMASK; break; case TPACPI_VIDEO_770: if (!acpi_evalf(NULL, &i, "\\VCDL", "d")) return -EIO; if (i)
status |= TP_ACPI_VIDEO_S_LCD; if (!acpi_evalf(NULL, &i, "\\VCDC", "d")) return -EIO; if (i)
status |= TP_ACPI_VIDEO_S_CRT; break; case TPACPI_VIDEO_NEW: if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 1) ||
!acpi_evalf(NULL, &i, "\\VCDC", "d")) return -EIO; if (i)
status |= TP_ACPI_VIDEO_S_CRT;
if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0) ||
!acpi_evalf(NULL, &i, "\\VCDL", "d")) return -EIO; if (i)
status |= TP_ACPI_VIDEO_S_LCD; if (!acpi_evalf(NULL, &i, "\\VCDD", "d")) return -EIO; if (i)
status |= TP_ACPI_VIDEO_S_DVI; break; default: return -ENOSYS;
}
return status;
}
staticint video_outputsw_set(int status)
{ int autosw; int res = 0;
switch (video_supported) { case TPACPI_VIDEO_570:
res = acpi_evalf(NULL, NULL, "\\_SB.PHS2", "vdd",
TP_ACPI_VIDEO_570_PHS2CMD,
status | TP_ACPI_VIDEO_570_PHS2SET); break; case TPACPI_VIDEO_770:
autosw = video_autosw_get(); if (autosw < 0) return autosw;
res = video_autosw_set(1); if (res) return res;
res = acpi_evalf(vid_handle, NULL, "ASWT", "vdd", status * 0x100, 0); if (!autosw && video_autosw_set(autosw)) {
pr_err("video auto-switch left enabled due to error\n"); return -EIO;
} break; case TPACPI_VIDEO_NEW:
res = acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80) &&
acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1); break; default: return -ENOSYS;
}
return (res) ? 0 : -EIO;
}
staticint video_autosw_get(void)
{ int autosw = 0;
switch (video_supported) { case TPACPI_VIDEO_570: if (!acpi_evalf(vid_handle, &autosw, "SWIT", "d")) return -EIO; break; case TPACPI_VIDEO_770: case TPACPI_VIDEO_NEW: if (!acpi_evalf(vid_handle, &autosw, "^VDEE", "d")) return -EIO; break; default: return -ENOSYS;
}
staticint video_outputsw_cycle(void)
{ int autosw = video_autosw_get(); int res;
if (autosw < 0) return autosw;
switch (video_supported) { case TPACPI_VIDEO_570:
res = video_autosw_set(1); if (res) return res;
res = acpi_evalf(ec_handle, NULL, "_Q16", "v"); break; case TPACPI_VIDEO_770: case TPACPI_VIDEO_NEW:
res = video_autosw_set(1); if (res) return res;
res = acpi_evalf(vid_handle, NULL, "VSWT", "v"); break; default: return -ENOSYS;
} if (!autosw && video_autosw_set(autosw)) {
pr_err("video auto-switch left enabled due to error\n"); return -EIO;
}
enum { /* For TPACPI_LED_OLD */
TPACPI_LED_EC_HLCL = 0x0c, /* EC reg to get led to power on */
TPACPI_LED_EC_HLBL = 0x0d, /* EC reg to blink a lit led */
TPACPI_LED_EC_HLMS = 0x0e, /* EC reg to select led to command */
};
vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n",
str_supported(led_supported), led_supported);
if (led_supported == TPACPI_LED_NONE) return -ENODEV;
tpacpi_leds = kcalloc(TPACPI_LED_NUMLEDS, sizeof(*tpacpi_leds),
GFP_KERNEL); if (!tpacpi_leds) {
pr_err("Out of memory for LED data\n"); return -ENOMEM;
}
for (i = 0; i < TPACPI_LED_NUMLEDS; i++) {
tpacpi_leds[i].led = -1;
if (!tpacpi_is_led_restricted(i) && test_bit(i, &useful_leds)) {
rc = tpacpi_init_led(i); if (rc < 0) {
led_exit(); return rc;
}
}
}
#ifdef CONFIG_THINKPAD_ACPI_UNSAFE_LEDS
pr_notice("warning: userspace override of important firmware LEDs is enabled\n"); #endif return 0;
}
if (led_supported == TPACPI_LED_570) { /* 570 */ int i, status; for (i = 0; i < 8; i++) {
status = led_get_status(i); if (status < 0) return -EIO;
seq_printf(m, "%d:\t\t%s\n", i, str_led_status(status));
}
}
seq_printf(m, "commands:\t<led> on, <led> off, <led> blink (<led> is 0-15)\n");
/* Function to check thermal read mode */ staticenum thermal_access_mode __init thermal_read_mode_check(void)
{
u8 t, ta1, ta2, ver = 0; int i; int acpi_tmp7;
if (thinkpad_id.ec_model) { /* * Direct EC access mode: sensors at registers 0x78-0x7F, * 0xC0-0xC7. Registers return 0x00 for non-implemented, * thermal sensors return 0x80 when not available. * * In some special cases (when Power Supply ID is 0xC2) * above rule causes thermal control issues. Offset 0xEF * determines EC version. 0xC0-0xC7 are not thermal registers * in Ver 3.
*/ if (!acpi_ec_read(TP_EC_FUNCREV, &ver))
pr_warn("Thinkpad ACPI EC unable to access EC version\n");
/* Support for Thinkpads with non-standard address */ if (thermal_with_ns_address) {
pr_info("ECFW with non-standard thermal registers found\n"); return TPACPI_THERMAL_TPEC_12;
}
ta1 = ta2 = 0; for (i = 0; i < 8; i++) { if (acpi_ec_read(TP_EC_THERMAL_TMP0 + i, &t)) {
ta1 |= t;
} else {
ta1 = 0; break;
} if (ver < 3) { if (acpi_ec_read(TP_EC_THERMAL_TMP8 + i, &t)) {
ta2 |= t;
} else {
ta1 = 0; break;
}
}
}
if (ta1 == 0) { /* This is sheer paranoia, but we handle it anyway */ if (acpi_tmp7) {
pr_err("ThinkPad ACPI EC access misbehaving, falling back to ACPI TMPx access mode\n"); return TPACPI_THERMAL_ACPI_TMP07;
}
pr_err("ThinkPad ACPI EC access misbehaving, disabling thermal sensors access\n"); return TPACPI_THERMAL_NONE;
}
/* * ThinkPads can read brightness from two places: EC HBRV (0x31), or * CMOS NVRAM byte 0x5E, bits 0-3. * * EC HBRV (0x31) has the following layout * Bit 7: unknown function * Bit 6: unknown function * Bit 5: Z: honour scale changes, NZ: ignore scale changes * Bit 4: must be set to zero to avoid problems * Bit 3-0: backlight brightness level * * brightness_get_raw returns status data in the HBRV layout * * WARNING: The X61 has been verified to use HBRV for something else, so * this should be used _only_ on IBM ThinkPads, and maybe with some careful * testing on the very early *60 Lenovo models...
*/
for (i = current_value; i != value; i += inc) if (issue_thinkpad_cmos_command(cmos_cmd)) return -EIO;
return 0;
}
/* May return EINTR which can always be mapped to ERESTARTSYS */ staticint brightness_set(unsignedint value)
{ int res;
if (value > bright_maxlvl) return -EINVAL;
vdbg_printk(TPACPI_DBG_BRGHT, "set backlight level to %d\n", value);
res = mutex_lock_killable(&brightness_mutex); if (res < 0) return res;
switch (brightness_mode) { case TPACPI_BRGHT_MODE_EC: case TPACPI_BRGHT_MODE_ECNVRAM:
res = tpacpi_brightness_set_ec(value); break; case TPACPI_BRGHT_MODE_UCMS_STEP:
res = tpacpi_brightness_set_ucmsstep(value); break; default:
res = -ENXIO;
}
mutex_unlock(&brightness_mutex); return res;
}
/* sysfs backlight class ----------------------------------------------- */
staticint brightness_update_status(struct backlight_device *bd)
{ int level = backlight_get_brightness(bd);
dbg_printk(TPACPI_DBG_BRGHT, "backlight: attempt to set level to %d\n",
level);
/* it is the backlight class's job (caller) to handle
* EINTR and other errors properly */ return brightness_set(level);
}
staticint brightness_get(struct backlight_device *bd)
{ int status, res;
res = mutex_lock_killable(&brightness_mutex); if (res < 0) return 0;
/* * Returns 0 (no ACPI _BCL or _BCL invalid), or size of brightness map
*/ staticunsignedint __init tpacpi_check_std_acpi_brightness_support(void)
{
acpi_handle video_device; int bcl_levels = 0;
tpacpi_acpi_handle_locate("video", NULL, &video_device); if (video_device)
bcl_levels = tpacpi_query_bcl_levels(video_device);
tp_features.bright_acpimode = (bcl_levels > 0);
return (bcl_levels > 2) ? (bcl_levels - 2) : 0;
}
/* * These are only useful for models that have only one possibility * of GPU. If the BIOS model handles both ATI and Intel, don't use * these quirks.
*/ #define TPACPI_BRGHT_Q_NOEC 0x0001 /* Must NOT use EC HBRV */ #define TPACPI_BRGHT_Q_EC 0x0002 /* Should or must use EC HBRV */ #define TPACPI_BRGHT_Q_ASK 0x8000 /* Ask for user report */
staticconststruct tpacpi_quirk brightness_quirk_table[] __initconst = { /* Models with ATI GPUs known to require ECNVRAM mode */
TPACPI_Q_IBM('1', 'Y', TPACPI_BRGHT_Q_EC), /* T43/p ATI */
/* Models with ATI GPUs that can use ECNVRAM */
TPACPI_Q_IBM('1', 'R', TPACPI_BRGHT_Q_EC), /* R50,51 T40-42 */
TPACPI_Q_IBM('1', 'Q', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
TPACPI_Q_IBM('7', '6', TPACPI_BRGHT_Q_EC), /* R52 */
TPACPI_Q_IBM('7', '8', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
/* we could run a quirks check here (same table used by
* brightness_init) if needed */
/* * We always attempt to detect acpi support, so as to switch * Lenovo Vista BIOS to ACPI brightness mode even if we are not * going to publish a backlight interface
*/
b = tpacpi_check_std_acpi_brightness_support(); switch (b) { case 16:
bright_maxlvl = 15; break; case 8: case 0:
bright_maxlvl = 7; break; default:
tp_features.bright_unkfw = 1;
bright_maxlvl = b - 1;
}
pr_debug("detected %u brightness levels\n", bright_maxlvl + 1);
}
/* tpacpi_detect_brightness_capabilities() must have run already */
/* if it is unknown, we don't handle it: it wouldn't be safe */ if (tp_features.bright_unkfw) return -ENODEV;
if (!brightness_enable) {
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT, "brightness support disabled by module parameter\n"); return -ENODEV;
}
if (acpi_video_get_backlight_type() != acpi_backlight_vendor) { if (brightness_enable > 1) {
pr_info("Standard ACPI backlight interface available, not loading native one\n"); return -ENODEV;
} elseif (brightness_enable == 1) {
pr_warn("Cannot enable backlight brightness support, ACPI is already handling it. Refer to the acpi_backlight kernel parameter.\n"); return -ENODEV;
}
} elseif (!tp_features.bright_acpimode) {
pr_notice("ACPI backlight interface not available\n"); return -ENODEV;
}
pr_notice("ACPI native brightness control enabled\n");
/* * Check for module parameter bogosity, note that we * init brightness_mode to TPACPI_BRGHT_MODE_MAX in order to be * able to detect "unspecified"
*/ if (brightness_mode > TPACPI_BRGHT_MODE_MAX) return -EINVAL;
/* TPACPI_BRGHT_MODE_AUTO not implemented yet, just use default */ if (brightness_mode == TPACPI_BRGHT_MODE_AUTO ||
brightness_mode == TPACPI_BRGHT_MODE_MAX) { if (quirks & TPACPI_BRGHT_Q_EC)
brightness_mode = TPACPI_BRGHT_MODE_ECNVRAM; else
brightness_mode = TPACPI_BRGHT_MODE_UCMS_STEP;
if (tpacpi_brightness_get_raw(&b) < 0) return -ENODEV;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_PLATFORM;
props.max_brightness = bright_maxlvl;
props.brightness = b & TP_EC_BACKLIGHT_LVLMSK;
ibm_backlight_device = backlight_device_register(TPACPI_BACKLIGHT_DEV_NAME,
NULL, NULL,
&ibm_backlight_data,
&props); if (IS_ERR(ibm_backlight_device)) { int rc = PTR_ERR(ibm_backlight_device);
ibm_backlight_device = NULL;
pr_err("Could not register backlight device\n"); return rc;
}
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT, "brightness is supported\n");
if (quirks & TPACPI_BRGHT_Q_ASK) {
pr_notice("brightness: will use unverified default: brightness_mode=%d\n",
brightness_mode);
pr_notice("brightness: please report to %s whether it works well or not on your ThinkPad\n",
TPACPI_MAIL);
}
/* Added by mistake in early 2007. Probably useless, but it could * be working around some unknown firmware problem where the value * read at startup doesn't match the real hardware state... so leave
* it in place just in case */
backlight_update_status(ibm_backlight_device);
staticint brightness_write(char *buf)
{ int level; int rc; char *cmd;
level = brightness_get(NULL); if (level < 0) return level;
while ((cmd = strsep(&buf, ","))) { if (strstarts(cmd, "up")) { if (level < bright_maxlvl)
level++;
} elseif (strstarts(cmd, "down")) { if (level > 0)
level--;
} elseif (sscanf(cmd, "level %d", &level) == 1 &&
level >= 0 && level <= bright_maxlvl) { /* new level set */
} else return -EINVAL;
}
tpacpi_disclose_usertask("procfs brightness", "set level to %d\n", level);
/* * Now we know what the final level should be, so we try to set it. * Doing it this way makes the syscall restartable in case of EINTR
*/
rc = brightness_set(level); if (!rc && ibm_backlight_device)
backlight_force_update(ibm_backlight_device,
BACKLIGHT_UPDATE_SYSFS); return (rc == -EINTR) ? -ERESTARTSYS : rc;
}
/* * IBM ThinkPads have a simple volume controller with MUTE gating. * Very early Lenovo ThinkPads follow the IBM ThinkPad spec. * * Since the *61 series (and probably also the later *60 series), Lenovo * ThinkPads only implement the MUTE gate. * * EC register 0x30 * Bit 6: MUTE (1 mutes sound) * Bit 3-0: Volume * Other bits should be zero as far as we know. * * This is also stored in CMOS NVRAM, byte 0x60, bit 6 (MUTE), and * bits 3-0 (volume). Other bits in NVRAM may have other functions, * such as bit 7 which is used to detect repeated presses of MUTE, * and we leave them unchanged. * * On newer Lenovo ThinkPads, the EC can automatically change the volume * in response to user input. Unfortunately, this rarely works well. * The laptop changes the state of its internal MUTE gate and, on some * models, sends KEY_MUTE, causing any user code that responds to the * mute button to get confused. The hardware MUTE gate is also * unnecessary, since user code can handle the mute button without * kernel or EC help. * * To avoid confusing userspace, we simply disable all EC-based mute * and volume controls when possible.
*/
/* returns < 0 on error, 0 on no change, 1 on change */ staticint __volume_set_volume_ec(const u8 vol)
{ int rc;
u8 s, n;
if (vol > TP_EC_VOLUME_MAX) return -EINVAL;
if (mutex_lock_killable(&volume_mutex) < 0) return -EINTR;
rc = volume_get_status_ec(&s); if (rc) goto unlock;
n = (s & ~TP_EC_AUDIO_LVL_MSK) | vol;
if (n != s) {
rc = volume_set_status_ec(n); if (!rc)
rc = 1;
}
unlock:
mutex_unlock(&volume_mutex); return rc;
}
staticint volume_set_software_mute(bool startup)
{ int result;
if (!tpacpi_is_lenovo()) return -ENODEV;
if (startup) { if (!acpi_evalf(ec_handle, &software_mute_orig_mode, "HAUM", "qd")) return -EIO;
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, "Initial HAUM setting was %d\n",
software_mute_orig_mode);
}
if (!acpi_evalf(ec_handle, &result, "SAUM", "qdd",
(int)TP_EC_MUTE_BTN_NONE)) return -EIO;
if (result != TP_EC_MUTE_BTN_NONE)
pr_warn("Unexpected SAUM result %d\n",
result);
/* * In software mute mode, the standard codec controls take * precendence, so we unmute the ThinkPad HW switch at * startup. Just on case there are SAUM-capable ThinkPads * with level controls, set max HW volume as well.
*/ if (tp_features.mixer_no_level_control)
result = volume_set_mute(false); else
result = volume_set_status(TP_EC_VOLUME_MAX);
if (result != 0)
pr_warn("Failed to unmute the HW mute switch\n");
return 0;
}
staticvoid volume_exit_software_mute(void)
{ int r;
if (!acpi_evalf(ec_handle, &r, "SAUM", "qdd", software_mute_orig_mode)
|| r != software_mute_orig_mode)
pr_warn("Failed to restore mute mode\n");
}
staticint volume_alsa_set_volume(const u8 vol)
{
dbg_printk(TPACPI_DBG_MIXER, "ALSA: trying to set volume level to %hu\n", vol); return __volume_set_volume_ec(vol);
}
if (alsa_card && alsa_card->private_data) {
d = alsa_card->private_data; if (d->ctl_mute_id)
snd_ctl_notify(alsa_card,
SNDRV_CTL_EVENT_MASK_VALUE,
d->ctl_mute_id); if (d->ctl_vol_id)
snd_ctl_notify(alsa_card,
SNDRV_CTL_EVENT_MASK_VALUE,
d->ctl_vol_id);
}
}
/* * Check for module parameter bogosity, note that we * init volume_mode to TPACPI_VOL_MODE_MAX in order to be * able to detect "unspecified"
*/ if (volume_mode > TPACPI_VOL_MODE_MAX) return -EINVAL;
if (volume_mode == TPACPI_VOL_MODE_UCMS_STEP) {
pr_err("UCMS step volume mode not implemented, please contact %s\n",
TPACPI_MAIL); return -ENODEV;
}
if (volume_capabilities >= TPACPI_VOL_CAP_MAX) return -EINVAL;
/* * The ALSA mixer is our primary interface. * When disabled, don't install the subdriver at all
*/ if (!alsa_enable) {
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, "ALSA mixer disabled by parameter, not loading volume subdriver...\n"); return -ENODEV;
}
if (volume_control_allowed) {
seq_printf(m, "commands:\tunmute, mute\n"); if (!tp_features.mixer_no_level_control) {
seq_printf(m, "commands:\tup, down\n");
seq_printf(m, "commands:\tlevel <level> (<level> is 0-%d)\n",
TP_EC_VOLUME_MAX);
}
}
}
return 0;
}
staticint volume_write(char *buf)
{
u8 s;
u8 new_level, new_mute; int l; char *cmd; int rc;
/* * We do allow volume control at driver startup, so that the * user can set initial state through the volume=... parameter hack.
*/ if (!volume_control_allowed && tpacpi_lifecycle != TPACPI_LIFE_INIT) { if (unlikely(!tp_warned.volume_ctrl_forbidden)) {
tp_warned.volume_ctrl_forbidden = 1;
pr_notice("Console audio control in monitor mode, changes are not allowed\n");
pr_notice("Use the volume_control=1 module parameter to enable volume control\n");
} return -EPERM;
}
rc = volume_get_status(&s); if (rc < 0) return rc;
new_level = s & TP_EC_AUDIO_LVL_MSK;
new_mute = s & TP_EC_AUDIO_MUTESW_MSK;
while ((cmd = strsep(&buf, ","))) { if (!tp_features.mixer_no_level_control) { if (strstarts(cmd, "up")) { if (new_mute)
new_mute = 0; elseif (new_level < TP_EC_VOLUME_MAX)
new_level++; continue;
} elseif (strstarts(cmd, "down")) { if (new_mute)
new_mute = 0; elseif (new_level > 0)
new_level--; continue;
} elseif (sscanf(cmd, "level %u", &l) == 1 &&
l >= 0 && l <= TP_EC_VOLUME_MAX) {
new_level = l; continue;
}
} if (strstarts(cmd, "mute"))
new_mute = TP_EC_AUDIO_MUTESW_MSK; elseif (strstarts(cmd, "unmute"))
new_mute = 0; else return -EINVAL;
}
if (tp_features.mixer_no_level_control) {
tpacpi_disclose_usertask("procfs volume", "%smute\n",
new_mute ? "" : "un");
rc = volume_set_mute(!!new_mute);
} else {
tpacpi_disclose_usertask("procfs volume", "%smute and set level to %d\n",
new_mute ? "" : "un", new_level);
rc = volume_set_status(new_mute | new_level);
}
volume_alsa_notify_change();
/************************************************************************* * Fan subdriver
*/
/* * FAN ACCESS MODES * * TPACPI_FAN_RD_ACPI_GFAN: * ACPI GFAN method: returns fan level * * see TPACPI_FAN_WR_ACPI_SFAN * EC 0x2f (HFSP) not available if GFAN exists * * TPACPI_FAN_WR_ACPI_SFAN: * ACPI SFAN method: sets fan level, 0 (stop) to 7 (max) * * EC 0x2f (HFSP) might be available *for reading*, but do not use * it for writing. * * TPACPI_FAN_RD_ACPI_FANG: * ACPI FANG method: returns fan control register * * Takes one parameter which is 0x8100 plus the offset to EC memory * address 0xf500 and returns the byte at this address. * * 0xf500: * When the value is less than 9 automatic mode is enabled * 0xf502: * Contains the current fan speed from 0-100% * 0xf506: * Bit 7 has to be set in order to enable manual control by * writing a value >= 9 to 0xf500 * * TPACPI_FAN_WR_ACPI_FANW: * ACPI FANW method: sets fan control registers * * Takes 0x8100 plus the offset to EC memory address 0xf500 and the * value to be written there as parameters. * * see TPACPI_FAN_RD_ACPI_FANG * * TPACPI_FAN_WR_TPEC: * ThinkPad EC register 0x2f (HFSP): fan control loop mode * Supported on almost all ThinkPads * * Fan speed changes of any sort (including those caused by the * disengaged mode) are usually done slowly by the firmware as the * maximum amount of fan duty cycle change per second seems to be * limited. * * Reading is not available if GFAN exists. * Writing is not available if SFAN exists. * * Bits * 7 automatic mode engaged; * (default operation mode of the ThinkPad) * fan level is ignored in this mode. * 6 full speed mode (takes precedence over bit 7); * not available on all thinkpads. May disable * the tachometer while the fan controller ramps up * the speed (which can take up to a few *minutes*). * Speeds up fan to 100% duty-cycle, which is far above * the standard RPM levels. It is not impossible that * it could cause hardware damage. * 5-3 unused in some models. Extra bits for fan level * in others, but still useless as all values above * 7 map to the same speed as level 7 in these models. * 2-0 fan level (0..7 usually) * 0x00 = stop * 0x07 = max (set when temperatures critical) * Some ThinkPads may have other levels, see * TPACPI_FAN_WR_ACPI_FANS (X31/X40/X41) * * FIRMWARE BUG: on some models, EC 0x2f might not be initialized at * boot. Apparently the EC does not initialize it, so unless ACPI DSDT * does so, its initial value is meaningless (0x07). * * For firmware bugs, refer to: * https://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues * * ---- * * ThinkPad EC register 0x84 (LSB), 0x85 (MSB): * Main fan tachometer reading (in RPM) * * This register is present on all ThinkPads with a new-style EC, and * it is known not to be present on the A21m/e, and T22, as there is * something else in offset 0x84 according to the ACPI DSDT. Other * ThinkPads from this same time period (and earlier) probably lack the * tachometer as well. * * Unfortunately a lot of ThinkPads with new-style ECs but whose firmware * was never fixed by IBM to report the EC firmware version string * probably support the tachometer (like the early X models), so * detecting it is quite hard. We need more data to know for sure. * * FIRMWARE BUG: always read 0x84 first, otherwise incorrect readings * might result. * * FIRMWARE BUG: may go stale while the EC is switching to full speed * mode. * * For firmware bugs, refer to: * https://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues * * ---- * * ThinkPad EC register 0x31 bit 0 (only on select models) * * When bit 0 of EC register 0x31 is zero, the tachometer registers * show the speed of the main fan. When bit 0 of EC register 0x31 * is one, the tachometer registers show the speed of the auxiliary * fan. * * Fan control seems to affect both fans, regardless of the state * of this bit. * * So far, only the firmware for the X60/X61 non-tablet versions * seem to support this (firmware TP-7M). * * TPACPI_FAN_WR_ACPI_FANS: * ThinkPad X31, X40, X41. Not available in the X60. * * FANS ACPI handle: takes three arguments: low speed, medium speed, * high speed. ACPI DSDT seems to map these three speeds to levels * as follows: STOP LOW LOW MED MED HIGH HIGH HIGH HIGH * (this map is stored on FAN0..FAN8 as "0,1,1,2,2,3,3,3,3") * * The speeds are stored on handles * (FANA:FAN9), (FANC:FANB), (FANE:FAND). * * There are three default speed sets, accessible as handles: * FS1L,FS1M,FS1H; FS2L,FS2M,FS2H; FS3L,FS3M,FS3H * * ACPI DSDT switches which set is in use depending on various * factors. * * TPACPI_FAN_WR_TPEC is also available and should be used to * command the fan. The X31/X40/X41 seems to have 8 fan levels, * but the ACPI tables just mention level 7. * * TPACPI_FAN_RD_TPEC_NS: * This mode is used for a few ThinkPads (L13 Yoga Gen2, X13 Yoga Gen2 etc.) * that are using non-standard EC locations for reporting fan speeds. * Currently these platforms only provide fan rpm reporting. *
*/
#define FAN_RPM_CAL_CONST 491520 /* FAN RPM calculation offset for some non-standard ECFW */
#define FAN_NS_CTRL_STATUS BIT(2) /* Bit which determines control is enabled or not */ #define FAN_NS_CTRL BIT(4) /* Bit which determines control is by host or EC */ #define FAN_CLOCK_TPM (22500*60) /* Ticks per minute for a 22.5 kHz clock */
enum { /* Fan control constants */
fan_status_offset = 0x2f, /* EC register 0x2f */
fan_rpm_offset = 0x84, /* EC register 0x84: LSB, 0x85 MSB (RPM)
* 0x84 must be read before 0x85 */
fan_select_offset = 0x31, /* EC register 0x31 (Firmware 7M)
bit 0 selects which fan is active */
fan_status_offset_ns = 0x93, /* Special status/control offset for non-standard EC Fan1 */
fan2_status_offset_ns = 0x96, /* Special status/control offset for non-standard EC Fan2 */
fan_rpm_status_ns = 0x95, /* Special offset for Fan1 RPM status for non-standard EC */
fan2_rpm_status_ns = 0x98, /* Special offset for Fan2 RPM status for non-standard EC */
TP_EC_FAN_FULLSPEED = 0x40, /* EC fan mode: full speed */
TP_EC_FAN_AUTO = 0x80, /* EC fan mode: auto fan control */
TPACPI_FAN_LAST_LEVEL = 0x100, /* Use cached last-seen fan level */
};
enum fan_status_access_mode {
TPACPI_FAN_NONE = 0, /* No fan status or control */
TPACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */
TPACPI_FAN_RD_ACPI_FANG, /* Use ACPI FANG */
TPACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */
TPACPI_FAN_RD_TPEC_NS, /* Use non-standard ACPI EC regs (eg: L13 Yoga gen2 etc.) */
};
enum fan_control_access_mode {
TPACPI_FAN_WR_NONE = 0, /* No fan control */
TPACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */
TPACPI_FAN_WR_ACPI_FANW, /* Use ACPI FANW */
TPACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */
TPACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */
};
/* * Unitialized HFSP quirk: ACPI DSDT and EC fail to initialize the * HFSP register at boot, so it contains 0x07 but the Thinkpad could * be in auto mode (0x80). * * This is corrected by any write to HFSP either by the driver, or * by the firmware. * * We assume 0x07 really means auto mode while this quirk is active, * as this is far more likely than the ThinkPad being in level 7, * which is only used by the firmware during thermal emergencies. * * Enable for TP-1Y (T43), TP-78 (R51e), TP-76 (R52), * TP-70 (T43, R52), which are known to be buggy.
*/
staticvoid fan_quirk1_setup(void)
{ if (fan_control_initial_status == 0x07) {
pr_notice("fan_init: initial fan status is unknown, assuming it is in auto mode\n");
tp_features.fan_ctrl_status_undef = 1;
}
}
staticvoid fan_quirk1_handle(u8 *fan_status)
{ if (unlikely(tp_features.fan_ctrl_status_undef)) { if (*fan_status != fan_control_initial_status) { /* something changed the HFSP regisnter since * driver init time, so it is not undefined
* anymore */
tp_features.fan_ctrl_status_undef = 0;
} else { /* Return most likely status. In fact, it
* might be the only possible status */
*fan_status = TP_EC_FAN_AUTO;
}
}
}
/* Select main fan on X60/X61, NOOP on others */ staticbool fan_select_fan1(void)
{ if (tp_features.second_fan) {
u8 val;
if (ec_read(fan_select_offset, &val) < 0) returnfalse;
val &= 0xFEU; if (ec_write(fan_select_offset, val) < 0) returnfalse;
} returntrue;
}
/* Select secondary fan on X60/X61 */ staticbool fan_select_fan2(void)
{
u8 val;
if (!tp_features.second_fan) returnfalse;
if (ec_read(fan_select_offset, &val) < 0) returnfalse;
val |= 0x01U; if (ec_write(fan_select_offset, val) < 0) returnfalse;
case TPACPI_FAN_RD_TPEC_NS:
rc = !acpi_ec_read(fan2_status_offset_ns, &status); if (rc) return -EIO; if (!(status & FAN_NS_CTRL_STATUS)) {
pr_info("secondary fan control not supported\n"); return -EIO;
}
rc = !acpi_ec_read(fan2_rpm_status_ns, &lo); if (rc) return -EIO; if (speed)
*speed = lo ? FAN_RPM_CAL_CONST / lo : 0; break; case TPACPI_FAN_RD_ACPI_FANG: { /* E531 */ int speed_tmp;
if (unlikely(!acpi_evalf(fang_handle, &speed_tmp, NULL, "dd", 0x8102))) return -EIO;
staticint fan_set_level(int level)
{ if (!fan_control_allowed) return -EPERM;
switch (fan_control_access_mode) { case TPACPI_FAN_WR_ACPI_SFAN: if ((level < 0) || (level > 7)) return -EINVAL;
if (tp_features.second_fan_ctl) { if (!fan_select_fan2() ||
!acpi_evalf(sfan_handle, NULL, NULL, "vd", level)) {
pr_warn("Couldn't set 2nd fan level, disabling support\n");
tp_features.second_fan_ctl = 0;
}
fan_select_fan1();
} if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level)) return -EIO; break;
case TPACPI_FAN_WR_ACPI_FANS: case TPACPI_FAN_WR_TPEC: if (!(level & TP_EC_FAN_AUTO) &&
!(level & TP_EC_FAN_FULLSPEED) &&
((level < 0) || (level > 7))) return -EINVAL;
/* safety net should the EC not support AUTO
* or FULLSPEED mode bits and just ignore them */ if (level & TP_EC_FAN_FULLSPEED)
level |= 7; /* safety min speed 7 */ elseif (level & TP_EC_FAN_AUTO)
level |= 4; /* safety min speed 4 */
if (tp_features.second_fan_ctl) { if (!fan_select_fan2() ||
!acpi_ec_write(fan_status_offset, level)) {
pr_warn("Couldn't set 2nd fan level, disabling support\n");
tp_features.second_fan_ctl = 0;
}
fan_select_fan1();
#define TPACPI_FAN_Q1 0x0001 /* Uninitialized HFSP */ #define TPACPI_FAN_2FAN 0x0002 /* EC 0x31 bit 0 selects fan2 */ #define TPACPI_FAN_2CTL 0x0004 /* selects fan2 control */ #define TPACPI_FAN_NOFAN 0x0008 /* no fan available */ #define TPACPI_FAN_NS 0x0010 /* For EC with non-Standard register addresses */ #define TPACPI_FAN_DECRPM 0x0020 /* For ECFW's with RPM in register as decimal */ #define TPACPI_FAN_TPR 0x0040 /* Fan speed is in Ticks Per Revolution */ #define TPACPI_FAN_NOACPI 0x0080 /* Don't use ACPI methods even if detected */
if (quirks & TPACPI_FAN_NOFAN) {
pr_info("No integrated ThinkPad fan available\n"); return -ENODEV;
}
if (quirks & TPACPI_FAN_NS) {
pr_info("ECFW with non-standard fan reg control found\n");
fan_with_ns_addr = 1; /* Fan ctrl support from host is undefined for now */
tp_features.fan_ctrl_status_undef = 1;
}
/* Check for the EC/BIOS with RPM reported in decimal*/ if (quirks & TPACPI_FAN_DECRPM) {
pr_info("ECFW with fan RPM as decimal in EC register\n");
ecfw_with_fan_dec_rpm = 1;
tp_features.fan_ctrl_status_undef = 1;
}
if (quirks & TPACPI_FAN_Q1)
fan_quirk1_setup(); if (quirks & TPACPI_FAN_TPR)
fan_speed_in_tpr = true; /* Try and probe the 2nd fan */
tp_features.second_fan = 1; /* needed for get_speed to work */
res = fan2_get_speed(&speed); if (res >= 0 && speed != FAN_NOT_PRESENT) { /* It responded - so let's assume it's there */
tp_features.second_fan = 1; /* fan control not currently available for ns ECFW */
tp_features.second_fan_ctl = !fan_with_ns_addr;
pr_info("secondary fan control detected & enabled\n");
} else { /* Fan not auto-detected */
tp_features.second_fan = 0; if (quirks & TPACPI_FAN_2FAN) {
tp_features.second_fan = 1;
pr_info("secondary fan support enabled\n");
} if (quirks & TPACPI_FAN_2CTL) {
tp_features.second_fan = 1;
tp_features.second_fan_ctl = 1;
pr_info("secondary fan control enabled\n");
}
}
} else {
pr_err("ThinkPad ACPI EC access misbehaving, fan status and control unavailable\n"); return -ENODEV;
}
}
if (sfan_handle) { /* 570, 770x-JL */
fan_control_access_mode = TPACPI_FAN_WR_ACPI_SFAN;
fan_control_commands |=
TPACPI_FAN_CMD_LEVEL | TPACPI_FAN_CMD_ENABLE;
} elseif (fanw_handle) { /* E531 */
fan_control_access_mode = TPACPI_FAN_WR_ACPI_FANW;
fan_control_commands |=
TPACPI_FAN_CMD_LEVEL | TPACPI_FAN_CMD_SPEED | TPACPI_FAN_CMD_ENABLE;
} else { if (!gfan_handle) { /* gfan without sfan means no fan control */ /* all other models implement TP EC 0x2f control */
/* fan control master switch */ if (!fan_control_allowed) {
fan_control_access_mode = TPACPI_FAN_WR_NONE;
fan_control_commands = 0;
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN, "fan control features disabled by parameter\n");
}
/* update fan_control_desired_level */ if (fan_status_access_mode != TPACPI_FAN_NONE)
fan_get_status_safe(NULL);
if (fan_status_access_mode == TPACPI_FAN_NONE &&
fan_control_access_mode == TPACPI_FAN_WR_NONE) return -ENODEV;
return 0;
}
staticvoid fan_exit(void)
{
vdbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_FAN, "cancelling any pending fan watchdog tasks\n");
/* Store fan status in cache */
fan_control_resume_level = 0;
rc = fan_get_status_safe(&fan_control_resume_level); if (rc)
pr_notice("failed to read fan level for later restore during resume: %d\n",
rc);
/* if it is undefined, don't attempt to restore it.
* KEEP THIS LAST */ if (tp_features.fan_ctrl_status_undef)
fan_control_resume_level = 0;
}
/* DSDT *always* updates status on resume */
tp_features.fan_ctrl_status_undef = 0;
if (!fan_control_allowed ||
!fan_control_resume_level ||
fan_get_status_safe(¤t_level)) return;
switch (fan_control_access_mode) { case TPACPI_FAN_WR_ACPI_SFAN: /* never decrease fan level */
do_set = (fan_control_resume_level > current_level); break; case TPACPI_FAN_WR_ACPI_FANS: case TPACPI_FAN_WR_TPEC: /* never decrease fan level, scale is: * TP_EC_FAN_FULLSPEED > 7 >= TP_EC_FAN_AUTO * * We expect the firmware to set either 7 or AUTO, but we * handle FULLSPEED out of paranoia. * * So, we can safely only restore FULLSPEED or 7, anything * else could slow the fan. Restoring AUTO is useless, at * best that's exactly what the DSDT already set (it is the * slower it uses). * * Always keep in mind that the DSDT *will* have set the * fans to what the vendor supposes is the best level. We * muck with it only to speed the fan up.
*/ if (fan_control_resume_level != 7 &&
!(fan_control_resume_level & TP_EC_FAN_FULLSPEED)) return; else
do_set = !(current_level & TP_EC_FAN_FULLSPEED) &&
(current_level != fan_control_resume_level); break; default: return;
} if (do_set) {
pr_notice("restoring fan level to 0x%02x\n",
fan_control_resume_level);
rc = fan_set_level_safe(fan_control_resume_level); if (rc < 0)
pr_notice("failed to restore fan level: %d\n", rc);
}
}
case TPACPI_FAN_RD_TPEC_NS: case TPACPI_FAN_RD_TPEC: case TPACPI_FAN_RD_ACPI_FANG: /* all except 570, 600e/x, 770e, 770x */
rc = fan_get_status_safe(&status); if (rc) return rc;
rc = fan_get_speed(&speed); if (rc < 0) return rc;
/* Check for fan speeds displayed in hexadecimal */ if (!ecfw_with_fan_dec_rpm)
seq_printf(m, "speed:\t\t%d\n", speed); else
seq_printf(m, "speed:\t\t%x\n", speed);
if (fan_status_access_mode == TPACPI_FAN_RD_TPEC_NS) { /* * No full speed bit in NS EC * EC Auto mode is set by default. * No other levels settings available
*/
seq_printf(m, "level:\t\t%s\n", status & FAN_NS_CTRL ? "unknown" : "auto");
} elseif (fan_status_access_mode == TPACPI_FAN_RD_TPEC) { if (status & TP_EC_FAN_FULLSPEED) /* Disengaged mode takes precedence */
seq_printf(m, "level:\t\tdisengaged\n"); elseif (status & TP_EC_FAN_AUTO)
seq_printf(m, "level:\t\tauto\n"); else
seq_printf(m, "level:\t\t%d\n", status);
} break;
case TPACPI_FAN_NONE: default:
seq_printf(m, "status:\t\tnot supported\n");
}
if (fan_control_commands & TPACPI_FAN_CMD_LEVEL) {
seq_printf(m, "commands:\tlevel <level>");
switch (fan_control_access_mode) { case TPACPI_FAN_WR_ACPI_SFAN:
seq_printf(m, " (<level> is 0-7)\n"); break;
/* * This evaluates a ACPI method call specific to the battery * ACPI extension. The specifics are that an error is marked * in the 32rd bit of the response, so we just check that here.
*/ static acpi_status tpacpi_battery_acpi_eval(char *method, int *ret, int param)
{ int response;
if (!acpi_evalf(hkey_handle, &response, method, "dd", param)) {
acpi_handle_err(hkey_handle, "%s: evaluate failed", method); return AE_ERROR;
} if (response & METHOD_ERR) {
acpi_handle_err(hkey_handle, "%s evaluated but flagged as error", method); return AE_ERROR;
}
*ret = response; return AE_OK;
}
staticint tpacpi_battery_get(int what, int battery, int *ret)
{ switch (what) { case THRESHOLD_START: if ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_START, ret, battery)) return -ENODEV;
/* The value is in the low 8 bits of the response */
*ret = *ret & 0xFF; return 0; case THRESHOLD_STOP: if ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_STOP, ret, battery)) return -ENODEV; /* Value is in lower 8 bits */
*ret = *ret & 0xFF; /* * On the stop value, if we return 0 that * does not make any sense. 0 means Default, which * means that charging stops at 100%, so we return * that.
*/ if (*ret == 0)
*ret = 100; return 0; case FORCE_DISCHARGE: if ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_DISCHARGE, ret, battery)) return -ENODEV; /* The force discharge status is in bit 0 */
*ret = *ret & 0x01; return 0; case INHIBIT_CHARGE: if ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_INHIBIT, ret, battery)) return -ENODEV; /* The inhibit charge status is in bit 0 */
*ret = *ret & 0x01; return 0; default:
pr_crit("wrong parameter: %d", what); return -EINVAL;
}
}
staticint tpacpi_battery_set(int what, int battery, int value)
{ int param, ret; /* The first 8 bits are the value of the threshold */
param = value; /* The battery ID is in bits 8-9, 2 bits */
param |= battery << 8;
switch (what) { case THRESHOLD_START: if ACPI_FAILURE(tpacpi_battery_acpi_eval(SET_START, &ret, param)) {
pr_err("failed to set charge threshold on battery %d",
battery); return -ENODEV;
} return 0; case THRESHOLD_STOP: if ACPI_FAILURE(tpacpi_battery_acpi_eval(SET_STOP, &ret, param)) {
pr_err("failed to set stop threshold: %d", battery); return -ENODEV;
} return 0; case FORCE_DISCHARGE: /* Force discharge is in bit 0, * break on AC attach is in bit 1 (won't work on some ThinkPads), * battery ID is in bits 8-9, 2 bits.
*/ if (ACPI_FAILURE(tpacpi_battery_acpi_eval(SET_DISCHARGE, &ret, param))) {
pr_err("failed to set force discharge on %d", battery); return -ENODEV;
} return 0; case INHIBIT_CHARGE: /* When setting inhibit charge, we set a default value of * always breaking on AC detach and the effective time is set to * be permanent. * The battery ID is in bits 4-5, 2 bits, * the effective time is in bits 8-23, 2 bytes. * A time of FFFF indicates forever.
*/
param = value;
param |= battery << 4;
param |= 0xFFFF << 8; if (ACPI_FAILURE(tpacpi_battery_acpi_eval(SET_INHIBIT, &ret, param))) {
pr_err("failed to set inhibit charge on %d", battery); return -ENODEV;
} return 0; default:
pr_crit("wrong parameter: %d", what); return -EINVAL;
}
}
staticint tpacpi_battery_set_validate(int what, int battery, int value)
{ int ret, v;
ret = tpacpi_battery_set(what, battery, value); if (ret < 0) return ret;
ret = tpacpi_battery_get(what, battery, &v); if (ret < 0) return ret;
if (v == value) return 0;
msleep(500);
ret = tpacpi_battery_get(what, battery, &v); if (ret < 0) return ret;
if (v == value) return 0;
return -EIO;
}
staticint tpacpi_battery_probe(int battery)
{ int ret = 0;
/* * 1) Get the current start threshold * 2) Check for support * 3) Get the current stop threshold * 4) Check for support * 5) Get the current force discharge status * 6) Check for support * 7) Get the current inhibit charge status * 8) Check for support
*/ if (acpi_has_method(hkey_handle, GET_START)) { if ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_START, &ret, battery)) {
pr_err("Error probing battery %d\n", battery); return -ENODEV;
} /* Individual addressing is in bit 9 */ if (ret & BIT(9))
battery_info.individual_addressing = true; /* Support is marked in bit 8 */ if (ret & BIT(8))
battery_info.batteries[battery].start_support = 1; else return -ENODEV; if (tpacpi_battery_get(THRESHOLD_START, battery,
&battery_info.batteries[battery].charge_start)) {
pr_err("Error probing battery %d\n", battery); return -ENODEV;
}
} if (acpi_has_method(hkey_handle, GET_STOP)) { if ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_STOP, &ret, battery)) {
pr_err("Error probing battery stop; %d\n", battery); return -ENODEV;
} /* Support is marked in bit 8 */ if (ret & BIT(8))
battery_info.batteries[battery].stop_support = 1; else return -ENODEV; if (tpacpi_battery_get(THRESHOLD_STOP, battery,
&battery_info.batteries[battery].charge_stop)) {
pr_err("Error probing battery stop: %d\n", battery); return -ENODEV;
}
} if (acpi_has_method(hkey_handle, GET_DISCHARGE)) { if (ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_DISCHARGE, &ret, battery))) {
pr_err("Error probing battery discharge; %d\n", battery); return -ENODEV;
} /* Support is marked in bit 8 */ if (ret & BIT(8))
battery_info.batteries[battery].charge_behaviours |=
BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE);
} if (acpi_has_method(hkey_handle, GET_INHIBIT)) { if (ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_INHIBIT, &ret, battery))) {
pr_err("Error probing battery inhibit charge; %d\n", battery); return -ENODEV;
} /* Support is marked in bit 5 */ if (ret & BIT(5))
battery_info.batteries[battery].charge_behaviours |=
BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE);
}
if (strcmp(battery_name, "BAT0") == 0 ||
tp_features.battery_force_primary) return BAT_PRIMARY; if (strcmp(battery_name, "BAT1") == 0) return BAT_SECONDARY; /* * If for some reason the battery is not BAT0 nor is it * BAT1, we will assume it's the default, first battery, * AKA primary.
*/
pr_warn("unknown battery %s, assuming primary", battery_name); return BAT_PRIMARY;
}
/* sysfs interface */
static ssize_t tpacpi_battery_store(int what, struct device *dev, constchar *buf, size_t count)
{ struct power_supply *supply = to_power_supply(dev); unsignedlong value; int battery, rval; /* * Some systems have support for more than * one battery. If that is the case, * tpacpi_battery_probe marked that addressing * them individually is supported, so we do that * based on the device struct. * * On systems that are not supported, we assume * the primary as most of the ACPI calls fail * with "Any Battery" as the parameter.
*/ if (battery_info.individual_addressing) /* BAT_PRIMARY or BAT_SECONDARY */
battery = tpacpi_battery_get_id(supply->desc->name); else
battery = BAT_PRIMARY;
rval = kstrtoul(buf, 10, &value); if (rval) return rval;
switch (what) { case THRESHOLD_START: if (!battery_info.batteries[battery].start_support) return -ENODEV; /* valid values are [0, 99] */ if (value > 99) return -EINVAL; if (value > battery_info.batteries[battery].charge_stop) return -EINVAL; if (tpacpi_battery_set(THRESHOLD_START, battery, value)) return -ENODEV;
battery_info.batteries[battery].charge_start = value; return count;
case THRESHOLD_STOP: if (!battery_info.batteries[battery].stop_support) return -ENODEV; /* valid values are [1, 100] */ if (value < 1 || value > 100) return -EINVAL; if (value < battery_info.batteries[battery].charge_start) return -EINVAL;
battery_info.batteries[battery].charge_stop = value; /* * When 100 is passed to stop, we need to flip * it to 0 as that the EC understands that as * "Default", which will charge to 100%
*/ if (value == 100)
value = 0; if (tpacpi_battery_set(THRESHOLD_STOP, battery, value)) return -EINVAL; return count; default:
pr_crit("Wrong parameter: %d", what); return -EINVAL;
} return count;
}
static ssize_t tpacpi_battery_show(int what, struct device *dev, char *buf)
{ struct power_supply *supply = to_power_supply(dev); int ret, battery; /* * Some systems have support for more than * one battery. If that is the case, * tpacpi_battery_probe marked that addressing * them individually is supported, so we; * based on the device struct. * * On systems that are not supported, we assume * the primary as most of the ACPI calls fail * with "Any Battery" as the parameter.
*/ if (battery_info.individual_addressing) /* BAT_PRIMARY or BAT_SECONDARY */
battery = tpacpi_battery_get_id(supply->desc->name); else
battery = BAT_PRIMARY; if (tpacpi_battery_get(what, battery, &ret)) return -ENODEV; return sysfs_emit(buf, "%d\n", ret);
}
static umode_t proxsensor_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n)
{ if (attr == &dev_attr_dytc_lapmode.attr) { /* * Platforms before DYTC version 5 claim to have a lap sensor, * but it doesn't work, so we ignore them.
*/ if (!has_lapsensor || dytc_version < 5) return 0;
} elseif (attr == &dev_attr_palmsensor.attr) { if (!has_palmsensor) return 0;
}
staticint tpacpi_proxsensor_init(struct ibm_init_struct *iibm)
{ int palm_err, lap_err;
palm_err = palmsensor_get(&has_palmsensor, &palm_state);
lap_err = lapsensor_get(&has_lapsensor, &lap_state); /* If support isn't available for both devices return -ENODEV */ if ((palm_err == -ENODEV) && (lap_err == -ENODEV)) return -ENODEV; /* Otherwise, if there was an error return it */ if (palm_err && (palm_err != -ENODEV)) return palm_err; if (lap_err && (lap_err != -ENODEV)) return lap_err;
#define DYTC_CMD_SET 1 /* To enable/disable IC function mode */ #define DYTC_CMD_MMC_GET 8 /* To get current MMC function and mode */ #define DYTC_CMD_RESET 0x1ff /* To reset back to default */
staticint convert_dytc_to_profile(int funcmode, int dytcmode, enum platform_profile_option *profile)
{ switch (funcmode) { case DYTC_FUNCTION_MMC: switch (dytcmode) { case DYTC_MODE_MMC_LOWPOWER:
*profile = PLATFORM_PROFILE_LOW_POWER; break; case DYTC_MODE_MMC_DEFAULT: case DYTC_MODE_MMC_BALANCE:
*profile = PLATFORM_PROFILE_BALANCED; break; case DYTC_MODE_MMC_PERFORM:
*profile = PLATFORM_PROFILE_PERFORMANCE; break; default: /* Unknown mode */ return -EINVAL;
} return 0; case DYTC_FUNCTION_PSC: if (dytcmode == platform_psc_profile_lowpower)
*profile = PLATFORM_PROFILE_LOW_POWER; elseif (dytcmode == platform_psc_profile_balanced)
*profile = PLATFORM_PROFILE_BALANCED; elseif (dytcmode == platform_psc_profile_performance)
*profile = PLATFORM_PROFILE_PERFORMANCE; else return -EINVAL;
return 0; case DYTC_FUNCTION_AMT: /* For now return balanced. It's the closest we have to 'auto' */
*profile = PLATFORM_PROFILE_BALANCED; return 0; default: /* Unknown function */
pr_debug("unknown function 0x%x\n", funcmode); return -EOPNOTSUPP;
} return 0;
}
/* * Helper function - check if we are in CQL mode and if we are * - disable CQL, * - run the command * - enable CQL * If not in CQL mode, just run the command
*/ staticint dytc_cql_command(int command, int *output)
{ int err, cmd_err, dummy; int cur_funcmode;
/* Determine if we are in CQL mode. This alters the commands we do */
err = dytc_command(DYTC_CMD_GET, output); if (err) return err;
cur_funcmode = (*output >> DYTC_GET_FUNCTION_BIT) & 0xF; /* Check if we're OK to return immediately */ if ((command == DYTC_CMD_GET) && (cur_funcmode != DYTC_FUNCTION_CQL)) return 0;
if (cur_funcmode == DYTC_FUNCTION_CQL) {
atomic_inc(&dytc_ignore_event);
err = dytc_command(DYTC_DISABLE_CQL, &dummy); if (err) return err;
}
cmd_err = dytc_command(command, output); /* Check return condition after we've restored CQL state */
if (cur_funcmode == DYTC_FUNCTION_CQL) {
err = dytc_command(DYTC_ENABLE_CQL, &dummy); if (err) return err;
} return cmd_err;
}
/* * dytc_profile_set: Function to register with platform_profile * handler. Sets current platform profile.
*/ staticint dytc_profile_set(struct device *dev, enum platform_profile_option profile)
{ int perfmode; int output; int err;
err = mutex_lock_interruptible(&dytc_mutex); if (err) return err;
err = convert_profile_to_dytc(profile, &perfmode); if (err) goto unlock;
if (dytc_capabilities & BIT(DYTC_FC_MMC)) { if (profile == PLATFORM_PROFILE_BALANCED) { /* * To get back to balanced mode we need to issue a reset command. * Note we still need to disable CQL mode before hand and re-enable * it afterwards, otherwise dytc_lapmode gets reset to 0 and stays * stuck at 0 for aprox. 30 minutes.
*/
err = dytc_cql_command(DYTC_CMD_RESET, &output); if (err) goto unlock;
} else { /* Determine if we are in CQL mode. This alters the commands we do */
err = dytc_cql_command(DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1),
&output); if (err) goto unlock;
}
} elseif (dytc_capabilities & BIT(DYTC_FC_PSC)) {
err = dytc_command(DYTC_SET_COMMAND(DYTC_FUNCTION_PSC, perfmode, 1), &output); if (err) goto unlock;
/* system supports AMT, activate it when on balanced */ if (dytc_capabilities & BIT(DYTC_FC_AMT))
dytc_control_amt(profile == PLATFORM_PROFILE_BALANCED);
} /* Success - update current profile */
dytc_current_profile = profile;
unlock:
mutex_unlock(&dytc_mutex); return err;
}
staticint set_keyboard_lang_command(int command)
{
acpi_handle sskl_handle; int output;
if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "SSKL", &sskl_handle))) { /* Platform doesn't support SSKL */ return -ENODEV;
}
if (!acpi_evalf(sskl_handle, &output, NULL, "dd", command)) return -EIO;
return 0;
}
staticint get_keyboard_lang(int *output)
{
acpi_handle gskl_handle; int kbd_lang;
if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "GSKL", &gskl_handle))) { /* Platform doesn't support GSKL */ return -ENODEV;
}
if (!acpi_evalf(gskl_handle, &kbd_lang, NULL, "dd", 0x02000000)) return -EIO;
/* * METHOD_ERR gets returned on devices where there are no special (e.g. '=', * '(' and ')') keys which use layout dependent key-press emulation.
*/ if (kbd_lang & METHOD_ERR) return -ENODEV;
*output = kbd_lang;
return 0;
}
/* sysfs keyboard language entry */ static ssize_t keyboard_lang_show(struct device *dev, struct device_attribute *attr, char *buf)
{ int output, err, i, len = 0;
err = get_keyboard_lang(&output); if (err) return err;
for (i = 0; i < ARRAY_SIZE(keyboard_lang_data); i++) { if (i)
len += sysfs_emit_at(buf, len, "%s", " ");
if (output == keyboard_lang_data[i].lang_code) {
len += sysfs_emit_at(buf, len, "[%s]", keyboard_lang_data[i].lang_str);
} else {
len += sysfs_emit_at(buf, len, "%s", keyboard_lang_data[i].lang_str);
}
}
len += sysfs_emit_at(buf, len, "\n");
/************************************************************************* * DPRC(Dynamic Power Reduction Control) subdriver, for the Lenovo WWAN * and WLAN feature.
*/ #define DPRC_GET_WWAN_ANTENNA_TYPE 0x40000 #define DPRC_WWAN_ANTENNA_TYPE_A_BIT BIT(4) #define DPRC_WWAN_ANTENNA_TYPE_B_BIT BIT(8) staticbool has_antennatype; staticint wwan_antennatype;
staticint dprc_command(int command, int *output)
{
acpi_handle dprc_handle;
if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "DPRC", &dprc_handle))) { /* Platform doesn't support DPRC */ return -ENODEV;
}
if (!acpi_evalf(dprc_handle, output, NULL, "dd", command)) return -EIO;
/* * METHOD_ERR gets returned on devices where few commands are not supported * for example command to get WWAN Antenna type command is not supported on * some devices.
*/ if (*output & METHOD_ERR) return -ENODEV;
return 0;
}
staticint get_wwan_antenna(int *wwan_antennatype)
{ int output, err;
/* Get current Antenna type */
err = dprc_command(DPRC_GET_WWAN_ANTENNA_TYPE, &output); if (err) return err;
/* * Auxmac * * This auxiliary mac address is enabled in the bios through the * MAC Address Pass-through feature. In most cases, there are three * possibilities: Internal Mac, Second Mac, and disabled. *
*/
/* * HKEY event callout for other subdrivers go here * (yes, it is ugly, but it is quick, safe, and gets the job done
*/ staticbool tpacpi_driver_event(constunsignedint hkey_event)
{ int camera_shutter_state;
switch (hkey_event) { case TP_HKEY_EV_BRGHT_UP: case TP_HKEY_EV_BRGHT_DOWN: if (ibm_backlight_device)
tpacpi_brightness_notify_change(); /* * Key press events are suppressed by default hotkey_user_mask * and should still be reported if explicitly requested.
*/ returnfalse; case TP_HKEY_EV_VOL_UP: case TP_HKEY_EV_VOL_DOWN: case TP_HKEY_EV_VOL_MUTE: if (alsa_card)
volume_alsa_notify_change();
/* Key events are suppressed by default hotkey_user_mask */ returnfalse; case TP_HKEY_EV_KBD_LIGHT: if (tp_features.kbdlight) { enum led_brightness brightness;
mutex_lock(&kbdlight_mutex);
/* * Check the brightness actually changed, setting the brightness * through kbdlight_set_level() also triggers this event.
*/
brightness = kbdlight_sysfs_get(NULL); if (kbdlight_brightness != brightness) {
kbdlight_brightness = brightness;
led_classdev_notify_brightness_hw_changed(
&tpacpi_led_kbdlight.led_classdev, brightness);
}
mutex_unlock(&kbdlight_mutex);
} /* Key events are suppressed by default hotkey_user_mask */ returnfalse; case TP_HKEY_EV_DFR_CHANGE_ROW:
adaptive_keyboard_change_row(); returntrue; case TP_HKEY_EV_DFR_S_QUICKVIEW_ROW:
adaptive_keyboard_s_quickview_row(); returntrue; case TP_HKEY_EV_THM_CSM_COMPLETED:
lapsensor_refresh(); /* If we are already accessing DYTC then skip dytc update */ if (!atomic_add_unless(&dytc_ignore_event, -1, 0))
dytc_profile_refresh();
returntrue; case TP_HKEY_EV_PRIVACYGUARD_TOGGLE: if (lcdshadow_dev) { enum drm_privacy_screen_status old_hw_state; bool changed;
if (changed)
drm_privacy_screen_call_notifier_chain(lcdshadow_dev);
} returntrue; case TP_HKEY_EV_AMT_TOGGLE: /* If we're enabling AMT we need to force balanced mode */ if (!dytc_amt_active) /* This will also set AMT mode enabled */
dytc_profile_set(NULL, PLATFORM_PROFILE_BALANCED); else
dytc_control_amt(!dytc_amt_active);
returntrue; case TP_HKEY_EV_CAMERASHUTTER_TOGGLE:
camera_shutter_state = get_camera_shutter(); if (camera_shutter_state < 0) {
pr_err("Error retrieving camera shutter state after shutter event\n"); returntrue;
}
mutex_lock(&tpacpi_inputdev_send_mutex);
mutex_unlock(&tpacpi_inputdev_send_mutex); returntrue; case TP_HKEY_EV_DOUBLETAP_TOGGLE:
tp_features.trackpoint_doubletap = !tp_features.trackpoint_doubletap; returntrue; case TP_HKEY_EV_PROFILE_TOGGLE: case TP_HKEY_EV_PROFILE_TOGGLE2:
platform_profile_cycle(); returntrue;
}
s = dmi_get_system_info(DMI_BIOS_VERSION);
tp->bios_version_str = kstrdup(s, GFP_KERNEL); if (s && !tp->bios_version_str) return -ENOMEM;
/* Really ancient ThinkPad 240X will fail this, which is fine */
t = tpacpi_parse_fw_id(tp->bios_version_str,
&tp->bios_model, &tp->bios_release); if (t != 'E' && t != 'C') return 0;
/* * ThinkPad T23 or newer, A31 or newer, R50e or newer, * X32 or newer, all Z series; Some models must have an * up-to-date BIOS or they will not be detected. * * See https://thinkwiki.org/wiki/List_of_DMI_IDs
*/ while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) { if (sscanf(dev->name, "IBM ThinkPad Embedded Controller -[%17c",
ec_fw_string) == 1) {
ec_fw_string[sizeof(ec_fw_string) - 1] = 0;
ec_fw_string[strcspn(ec_fw_string, " ]")] = 0; break;
}
}
/* Newer ThinkPads have different EC program info table */ if (!ec_fw_string[0])
dmi_walk(find_new_ec_fwstr, &ec_fw_string);
if (ec_fw_string[0]) {
tp->ec_version_str = kstrdup(ec_fw_string, GFP_KERNEL); if (!tp->ec_version_str) return -ENOMEM;
t = tpacpi_parse_fw_id(ec_fw_string,
&tp->ec_model, &tp->ec_release); if (t != 'H') {
pr_notice("ThinkPad firmware release %s doesn't match the known patterns\n",
ec_fw_string);
pr_notice("please report this to %s\n", TPACPI_MAIL);
}
}
s = dmi_get_system_info(DMI_PRODUCT_VERSION); if (s && !(strncasecmp(s, "ThinkPad", 8) && strncasecmp(s, "Lenovo", 6))) {
tp->model_str = kstrdup(s, GFP_KERNEL); if (!tp->model_str) return -ENOMEM;
} else {
s = dmi_get_system_info(DMI_BIOS_VENDOR); if (s && !(strncasecmp(s, "Lenovo", 6))) {
tp->model_str = kstrdup(s, GFP_KERNEL); if (!tp->model_str) return -ENOMEM;
}
}
s = dmi_get_system_info(DMI_PRODUCT_NAME);
tp->nummodel_str = kstrdup(s, GFP_KERNEL); if (s && !tp->nummodel_str) return -ENOMEM;
return 0;
}
staticint __init probe_for_thinkpad(void)
{ int is_thinkpad;
if (acpi_disabled) return -ENODEV;
/* It would be dangerous to run the driver in this case */ if (!tpacpi_is_ibm() && !tpacpi_is_lenovo()) return -ENODEV;
/* * Non-ancient models have better DMI tagging, but very old models * don't. tpacpi_is_fw_known() is a cheat to help in that case.
*/
is_thinkpad = (thinkpad_id.model_str != NULL) ||
(thinkpad_id.ec_model != 0) ||
tpacpi_is_fw_known();
/* The EC handler is required */
tpacpi_acpi_handle_locate("ec", TPACPI_ACPI_EC_HID, &ec_handle); if (!ec_handle) { if (is_thinkpad)
pr_err("Not yet supported ThinkPad detected!\n"); return -ENODEV;
}
module_param_named(volume_capabilities, volume_capabilities, uint, 0444);
MODULE_PARM_DESC(volume_capabilities, "Selects the mixer capabilities: 0=auto, 1=volume and mute, 2=mute only");
module_param_named(volume_control, volume_control_allowed, bool, 0444);
MODULE_PARM_DESC(volume_control, "Enables software override for the console audio control when true");
module_param_named(software_mute, software_mute_requested, bool, 0444);
MODULE_PARM_DESC(software_mute, "Request full software mute control");
/* ALSA module API parameters */
module_param_named(index, alsa_index, int, 0444);
MODULE_PARM_DESC(index, "ALSA index for the ACPI EC Mixer");
module_param_named(id, alsa_id, charp, 0444);
MODULE_PARM_DESC(id, "ALSA id for the ACPI EC Mixer");
module_param_named(enable, alsa_enable, bool, 0444);
MODULE_PARM_DESC(enable, "Enable the ALSA interface for the ACPI EC Mixer"); #endif/* CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */
/* The module parameter can't be read back, that's why 0 is used here */ #define TPACPI_PARAM(feature) \
module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command at module load, see documentation")
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
module_param(dbg_wlswemul, uint, 0444);
MODULE_PARM_DESC(dbg_wlswemul, "Enables WLSW emulation");
module_param_named(wlsw_state, tpacpi_wlsw_emulstate, bool, 0);
MODULE_PARM_DESC(wlsw_state, "Initial state of the emulated WLSW switch");
module_param(dbg_bluetoothemul, uint, 0444);
MODULE_PARM_DESC(dbg_bluetoothemul, "Enables bluetooth switch emulation");
module_param_named(bluetooth_state, tpacpi_bluetooth_emulstate, bool, 0);
MODULE_PARM_DESC(bluetooth_state, "Initial state of the emulated bluetooth switch");
module_param(dbg_wwanemul, uint, 0444);
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, 0444);
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
ret = get_thinkpad_model_data(&thinkpad_id); if (ret) {
pr_err("unable to get DMI data: %d\n", ret);
thinkpad_acpi_module_exit(); return ret;
}
ret = probe_for_thinkpad(); if (ret) {
thinkpad_acpi_module_exit(); return ret;
}
/* * Quirk: in some models (e.g. X380 Yoga), an object named ECRD * exists, but it is a register, not a method.
*/ if (ecrd_handle) {
acpi_get_type(ecrd_handle, &obj_type); if (obj_type != ACPI_TYPE_METHOD)
ecrd_handle = NULL;
} if (ecwr_handle) {
acpi_get_type(ecwr_handle, &obj_type); if (obj_type != ACPI_TYPE_METHOD)
ecwr_handle = NULL;
}
tpacpi_wq = create_singlethread_workqueue(TPACPI_WORKQUEUE_NAME); if (!tpacpi_wq) {
thinkpad_acpi_module_exit(); return -ENOMEM;
}
proc_dir = proc_mkdir(TPACPI_PROC_DIR, acpi_root_dir); if (!proc_dir) {
pr_err("unable to create proc dir " TPACPI_PROC_DIR "\n");
thinkpad_acpi_module_exit(); return -ENODEV;
}
dmi_id = dmi_first_match(fwbug_list); if (dmi_id)
tp_features.quirks = dmi_id->driver_data;
/* Device initialization */
tpacpi_pdev = platform_device_register_simple(TPACPI_DRVR_NAME, PLATFORM_DEVID_NONE,
NULL, 0); if (IS_ERR(tpacpi_pdev)) {
ret = PTR_ERR(tpacpi_pdev);
tpacpi_pdev = NULL;
pr_err("unable to register platform device\n");
thinkpad_acpi_module_exit(); return ret;
}
ret = platform_driver_probe(&tpacpi_pdriver, tpacpi_pdriver_probe); if (ret) {
pr_err("unable to register main platform driver\n");
thinkpad_acpi_module_exit(); return ret;
}
tp_features.platform_drv_registered = 1;
tpacpi_sensors_pdev = platform_create_bundle(&tpacpi_hwmon_pdriver,
tpacpi_hwmon_pdriver_probe,
NULL, 0, NULL, 0); if (IS_ERR(tpacpi_sensors_pdev)) {
ret = PTR_ERR(tpacpi_sensors_pdev);
tpacpi_sensors_pdev = NULL;
pr_err("unable to register hwmon platform device/driver bundle\n");
thinkpad_acpi_module_exit(); return ret;
}
tpacpi_lifecycle = TPACPI_LIFE_RUNNING;
return 0;
}
MODULE_ALIAS(TPACPI_DRVR_SHORTNAME);
/* * This will autoload the driver in almost every ThinkPad * in widespread use. * * Only _VERY_ old models, like the 240, 240x and 570 lack * the HKEY event interface.
*/
MODULE_DEVICE_TABLE(acpi, ibm_htk_device_ids);
/* Ancient thinkpad BIOSes have to be identified by * BIOS type or model number, and there are far less
* BIOS types than model numbers... */
IBM_BIOS_MODULE_ALIAS("I[MU]"); /* 570, 570e */
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.436Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-04-28)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.