From 19d337dff95cbf76edd3ad95c0cee2732c3e1ec5 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 2 Jun 2009 13:01:37 +0200 Subject: rfkill: rewrite This patch completely rewrites the rfkill core to address the following deficiencies: * all rfkill drivers need to implement polling where necessary rather than having one central implementation * updating the rfkill state cannot be done from arbitrary contexts, forcing drivers to use schedule_work and requiring lots of code * rfkill drivers need to keep track of soft/hard blocked internally -- the core should do this * the rfkill API has many unexpected quirks, for example being asymmetric wrt. alloc/free and register/unregister * rfkill can call back into a driver from within a function the driver called -- this is prone to deadlocks and generally should be avoided * rfkill-input pointlessly is a separate module * drivers need to #ifdef rfkill functions (unless they want to depend on or select RFKILL) -- rfkill should provide inlines that do nothing if it isn't compiled in * the rfkill structure is not opaque -- drivers need to initialise it correctly (lots of sanity checking code required) -- instead force drivers to pass the right variables to rfkill_alloc() * the documentation is hard to read because it always assumes the reader is completely clueless and contains way TOO MANY CAPS * the rfkill code needlessly uses a lot of locks and atomic operations in locked sections * fix LED trigger to actually change the LED when the radio state changes -- this wasn't done before Tested-by: Alan Jenkins Signed-off-by: Henrique de Moraes Holschuh [thinkpad] Signed-off-by: Johannes Berg Signed-off-by: John W. Linville --- drivers/platform/x86/thinkpad_acpi.c | 873 ++++++++++++++++++----------------- 1 file changed, 452 insertions(+), 421 deletions(-) (limited to 'drivers/platform/x86/thinkpad_acpi.c') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 912be65b626..cfcafa4e947 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -166,13 +166,6 @@ enum { #define TPACPI_MAX_ACPI_ARGS 3 -/* rfkill switches */ -enum { - TPACPI_RFK_BLUETOOTH_SW_ID = 0, - TPACPI_RFK_WWAN_SW_ID, - TPACPI_RFK_UWB_SW_ID, -}; - /* printk headers */ #define TPACPI_LOG TPACPI_FILE ": " #define TPACPI_EMERG KERN_EMERG TPACPI_LOG @@ -1005,67 +998,237 @@ static int __init tpacpi_check_std_acpi_brightness_support(void) return 0; } -static int __init tpacpi_new_rfkill(const unsigned int id, - struct rfkill **rfk, +static void printk_deprecated_attribute(const char * const what, + const char * const details) +{ + tpacpi_log_usertask("deprecated sysfs attribute"); + printk(TPACPI_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 +}; + +/* rfkill switches */ +enum tpacpi_rfk_id { + TPACPI_RFK_BLUETOOTH_SW_ID = 0, + TPACPI_RFK_WWAN_SW_ID, + TPACPI_RFK_UWB_SW_ID, + TPACPI_RFK_SW_MAX +}; + +static const char *tpacpi_rfkill_names[] = { + [TPACPI_RFK_BLUETOOTH_SW_ID] = "bluetooth", + [TPACPI_RFK_WWAN_SW_ID] = "wwan", + [TPACPI_RFK_UWB_SW_ID] = "uwb", + [TPACPI_RFK_SW_MAX] = NULL +}; + +/* ThinkPad-ACPI rfkill subdriver */ +struct tpacpi_rfk { + struct rfkill *rfkill; + enum tpacpi_rfk_id id; + const struct tpacpi_rfk_ops *ops; +}; + +struct tpacpi_rfk_ops { + /* firmware interface */ + int (*get_status)(void); + int (*set_status)(const enum tpacpi_rfkill_state); +}; + +static struct tpacpi_rfk *tpacpi_rfkill_switches[TPACPI_RFK_SW_MAX]; + +/* Query FW and update rfkill sw state for a given rfkill switch */ +static int tpacpi_rfk_update_swstate(const struct tpacpi_rfk *tp_rfk) +{ + int status; + + if (!tp_rfk) + return -ENODEV; + + status = (tp_rfk->ops->get_status)(); + if (status < 0) + return status; + + rfkill_set_sw_state(tp_rfk->rfkill, + (status == TPACPI_RFK_RADIO_OFF)); + + return status; +} + +/* Query FW and update rfkill sw state for all rfkill switches */ +static void tpacpi_rfk_update_swstate_all(void) +{ + unsigned int i; + + for (i = 0; i < TPACPI_RFK_SW_MAX; i++) + tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[i]); +} + +/* + * Sync the HW-blocking state of all rfkill switches, + * do notice it causes the rfkill core to schedule uevents + */ +static void tpacpi_rfk_update_hwblock_state(bool blocked) +{ + unsigned int 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 */ +static int hotkey_get_wlsw(void); + +/* Call to query WLSW state and update all rfkill switches */ +static bool 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) + return false; + + hw_blocked = (res == TPACPI_RFK_RADIO_OFF); + tpacpi_rfk_update_hwblock_state(hw_blocked); + + return hw_blocked; +} + +static int tpacpi_rfk_hook_set_block(void *data, bool blocked) +{ + struct tpacpi_rfk *tp_rfk = data; + int res; + + dbg_printk(TPACPI_DBG_RFKILL, + "request to change radio state to %s\n", + blocked ? "blocked" : "unblocked"); + + /* try to set radio state */ + res = (tp_rfk->ops->set_status)(blocked ? + TPACPI_RFK_RADIO_OFF : TPACPI_RFK_RADIO_ON); + + /* and update the rfkill core with whatever the FW really did */ + tpacpi_rfk_update_swstate(tp_rfk); + + return (res < 0) ? res : 0; +} + +static const struct rfkill_ops tpacpi_rfk_rfkill_ops = { + .set_block = tpacpi_rfk_hook_set_block, +}; + +static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, + const struct tpacpi_rfk_ops *tp_rfkops, const enum rfkill_type rfktype, const char *name, - const bool set_default, - int (*toggle_radio)(void *, enum rfkill_state), - int (*get_state)(void *, enum rfkill_state *)) + const bool set_default) { + struct tpacpi_rfk *atp_rfk; int res; - enum rfkill_state initial_state = RFKILL_STATE_SOFT_BLOCKED; + bool initial_sw_state = false; + int initial_sw_status; - res = get_state(NULL, &initial_state); - if (res < 0) { + BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]); + + initial_sw_status = (tp_rfkops->get_status)(); + if (initial_sw_status < 0) { printk(TPACPI_ERR "failed to read initial state for %s, error %d; " - "will turn radio off\n", name, res); - } else if (set_default) { - /* try to set the initial state as the default for the rfkill - * type, since we ask the firmware to preserve it across S5 in - * NVRAM */ - if (rfkill_set_default(rfktype, - (initial_state == RFKILL_STATE_UNBLOCKED) ? - RFKILL_STATE_UNBLOCKED : - RFKILL_STATE_SOFT_BLOCKED) == -EPERM) - vdbg_printk(TPACPI_DBG_RFKILL, - "Default state for %s cannot be changed\n", - name); - } - - *rfk = rfkill_allocate(&tpacpi_pdev->dev, rfktype); - if (!*rfk) { + "will turn radio off\n", name, initial_sw_status); + } else { + initial_sw_state = (initial_sw_status == TPACPI_RFK_RADIO_OFF); + if (set_default) { + /* try to set the initial state as the default for the + * rfkill type, since we ask the firmware to preserve + * it across S5 in NVRAM */ + rfkill_set_global_sw_state(rfktype, initial_sw_state); + } + } + + 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) { printk(TPACPI_ERR "failed to allocate memory for rfkill class\n"); + kfree(atp_rfk); return -ENOMEM; } - (*rfk)->name = name; - (*rfk)->get_state = get_state; - (*rfk)->toggle_radio = toggle_radio; - (*rfk)->state = initial_state; + atp_rfk->id = id; + atp_rfk->ops = tp_rfkops; + + rfkill_set_states(atp_rfk->rfkill, initial_sw_state, + tpacpi_rfk_check_hwblock_state()); - res = rfkill_register(*rfk); + res = rfkill_register(atp_rfk->rfkill); if (res < 0) { printk(TPACPI_ERR "failed to register %s rfkill switch: %d\n", name, res); - rfkill_free(*rfk); - *rfk = NULL; + rfkill_destroy(atp_rfk->rfkill); + kfree(atp_rfk); return res; } + tpacpi_rfkill_switches[id] = atp_rfk; return 0; } -static void printk_deprecated_attribute(const char * const what, - const char * const details) +static void tpacpi_destroy_rfkill(const enum tpacpi_rfk_id id) { - tpacpi_log_usertask("deprecated sysfs attribute"); - printk(TPACPI_WARN "WARNING: sysfs attribute %s is deprecated and " - "will be removed. %s\n", - what, details); + struct tpacpi_rfk *tp_rfk; + + BUG_ON(id >= TPACPI_RFK_SW_MAX); + + tp_rfk = tpacpi_rfkill_switches[id]; + if (tp_rfk) { + rfkill_unregister(tp_rfk->rfkill); + tpacpi_rfkill_switches[id] = NULL; + kfree(tp_rfk); + } } static void printk_deprecated_rfkill_attribute(const char * const what) @@ -1074,6 +1237,112 @@ static void printk_deprecated_rfkill_attribute(const char * const what) "Please switch to generic rfkill before year 2010"); } +/* sysfs enable ------------------------------------------------ */ +static ssize_t tpacpi_rfk_sysfs_enable_show(const enum tpacpi_rfk_id id, + struct device_attribute *attr, + char *buf) +{ + int status; + + printk_deprecated_rfkill_attribute(attr->attr.name); + + /* 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; + } + + return snprintf(buf, PAGE_SIZE, "%d\n", + (status == TPACPI_RFK_RADIO_ON) ? 1 : 0); +} + +static ssize_t tpacpi_rfk_sysfs_enable_store(const enum tpacpi_rfk_id id, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long t; + int res; + + printk_deprecated_rfkill_attribute(attr->attr.name); + + if (parse_strtoul(buf, 1, &t)) + return -EINVAL; + + 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 -------------------------------------------------------------- */ +static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, char *p) +{ + int len = 0; + + if (id >= TPACPI_RFK_SW_MAX) + len += sprintf(p + len, "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; + } + + len += sprintf(p + len, "status:\t\t%s\n", + (status == TPACPI_RFK_RADIO_ON) ? + "enabled" : "disabled"); + len += sprintf(p + len, "commands:\tenable, disable\n"); + } + + return len; +} + +static int tpacpi_rfk_procfs_write(const enum tpacpi_rfk_id id, char *buf) +{ + char *cmd; + int status = -1; + int res = 0; + + if (id >= TPACPI_RFK_SW_MAX) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "enable") == 0) + status = TPACPI_RFK_RADIO_ON; + else if (strlencmp(cmd, "disable") == 0) + status = TPACPI_RFK_RADIO_OFF; + else + return -EINVAL; + } + + if (status != -1) { + tpacpi_disclose_usertask("procfs", "attempt to %s %s\n", + (status == TPACPI_RFK_RADIO_ON) ? + "enable" : "disable", + tpacpi_rfkill_names[id]); + res = (tpacpi_rfkill_switches[id]->ops->set_status)(status); + tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]); + } + + return res; +} + /************************************************************************* * thinkpad-acpi driver attributes */ @@ -1127,8 +1396,6 @@ static DRIVER_ATTR(version, S_IRUGO, #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES -static void tpacpi_send_radiosw_update(void); - /* wlsw_emulstate ------------------------------------------------------ */ static ssize_t tpacpi_driver_wlsw_emulstate_show(struct device_driver *drv, char *buf) @@ -1144,11 +1411,10 @@ static ssize_t tpacpi_driver_wlsw_emulstate_store(struct device_driver *drv, if (parse_strtoul(buf, 1, &t)) return -EINVAL; - if (tpacpi_wlsw_emulstate != t) { - tpacpi_wlsw_emulstate = !!t; - tpacpi_send_radiosw_update(); - } else + if (tpacpi_wlsw_emulstate != !!t) { tpacpi_wlsw_emulstate = !!t; + tpacpi_rfk_update_hwblock_state(!t); /* negative logic */ + } return count; } @@ -1463,17 +1729,23 @@ static struct attribute_set *hotkey_dev_attributes; /* HKEY.MHKG() return bits */ #define TP_HOTKEY_TABLET_MASK (1 << 3) -static int hotkey_get_wlsw(int *status) +static int hotkey_get_wlsw(void) { + int status; + + if (!tp_features.hotkey_wlsw) + return -ENODEV; + #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES - if (dbg_wlswemul) { - *status = !!tpacpi_wlsw_emulstate; - return 0; - } + if (dbg_wlswemul) + return (tpacpi_wlsw_emulstate) ? + TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF; #endif - if (!acpi_evalf(hkey_handle, status, "WLSW", "d")) + + if (!acpi_evalf(hkey_handle, &status, "WLSW", "d")) return -EIO; - return 0; + + return (status) ? TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF; } static int hotkey_get_tablet_mode(int *status) @@ -2107,12 +2379,16 @@ static ssize_t hotkey_radio_sw_show(struct device *dev, struct device_attribute *attr, char *buf) { - int res, s; - res = hotkey_get_wlsw(&s); + int res; + res = hotkey_get_wlsw(); if (res < 0) return res; - return snprintf(buf, PAGE_SIZE, "%d\n", !!s); + /* Opportunistic update */ + tpacpi_rfk_update_hwblock_state((res == TPACPI_RFK_RADIO_OFF)); + + return snprintf(buf, PAGE_SIZE, "%d\n", + (res == TPACPI_RFK_RADIO_OFF) ? 0 : 1); } static struct device_attribute dev_attr_hotkey_radio_sw = @@ -2223,30 +2499,52 @@ static struct attribute *hotkey_mask_attributes[] __initdata = { &dev_attr_hotkey_wakeup_hotunplug_complete.attr, }; -static void bluetooth_update_rfk(void); -static void wan_update_rfk(void); -static void uwb_update_rfk(void); +/* + * Sync both the hw and sw blocking state of all switches + */ static void tpacpi_send_radiosw_update(void) { int wlsw; - /* Sync these BEFORE sending any rfkill events */ - if (tp_features.bluetooth) - bluetooth_update_rfk(); - if (tp_features.wan) - wan_update_rfk(); - if (tp_features.uwb) - uwb_update_rfk(); + /* + * 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 syncronization point + * for the above. + * + * We optimize to avoid numerous calls to hotkey_get_wlsw. + */ - if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&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 sw blocking state */ + tpacpi_rfk_update_swstate_all(); + + /* 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); input_report_switch(tpacpi_inputdev, - SW_RFKILL_ALL, !!wlsw); + SW_RFKILL_ALL, (wlsw > 0)); input_sync(tpacpi_inputdev); mutex_unlock(&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(); } @@ -3056,8 +3354,6 @@ enum { #define TPACPI_RFK_BLUETOOTH_SW_NAME "tpacpi_bluetooth_sw" -static struct rfkill *tpacpi_bluetooth_rfkill; - static void bluetooth_suspend(pm_message_t state) { /* Try to make sure radio will resume powered off */ @@ -3067,83 +3363,47 @@ static void bluetooth_suspend(pm_message_t state) "bluetooth power down on resume request failed\n"); } -static int bluetooth_get_radiosw(void) +static int bluetooth_get_status(void) { int status; - if (!tp_features.bluetooth) - return -ENODEV; - - /* WLSW overrides bluetooth in firmware/hardware, reflect that */ - if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status) - return RFKILL_STATE_HARD_BLOCKED; - #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES if (dbg_bluetoothemul) return (tpacpi_bluetooth_emulstate) ? - RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED; + TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF; #endif if (!acpi_evalf(hkey_handle, &status, "GBDC", "d")) return -EIO; return ((status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0) ? - RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED; + TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF; } -static void bluetooth_update_rfk(void) +static int bluetooth_set_status(enum tpacpi_rfkill_state state) { int status; - if (!tpacpi_bluetooth_rfkill) - return; - - status = bluetooth_get_radiosw(); - if (status < 0) - return; - rfkill_force_state(tpacpi_bluetooth_rfkill, status); - vdbg_printk(TPACPI_DBG_RFKILL, - "forced rfkill state to %d\n", - status); -} - -static int bluetooth_set_radiosw(int radio_on, int update_rfk) -{ - int status; - - if (!tp_features.bluetooth) - return -ENODEV; - - /* WLSW overrides bluetooth in firmware/hardware, but there is no - * reason to risk weird behaviour. */ - if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status - && radio_on) - return -EPERM; - - vdbg_printk(TPACPI_DBG_RFKILL, - "will %s bluetooth\n", radio_on ? "enable" : "disable"); + "will attempt to %s bluetooth\n", + (state == TPACPI_RFK_RADIO_ON) ? "enable" : "disable"); #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES if (dbg_bluetoothemul) { - tpacpi_bluetooth_emulstate = !!radio_on; - if (update_rfk) - bluetooth_update_rfk(); + tpacpi_bluetooth_emulstate = (state == TPACPI_RFK_RADIO_ON); return 0; } #endif /* We make sure to keep TP_ACPI_BLUETOOTH_RESUMECTRL off */ - if (radio_on) + if (state == TPACPI_RFK_RADIO_ON) status = TP_ACPI_BLUETOOTH_RADIOSSW; else status = 0; + if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status)) return -EIO; - if (update_rfk) - bluetooth_update_rfk(); - return 0; } @@ -3152,35 +3412,16 @@ static ssize_t bluetooth_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { - int status; - - printk_deprecated_rfkill_attribute("bluetooth_enable"); - - status = bluetooth_get_radiosw(); - if (status < 0) - return status; - - return snprintf(buf, PAGE_SIZE, "%d\n", - (status == RFKILL_STATE_UNBLOCKED) ? 1 : 0); + return tpacpi_rfk_sysfs_enable_show(TPACPI_RFK_BLUETOOTH_SW_ID, + attr, buf); } static ssize_t bluetooth_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - unsigned long t; - int res; - - printk_deprecated_rfkill_attribute("bluetooth_enable"); - - if (parse_strtoul(buf, 1, &t)) - return -EINVAL; - - tpacpi_disclose_usertask("bluetooth_enable", "set to %ld\n", t); - - res = bluetooth_set_radiosw(t, 1); - - return (res) ? res : count; + return tpacpi_rfk_sysfs_enable_store(TPACPI_RFK_BLUETOOTH_SW_ID, + attr, buf, count); } static struct device_attribute dev_attr_bluetooth_enable = @@ -3198,23 +3439,10 @@ static const struct attribute_group bluetooth_attr_group = { .attrs = bluetooth_attributes, }; -static int tpacpi_bluetooth_rfk_get(void *data, enum rfkill_state *state) -{ - int bts = bluetooth_get_radiosw(); - - if (bts < 0) - return bts; - - *state = bts; - return 0; -} - -static int tpacpi_bluetooth_rfk_set(void *data, enum rfkill_state state) -{ - dbg_printk(TPACPI_DBG_RFKILL, - "request to change radio state to %d\n", state); - return bluetooth_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0); -} +static const struct tpacpi_rfk_ops bluetooth_tprfk_ops = { + .get_status = bluetooth_get_status, + .set_status = bluetooth_set_status, +}; static void bluetooth_shutdown(void) { @@ -3230,13 +3458,12 @@ static void bluetooth_shutdown(void) static void bluetooth_exit(void) { - bluetooth_shutdown(); - - if (tpacpi_bluetooth_rfkill) - rfkill_unregister(tpacpi_bluetooth_rfkill); - sysfs_remove_group(&tpacpi_pdev->dev.kobj, &bluetooth_attr_group); + + tpacpi_destroy_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID); + + bluetooth_shutdown(); } static int __init bluetooth_init(struct ibm_init_struct *iibm) @@ -3277,20 +3504,18 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm) if (!tp_features.bluetooth) return 1; - res = sysfs_create_group(&tpacpi_pdev->dev.kobj, - &bluetooth_attr_group); - if (res) - return res; - res = tpacpi_new_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID, - &tpacpi_bluetooth_rfkill, + &bluetooth_tprfk_ops, RFKILL_TYPE_BLUETOOTH, TPACPI_RFK_BLUETOOTH_SW_NAME, - true, - tpacpi_bluetooth_rfk_set, - tpacpi_bluetooth_rfk_get); + true); + if (res) + return res; + + res = sysfs_create_group(&tpacpi_pdev->dev.kobj, + &bluetooth_attr_group); if (res) { - bluetooth_exit(); + tpacpi_destroy_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID); return res; } @@ -3300,46 +3525,12 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm) /* procfs -------------------------------------------------------------- */ static int bluetooth_read(char *p) { - int len = 0; - int status = bluetooth_get_radiosw(); - - if (!tp_features.bluetooth) - len += sprintf(p + len, "status:\t\tnot supported\n"); - else { - len += sprintf(p + len, "status:\t\t%s\n", - (status == RFKILL_STATE_UNBLOCKED) ? - "enabled" : "disabled"); - len += sprintf(p + len, "commands:\tenable, disable\n"); - } - - return len; + return tpacpi_rfk_procfs_read(TPACPI_RFK_BLUETOOTH_SW_ID, p); } static int bluetooth_write(char *buf) { - char *cmd; - int state = -1; - - if (!tp_features.bluetooth) - return -ENODEV; - - while ((cmd = next_cmd(&buf))) { - if (strlencmp(cmd, "enable") == 0) { - state = 1; - } else if (strlencmp(cmd, "disable") == 0) { - state = 0; - } else - return -EINVAL; - } - - if (state != -1) { - tpacpi_disclose_usertask("procfs bluetooth", - "attempt to %s\n", - state ? "enable" : "disable"); - bluetooth_set_radiosw(state, 1); - } - - return 0; + return tpacpi_rfk_procfs_write(TPACPI_RFK_BLUETOOTH_SW_ID, buf); } static struct ibm_struct bluetooth_driver_data = { @@ -3365,8 +3556,6 @@ enum { #define TPACPI_RFK_WWAN_SW_NAME "tpacpi_wwan_sw" -static struct rfkill *tpacpi_wan_rfkill; - static void wan_suspend(pm_message_t state) { /* Try to make sure radio will resume powered off */ @@ -3376,83 +3565,47 @@ static void wan_suspend(pm_message_t state) "WWAN power down on resume request failed\n"); } -static int wan_get_radiosw(void) +static int wan_get_status(void) { int status; - if (!tp_features.wan) - return -ENODEV; - - /* WLSW overrides WWAN in firmware/hardware, reflect that */ - if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status) - return RFKILL_STATE_HARD_BLOCKED; - #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES if (dbg_wwanemul) return (tpacpi_wwan_emulstate) ? - RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED; + TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF; #endif if (!acpi_evalf(hkey_handle, &status, "GWAN", "d")) return -EIO; return ((status & TP_ACPI_WANCARD_RADIOSSW) != 0) ? - RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED; -} - -static void wan_update_rfk(void) -{ - int status; - - if (!tpacpi_wan_rfkill) - return; - - status = wan_get_radiosw(); - if (status < 0) - return; - rfkill_force_state(tpacpi_wan_rfkill, status); - - vdbg_printk(TPACPI_DBG_RFKILL, - "forced rfkill state to %d\n", - status); + TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF; } -static int wan_set_radiosw(int radio_on, int update_rfk) +static int wan_set_status(enum tpacpi_rfkill_state state) { int status; - if (!tp_features.wan) - return -ENODEV; - - /* WLSW overrides bluetooth in firmware/hardware, but there is no - * reason to risk weird behaviour. */ - if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status - && radio_on) - return -EPERM; - vdbg_printk(TPACPI_DBG_RFKILL, - "will %s WWAN\n", radio_on ? "enable" : "disable"); + "will attempt to %s wwan\n", + (state == TPACPI_RFK_RADIO_ON) ? "enable" : "disable"); #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES if (dbg_wwanemul) { - tpacpi_wwan_emulstate = !!radio_on; - if (update_rfk) - wan_update_rfk(); + tpacpi_wwan_emulstate = (state == TPACPI_RFK_RADIO_ON); return 0; } #endif /* We make sure to keep TP_ACPI_WANCARD_RESUMECTRL off */ - if (radio_on) + if (state == TPACPI_RFK_RADIO_ON) status = TP_ACPI_WANCARD_RADIOSSW; else status = 0; + if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) return -EIO; - if (update_rfk) - wan_update_rfk(); - return 0; } @@ -3461,35 +3614,16 @@ static ssize_t wan_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { - int status; - - printk_deprecated_rfkill_attribute("wwan_enable"); - - status = wan_get_radiosw(); - if (status < 0) - return status; - - return snprintf(buf, PAGE_SIZE, "%d\n", - (status == RFKILL_STATE_UNBLOCKED) ? 1 : 0); + return tpacpi_rfk_sysfs_enable_show(TPACPI_RFK_WWAN_SW_ID, + attr, buf); } static ssize_t wan_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - unsigned long t; - int res; - - printk_deprecated_rfkill_attribute("wwan_enable"); - - if (parse_strtoul(buf, 1, &t)) - return -EINVAL; - - tpacpi_disclose_usertask("wwan_enable", "set to %ld\n", t); - - res = wan_set_radiosw(t, 1); - - return (res) ? res : count; + return tpacpi_rfk_sysfs_enable_store(TPACPI_RFK_WWAN_SW_ID, + attr, buf, count); } static struct device_attribute dev_attr_wan_enable = @@ -3507,23 +3641,10 @@ static const struct attribute_group wan_attr_group = { .attrs = wan_attributes, }; -static int tpacpi_wan_rfk_get(void *data, enum rfkill_state *state) -{ - int wans = wan_get_radiosw(); - - if (wans < 0) - return wans; - - *state = wans; - return 0; -} - -static int tpacpi_wan_rfk_set(void *data, enum rfkill_state state) -{ - dbg_printk(TPACPI_DBG_RFKILL, - "request to change radio state to %d\n", state); - return wan_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0); -} +static const struct tpacpi_rfk_ops wan_tprfk_ops = { + .get_status = wan_get_status, + .set_status = wan_set_status, +}; static void wan_shutdown(void) { @@ -3539,13 +3660,12 @@ static void wan_shutdown(void) static void wan_exit(void) { - wan_shutdown(); - - if (tpacpi_wan_rfkill) - rfkill_unregister(tpacpi_wan_rfkill); - sysfs_remove_group(&tpacpi_pdev->dev.kobj, &wan_attr_group); + + tpacpi_destroy_rfkill(TPACPI_RFK_WWAN_SW_ID); + + wan_shutdown(); } static int __init wan_init(struct ibm_init_struct *iibm) @@ -3584,20 +3704,19 @@ static int __init wan_init(struct ibm_init_struct *iibm) if (!tp_features.wan) return 1; - res = sysfs_create_group(&tpacpi_pdev->dev.kobj, - &wan_attr_group); - if (res) - return res; - res = tpacpi_new_rfkill(TPACPI_RFK_WWAN_SW_ID, - &tpacpi_wan_rfkill, + &wan_tprfk_ops, RFKILL_TYPE_WWAN, TPACPI_RFK_WWAN_SW_NAME, - true, - tpacpi_wan_rfk_set, - tpacpi_wan_rfk_get); + true); + if (res) + return res; + + res = sysfs_create_group(&tpacpi_pdev->dev.kobj, + &wan_attr_group); + if (res) { - wan_exit(); + tpacpi_destroy_rfkill(TPACPI_RFK_WWAN_SW_ID); return res; } @@ -3607,48 +3726,12 @@ static int __init wan_init(struct ibm_init_struct *iibm) /* procfs -------------------------------------------------------------- */ static int wan_read(char *p) { - int len = 0; - int status = wan_get_radiosw(); - - tpacpi_disclose_usertask("procfs wan", "read"); - - if (!tp_features.wan) - len += sprintf(p + len, "status:\t\tnot supported\n"); - else { - len += sprintf(p + len, "status:\t\t%s\n", - (status == RFKILL_STATE_UNBLOCKED) ? - "enabled" : "disabled"); - len += sprintf(p + len, "commands:\tenable, disable\n"); - } - - return len; + return tpacpi_rfk_procfs_read(TPACPI_RFK_WWAN_SW_ID, p); } static int wan_write(char *buf) { - char *cmd; - int state = -1; - - if (!tp_features.wan) - return -ENODEV; - - while ((cmd = next_cmd(&buf))) { - if (strlencmp(cmd, "enable") == 0) { - state = 1; - } else if (strlencmp(cmd, "disable") == 0) { - state = 0; - } else - return -EINVAL; - } - - if (state != -1) { - tpacpi_disclose_usertask("procfs wan", - "attempt to %s\n", - state ? "enable" : "disable"); - wan_set_radiosw(state, 1); - } - - return 0; + return tpacpi_rfk_procfs_write(TPACPI_RFK_WWAN_SW_ID, buf); } static struct ibm_struct wan_driver_data = { @@ -3672,108 +3755,59 @@ enum { #define TPACPI_RFK_UWB_SW_NAME "tpacpi_uwb_sw" -static struct rfkill *tpacpi_uwb_rfkill; - -static int uwb_get_radiosw(void) +static int uwb_get_status(void) { int status; - if (!tp_features.uwb) - return -ENODEV; - - /* WLSW overrides UWB in firmware/hardware, reflect that */ - if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status) - return RFKILL_STATE_HARD_BLOCKED; - #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES if (dbg_uwbemul) return (tpacpi_uwb_emulstate) ? - RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED; + TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF; #endif if (!acpi_evalf(hkey_handle, &status, "GUWB", "d")) return -EIO; return ((status & TP_ACPI_UWB_RADIOSSW) != 0) ? - RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED; + TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF; } -static void uwb_update_rfk(void) +static int uwb_set_status(enum tpacpi_rfkill_state state) { int status; - if (!tpacpi_uwb_rfkill) - return; - - status = uwb_get_radiosw(); - if (status < 0) - return; - rfkill_force_state(tpacpi_uwb_rfkill, status); - vdbg_printk(TPACPI_DBG_RFKILL, - "forced rfkill state to %d\n", - status); -} - -static int uwb_set_radiosw(int radio_on, int update_rfk) -{ - int status; - - if (!tp_features.uwb) - return -ENODEV; - - /* WLSW overrides UWB in firmware/hardware, but there is no - * reason to risk weird behaviour. */ - if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status - && radio_on) - return -EPERM; - - vdbg_printk(TPACPI_DBG_RFKILL, - "will %s UWB\n", radio_on ? "enable" : "disable"); + "will attempt to %s UWB\n", + (state == TPACPI_RFK_RADIO_ON) ? "enable" : "disable"); #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES if (dbg_uwbemul) { - tpacpi_uwb_emulstate = !!radio_on; - if (update_rfk) - uwb_update_rfk(); + tpacpi_uwb_emulstate = (state == TPACPI_RFK_RADIO_ON); return 0; } #endif - status = (radio_on) ? TP_ACPI_UWB_RADIOSSW : 0; + if (state == TPACPI_RFK_RADIO_ON) + status = TP_ACPI_UWB_RADIOSSW; + else + status = 0; + if (!acpi_evalf(hkey_handle, NULL, "SUWB", "vd", status)) return -EIO; - if (update_rfk) - uwb_update_rfk(); - return 0; } /* --------------------------------------------------------------------- */ -static int tpacpi_uwb_rfk_get(void *data, enum rfkill_state *state) -{ - int uwbs = uwb_get_radiosw(); - - if (uwbs < 0) - return uwbs; - - *state = uwbs; - return 0; -} - -static int tpacpi_uwb_rfk_set(void *data, enum rfkill_state state) -{ - dbg_printk(TPACPI_DBG_RFKILL, - "request to change radio state to %d\n", state); - return uwb_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0); -} +static const struct tpacpi_rfk_ops uwb_tprfk_ops = { + .get_status = uwb_get_status, + .set_status = uwb_set_status, +}; static void uwb_exit(void) { - if (tpacpi_uwb_rfkill) - rfkill_unregister(tpacpi_uwb_rfkill); + tpacpi_destroy_rfkill(TPACPI_RFK_UWB_SW_ID); } static int __init uwb_init(struct ibm_init_struct *iibm) @@ -3813,13 +3847,10 @@ static int __init uwb_init(struct ibm_init_struct *iibm) return 1; res = tpacpi_new_rfkill(TPACPI_RFK_UWB_SW_ID, - &tpacpi_uwb_rfkill, + &uwb_tprfk_ops, RFKILL_TYPE_UWB, TPACPI_RFK_UWB_SW_NAME, - false, - tpacpi_uwb_rfk_set, - tpacpi_uwb_rfk_get); - + false); return res; } -- cgit v1.2.3-70-g09d2 From b3fa1329eaf2a7b97124dacf5b663fd51346ac19 Mon Sep 17 00:00:00 2001 From: Alan Jenkins Date: Mon, 8 Jun 2009 13:27:27 +0100 Subject: rfkill: remove set_global_sw_state rfkill_set_global_sw_state() (previously rfkill_set_default()) will no longer be exported by the rewritten rfkill core. Instead, platform drivers which can provide persistent soft-rfkill state across power-down/reboot should indicate their initial state by calling rfkill_set_sw_state() before registration. Otherwise, they will be initialized to a default value during registration by a set_block call. We remove existing calls to rfkill_set_sw_state() which happen before registration, since these had no effect in the old model. If these drivers do have persistent state, the calls can be put back (subject to testing :-). This affects hp-wmi and acer-wmi. Drivers with persistent state will affect the global state only if rfkill-input is enabled. This is required, otherwise booting with wireless soft-blocked and pressing the wireless-toggle key once would have no apparent effect. This special case will be removed in future along with rfkill-input, in favour of a more flexible userspace daemon (see Documentation/feature-removal-schedule.txt). Now rfkill_global_states[n].def is only used to preserve global states over EPO, it is renamed to ".sav". Signed-off-by: Alan Jenkins Acked-by: Henrique de Moraes Holschuh Signed-off-by: John W. Linville --- drivers/platform/x86/acer-wmi.c | 3 -- drivers/platform/x86/eeepc-laptop.c | 8 ++-- drivers/platform/x86/hp-wmi.c | 4 -- drivers/platform/x86/thinkpad_acpi.c | 31 +++++++------- include/linux/rfkill.h | 28 ++++--------- net/rfkill/core.c | 81 +++++++++++++----------------------- 6 files changed, 56 insertions(+), 99 deletions(-) (limited to 'drivers/platform/x86/thinkpad_acpi.c') diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index b618fa51db2..09a503e5da6 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -988,7 +988,6 @@ static struct rfkill *acer_rfkill_register(struct device *dev, char *name, u32 cap) { int err; - u32 state; struct rfkill *rfkill_dev; rfkill_dev = rfkill_alloc(name, dev, type, @@ -996,8 +995,6 @@ static struct rfkill *acer_rfkill_register(struct device *dev, (void *)(unsigned long)cap); if (!rfkill_dev) return ERR_PTR(-ENOMEM); - get_u32(&state, cap); - rfkill_set_sw_state(rfkill_dev, !state); err = rfkill_register(rfkill_dev); if (err) { diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index 1208d0cedd1..03bf522bd7a 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -675,8 +675,8 @@ static int eeepc_hotk_add(struct acpi_device *device) if (!ehotk->eeepc_wlan_rfkill) goto wlan_fail; - rfkill_set_global_sw_state(RFKILL_TYPE_WLAN, - get_acpi(CM_ASL_WLAN) != 1); + rfkill_set_sw_state(ehotk->eeepc_wlan_rfkill, + get_acpi(CM_ASL_WLAN) != 1); result = rfkill_register(ehotk->eeepc_wlan_rfkill); if (result) goto wlan_fail; @@ -693,8 +693,8 @@ static int eeepc_hotk_add(struct acpi_device *device) if (!ehotk->eeepc_bluetooth_rfkill) goto bluetooth_fail; - rfkill_set_global_sw_state(RFKILL_TYPE_BLUETOOTH, - get_acpi(CM_ASL_BLUETOOTH) != 1); + rfkill_set_sw_state(ehotk->eeepc_bluetooth_rfkill, + get_acpi(CM_ASL_BLUETOOTH) != 1); result = rfkill_register(ehotk->eeepc_bluetooth_rfkill); if (result) goto bluetooth_fail; diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index 8d931145cbf..16fffe44e33 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -422,7 +422,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) RFKILL_TYPE_WLAN, &hp_wmi_rfkill_ops, (void *) 0); - rfkill_set_sw_state(wifi_rfkill, hp_wmi_wifi_state()); err = rfkill_register(wifi_rfkill); if (err) goto register_wifi_error; @@ -433,8 +432,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) RFKILL_TYPE_BLUETOOTH, &hp_wmi_rfkill_ops, (void *) 1); - rfkill_set_sw_state(bluetooth_rfkill, - hp_wmi_bluetooth_state()); err = rfkill_register(bluetooth_rfkill); if (err) goto register_bluetooth_error; @@ -445,7 +442,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) RFKILL_TYPE_WWAN, &hp_wmi_rfkill_ops, (void *) 2); - rfkill_set_sw_state(wwan_rfkill, hp_wmi_wwan_state()); err = rfkill_register(wwan_rfkill); if (err) goto register_wwan_err; diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index cfcafa4e947..86e958539f4 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -1168,21 +1168,6 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]); - initial_sw_status = (tp_rfkops->get_status)(); - if (initial_sw_status < 0) { - printk(TPACPI_ERR - "failed to read initial state for %s, error %d; " - "will turn radio off\n", name, initial_sw_status); - } else { - initial_sw_state = (initial_sw_status == TPACPI_RFK_RADIO_OFF); - if (set_default) { - /* try to set the initial state as the default for the - * rfkill type, since we ask the firmware to preserve - * it across S5 in NVRAM */ - rfkill_set_global_sw_state(rfktype, initial_sw_state); - } - } - atp_rfk = kzalloc(sizeof(struct tpacpi_rfk), GFP_KERNEL); if (atp_rfk) atp_rfk->rfkill = rfkill_alloc(name, @@ -1200,8 +1185,20 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, atp_rfk->id = id; atp_rfk->ops = tp_rfkops; - rfkill_set_states(atp_rfk->rfkill, initial_sw_state, - tpacpi_rfk_check_hwblock_state()); + initial_sw_status = (tp_rfkops->get_status)(); + if (initial_sw_status < 0) { + printk(TPACPI_ERR + "failed to read initial state for %s, error %d\n", + name, initial_sw_status); + } else { + initial_sw_state = (initial_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_set_sw_state(atp_rfk->rfkill, initial_sw_state); + } + } + rfkill_set_hw_state(atp_rfk->rfkill, tpacpi_rfk_check_hwblock_state()); res = rfkill_register(atp_rfk->rfkill); if (res < 0) { diff --git a/include/linux/rfkill.h b/include/linux/rfkill.h index d7e818ad0bc..c1dca0b8138 100644 --- a/include/linux/rfkill.h +++ b/include/linux/rfkill.h @@ -157,8 +157,14 @@ struct rfkill * __must_check rfkill_alloc(const char *name, * @rfkill: rfkill structure to be registered * * This function should be called by the transmitter driver to register - * the rfkill structure needs to be registered. Before calling this function - * the driver needs to be ready to service method calls from rfkill. + * the rfkill structure. Before calling this function the driver needs + * to be ready to service method calls from rfkill. + * + * If the software blocked state is not set before registration, + * set_block will be called to initialize it to a default value. + * + * If the hardware blocked state is not set before registration, + * it is assumed to be unblocked. */ int __must_check rfkill_register(struct rfkill *rfkill); @@ -250,19 +256,6 @@ bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked); */ void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw); -/** - * rfkill_set_global_sw_state - set global sw block default - * @type: rfkill type to set default for - * @blocked: default to set - * - * This function sets the global default -- use at boot if your platform has - * an rfkill switch. If not early enough this call may be ignored. - * - * XXX: instead of ignoring -- how about just updating all currently - * registered drivers? - */ -void rfkill_set_global_sw_state(const enum rfkill_type type, bool blocked); - /** * rfkill_blocked - query rfkill block * @@ -317,11 +310,6 @@ static inline void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw) { } -static inline void rfkill_set_global_sw_state(const enum rfkill_type type, - bool blocked) -{ -} - static inline bool rfkill_blocked(struct rfkill *rfkill) { return false; diff --git a/net/rfkill/core.c b/net/rfkill/core.c index e161ebc40a3..fa430bd03f1 100644 --- a/net/rfkill/core.c +++ b/net/rfkill/core.c @@ -57,6 +57,7 @@ struct rfkill { bool registered; bool suspended; + bool persistent; const struct rfkill_ops *ops; void *data; @@ -116,11 +117,9 @@ MODULE_PARM_DESC(default_state, "Default initial state for all radio types, 0 = radio off"); static struct { - bool cur, def; + bool cur, sav; } rfkill_global_states[NUM_RFKILL_TYPES]; -static unsigned long rfkill_states_default_locked; - static bool rfkill_epo_lock_active; @@ -392,7 +391,7 @@ void rfkill_epo(void) rfkill_set_block(rfkill, true); for (i = 0; i < NUM_RFKILL_TYPES; i++) { - rfkill_global_states[i].def = rfkill_global_states[i].cur; + rfkill_global_states[i].sav = rfkill_global_states[i].cur; rfkill_global_states[i].cur = true; } @@ -417,7 +416,7 @@ void rfkill_restore_states(void) rfkill_epo_lock_active = false; for (i = 0; i < NUM_RFKILL_TYPES; i++) - __rfkill_switch_all(i, rfkill_global_states[i].def); + __rfkill_switch_all(i, rfkill_global_states[i].sav); mutex_unlock(&rfkill_global_mutex); } @@ -464,29 +463,6 @@ bool rfkill_get_global_sw_state(const enum rfkill_type type) } #endif -void rfkill_set_global_sw_state(const enum rfkill_type type, bool blocked) -{ - BUG_ON(type == RFKILL_TYPE_ALL); - - mutex_lock(&rfkill_global_mutex); - - /* don't allow unblock when epo */ - if (rfkill_epo_lock_active && !blocked) - goto out; - - /* too late */ - if (rfkill_states_default_locked & BIT(type)) - goto out; - - rfkill_states_default_locked |= BIT(type); - - rfkill_global_states[type].cur = blocked; - rfkill_global_states[type].def = blocked; - out: - mutex_unlock(&rfkill_global_mutex); -} -EXPORT_SYMBOL(rfkill_set_global_sw_state); - bool rfkill_set_hw_state(struct rfkill *rfkill, bool blocked) { @@ -532,13 +508,14 @@ bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked) blocked = blocked || hwblock; spin_unlock_irqrestore(&rfkill->lock, flags); - if (!rfkill->registered) - return blocked; + if (!rfkill->registered) { + rfkill->persistent = true; + } else { + if (prev != blocked && !hwblock) + schedule_work(&rfkill->uevent_work); - if (prev != blocked && !hwblock) - schedule_work(&rfkill->uevent_work); - - rfkill_led_trigger_event(rfkill); + rfkill_led_trigger_event(rfkill); + } return blocked; } @@ -563,13 +540,14 @@ void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw) spin_unlock_irqrestore(&rfkill->lock, flags); - if (!rfkill->registered) - return; - - if (swprev != sw || hwprev != hw) - schedule_work(&rfkill->uevent_work); + if (!rfkill->registered) { + rfkill->persistent = true; + } else { + if (swprev != sw || hwprev != hw) + schedule_work(&rfkill->uevent_work); - rfkill_led_trigger_event(rfkill); + rfkill_led_trigger_event(rfkill); + } } EXPORT_SYMBOL(rfkill_set_states); @@ -888,15 +866,6 @@ int __must_check rfkill_register(struct rfkill *rfkill) dev_set_name(dev, "rfkill%lu", rfkill_no); rfkill_no++; - if (!(rfkill_states_default_locked & BIT(rfkill->type))) { - /* first of its kind */ - BUILD_BUG_ON(NUM_RFKILL_TYPES > - sizeof(rfkill_states_default_locked) * 8); - rfkill_states_default_locked |= BIT(rfkill->type); - rfkill_global_states[rfkill->type].cur = - rfkill_global_states[rfkill->type].def; - } - list_add_tail(&rfkill->node, &rfkill_list); error = device_add(dev); @@ -916,7 +885,17 @@ int __must_check rfkill_register(struct rfkill *rfkill) if (rfkill->ops->poll) schedule_delayed_work(&rfkill->poll_work, round_jiffies_relative(POLL_INTERVAL)); - schedule_work(&rfkill->sync_work); + + if (!rfkill->persistent || rfkill_epo_lock_active) { + schedule_work(&rfkill->sync_work); + } else { +#ifdef CONFIG_RFKILL_INPUT + bool soft_blocked = !!(rfkill->state & RFKILL_BLOCK_SW); + + if (!atomic_read(&rfkill_input_disabled)) + __rfkill_switch_all(rfkill->type, soft_blocked); +#endif + } rfkill_send_events(rfkill, RFKILL_OP_ADD); @@ -1193,7 +1172,7 @@ static int __init rfkill_init(void) int i; for (i = 0; i < NUM_RFKILL_TYPES; i++) - rfkill_global_states[i].def = !rfkill_default_state; + rfkill_global_states[i].cur = !rfkill_default_state; error = class_register(&rfkill_class); if (error) -- cgit v1.2.3-70-g09d2 From 050df107c408a3df048524b3783a5fc6d4dccfdb Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 30 May 2009 13:25:05 -0300 Subject: thinkpad-acpi: store fw version with strict checking Extend the thinkpad model and firmware identification data with the release serial number for the BIOS and firmware (when available), as that is easier to parse and compare than the version strings. We're going to greatly extend the use of the ThinkPad DMI data through quirk lists, so it is best to be quite strict and make sure what we get from DMI is exactly what we expect, otherwise quirk matching may result in quite insane things. IBM (and Lenovo, at least for the ThinkPad line) uses this schema for firmware versioning and model: Firmware model: Two digits, [0-9A-Z] Firmware version: AABBCCDD, where AA = firmware model, see above BB = "ET" for BIOS, "HT" for EC CC = release version, two digits, [0-9A-Z], "00" < "09" < "0A" < "10" < "A0" < "ZZ" DD = "WW" Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 46 ++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) (limited to 'drivers/platform/x86/thinkpad_acpi.c') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 912be65b626..d2a0ef83fbb 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -284,8 +284,10 @@ struct thinkpad_id_data { char *bios_version_str; /* Something like 1ZET51WW (1.03z) */ char *ec_version_str; /* Something like 1ZHT51WW-1.04a */ - u16 bios_model; /* Big Endian, TP-1Y = 0x5931, 0 = unknown */ + u16 bios_model; /* 1Y = 0x5931, 0 = unknown */ u16 ec_model; + u16 bios_release; /* 1ZETK1WW = 0x314b, 0 = unknown */ + u16 ec_release; char *model_str; /* ThinkPad T43 */ char *nummodel_str; /* 9384A9C for a 9384-A9C model */ @@ -7357,6 +7359,24 @@ err_out: /* Probing */ +static bool __pure __init tpacpi_is_fw_digit(const char c) +{ + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z'); +} + +/* Most models: xxyTkkWW (#.##c); Ancient 570/600 and -SL lacks (#.##c) */ +static bool __pure __init tpacpi_is_valid_fw_id(const char* const s, + const char t) +{ + return s && strlen(s) >= 8 && + tpacpi_is_fw_digit(s[0]) && + tpacpi_is_fw_digit(s[1]) && + s[2] == t && s[3] == 'T' && + tpacpi_is_fw_digit(s[4]) && + tpacpi_is_fw_digit(s[5]) && + s[6] == 'W' && s[7] == 'W'; +} + /* returns 0 - probe ok, or < 0 - probe error. * Probe ok doesn't mean thinkpad found. * On error, kfree() cleanup on tp->* is not performed, caller must do it */ @@ -7383,10 +7403,15 @@ static int __must_check __init get_thinkpad_model_data( tp->bios_version_str = kstrdup(s, GFP_KERNEL); if (s && !tp->bios_version_str) return -ENOMEM; - if (!tp->bios_version_str) + + /* Really ancient ThinkPad 240X will fail this, which is fine */ + if (!tpacpi_is_valid_fw_id(tp->bios_version_str, 'E')) return 0; + tp->bios_model = tp->bios_version_str[0] | (tp->bios_version_str[1] << 8); + tp->bios_release = (tp->bios_version_str[4] << 8) + | tp->bios_version_str[5]; /* * ThinkPad T23 or newer, A31 or newer, R50e or newer, @@ -7405,8 +7430,21 @@ static int __must_check __init get_thinkpad_model_data( tp->ec_version_str = kstrdup(ec_fw_string, GFP_KERNEL); if (!tp->ec_version_str) return -ENOMEM; - tp->ec_model = ec_fw_string[0] - | (ec_fw_string[1] << 8); + + if (tpacpi_is_valid_fw_id(ec_fw_string, 'H')) { + tp->ec_model = ec_fw_string[0] + | (ec_fw_string[1] << 8); + tp->ec_release = (ec_fw_string[4] << 8) + | ec_fw_string[5]; + } else { + printk(TPACPI_NOTICE + "ThinkPad firmware release %s " + "doesn't match the known patterns\n", + ec_fw_string); + printk(TPACPI_NOTICE + "please report this to %s\n", + TPACPI_MAIL); + } break; } } -- cgit v1.2.3-70-g09d2 From 7d95a3d564901e88ed42810f054e579874151999 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 30 May 2009 13:25:06 -0300 Subject: thinkpad-acpi: add quirklist engine Add a quirklist engine suitable for matching ThinkPad firmware, and change the code to use it. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 119 ++++++++++++++++++++++++++++------- 1 file changed, 98 insertions(+), 21 deletions(-) (limited to 'drivers/platform/x86/thinkpad_acpi.c') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index d2a0ef83fbb..3981b060b7d 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -364,6 +364,73 @@ static void tpacpi_log_usertask(const char * const what) } \ } while (0) +/* + * Quirk handling helpers + * + * ThinkPad IDs and versions seen in the field so far + * are two-characters from the set [0-9A-Z], i.e. base 36. + * + * We use values well outside that range as specials. + */ + +#define TPACPI_MATCH_ANY 0xffffU +#define TPACPI_MATCH_UNKNOWN 0U + +/* TPID('1', 'Y') == 0x5931 */ +#define TPID(__c1, __c2) (((__c2) << 8) | (__c1)) + +#define TPACPI_Q_IBM(__id1, __id2, __quirk) \ + { .vendor = PCI_VENDOR_ID_IBM, \ + .bios = TPID(__id1, __id2), \ + .ec = TPACPI_MATCH_ANY, \ + .quirks = (__quirk) } + +#define TPACPI_Q_LNV(__id1, __id2, __quirk) \ + { .vendor = PCI_VENDOR_ID_LENOVO, \ + .bios = TPID(__id1, __id2), \ + .ec = TPACPI_MATCH_ANY, \ + .quirks = (__quirk) } + +struct tpacpi_quirk { + unsigned int vendor; + u16 bios; + u16 ec; + unsigned long quirks; +}; + +/** + * 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 much match. + */ +static unsigned long __init tpacpi_check_quirks( + const struct tpacpi_quirk *qlist, + unsigned int 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; + + qlist_size--; + qlist++; + } + return 0; +} + + /**************************************************************************** **************************************************************************** * @@ -6223,30 +6290,18 @@ TPACPI_HANDLE(sfan, ec, "SFAN", /* 570 */ * 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. */ -static void fan_quirk1_detect(void) +static void fan_quirk1_setup(void) { - /* In some ThinkPads, neither the EC nor the ACPI - * DSDT initialize the HFSP register, and it ends up - * being initially set to 0x07 when it *could* be - * either 0x07 or 0x80. - * - * Enable for TP-1Y (T43), TP-78 (R51e), - * TP-76 (R52), TP-70 (T43, R52), which are known - * to be buggy. */ if (fan_control_initial_status == 0x07) { - switch (thinkpad_id.ec_model) { - case 0x5931: /* TP-1Y */ - case 0x3837: /* TP-78 */ - case 0x3637: /* TP-76 */ - case 0x3037: /* TP-70 */ - printk(TPACPI_NOTICE - "fan_init: initial fan status is unknown, " - "assuming it is in auto mode\n"); - tp_features.fan_ctrl_status_undef = 1; - ;; - } + printk(TPACPI_NOTICE + "fan_init: initial fan status is unknown, " + "assuming it is in auto mode\n"); + tp_features.fan_ctrl_status_undef = 1; } } @@ -6804,9 +6859,27 @@ static const struct attribute_group fan_attr_group = { .attrs = fan_attributes, }; +#define TPACPI_FAN_Q1 0x0001 + +#define TPACPI_FAN_QI(__id1, __id2, __quirks) \ + { .vendor = PCI_VENDOR_ID_IBM, \ + .bios = TPACPI_MATCH_ANY, \ + .ec = TPID(__id1, __id2), \ + .quirks = __quirks } + +static const struct tpacpi_quirk fan_quirk_table[] __initconst = { + TPACPI_FAN_QI('1', 'Y', TPACPI_FAN_Q1), + TPACPI_FAN_QI('7', '8', TPACPI_FAN_Q1), + TPACPI_FAN_QI('7', '6', TPACPI_FAN_Q1), + TPACPI_FAN_QI('7', '0', TPACPI_FAN_Q1), +}; + +#undef TPACPI_FAN_QI + static int __init fan_init(struct ibm_init_struct *iibm) { int rc; + unsigned long quirks; vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN, "initializing fan subdriver\n"); @@ -6823,6 +6896,9 @@ static int __init fan_init(struct ibm_init_struct *iibm) TPACPI_ACPIHANDLE_INIT(gfan); TPACPI_ACPIHANDLE_INIT(sfan); + quirks = tpacpi_check_quirks(fan_quirk_table, + ARRAY_SIZE(fan_quirk_table)); + if (gfan_handle) { /* 570, 600e/x, 770e, 770x */ fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN; @@ -6832,7 +6908,8 @@ static int __init fan_init(struct ibm_init_struct *iibm) if (likely(acpi_ec_read(fan_status_offset, &fan_control_initial_status))) { fan_status_access_mode = TPACPI_FAN_RD_TPEC; - fan_quirk1_detect(); + if (quirks & TPACPI_FAN_Q1) + fan_quirk1_setup(); } else { printk(TPACPI_ERR "ThinkPad ACPI EC access misbehaving, " -- cgit v1.2.3-70-g09d2 From 60201732f03c1231742e5872abe55a3bf59849a5 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 30 May 2009 13:25:07 -0300 Subject: thinkpad-acpi: fix BEEP ACPI handler warnings Some ThinkPads want two arguments for BEEP, while others want just one, causing ACPICA to log warnings like this: ACPI Warning (nseval-0177): Excess arguments - method [BEEP] needs 1, found 2 [20080926] Deal with it. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) (limited to 'drivers/platform/x86/thinkpad_acpi.c') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 3981b060b7d..da739d5c921 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -264,6 +264,7 @@ static struct { u32 wan:1; u32 uwb:1; u32 fan_ctrl_status_undef:1; + u32 beep_needs_two_args:1; u32 input_device_registered:1; u32 platform_drv_registered:1; u32 platform_drv_attrs_registered:1; @@ -5142,8 +5143,17 @@ static struct ibm_struct led_driver_data = { TPACPI_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */ +#define TPACPI_BEEP_Q1 0x0001 + +static const struct tpacpi_quirk beep_quirk_table[] __initconst = { + TPACPI_Q_IBM('I', 'M', TPACPI_BEEP_Q1), /* 570 */ + TPACPI_Q_IBM('I', 'U', TPACPI_BEEP_Q1), /* 570E - unverified */ +}; + static int __init beep_init(struct ibm_init_struct *iibm) { + unsigned long quirks; + vdbg_printk(TPACPI_DBG_INIT, "initializing beep subdriver\n"); TPACPI_ACPIHANDLE_INIT(beep); @@ -5151,6 +5161,11 @@ static int __init beep_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT, "beep is %s\n", str_supported(beep_handle != NULL)); + quirks = tpacpi_check_quirks(beep_quirk_table, + ARRAY_SIZE(beep_quirk_table)); + + tp_features.beep_needs_two_args = !!(quirks & TPACPI_BEEP_Q1); + return (beep_handle)? 0 : 1; } @@ -5182,8 +5197,15 @@ static int beep_write(char *buf) /* beep_cmd set */ } else return -EINVAL; - if (!acpi_evalf(beep_handle, NULL, NULL, "vdd", beep_cmd, 0)) - return -EIO; + if (tp_features.beep_needs_two_args) { + if (!acpi_evalf(beep_handle, NULL, NULL, "vdd", + beep_cmd, 0)) + return -EIO; + } else { + if (!acpi_evalf(beep_handle, NULL, NULL, "vd", + beep_cmd)) + return -EIO; + } } return 0; -- cgit v1.2.3-70-g09d2 From f21179a47ff8d1046a61c1cf5920244997a4a7bb Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 30 May 2009 13:25:08 -0300 Subject: thinkpad-acpi: enhance led support Add support for extra LEDs on recent ThinkPads, and avoid registering with the led class the LEDs which are not available for a given ThinkPad model. All non-restricted LEDs are always available through the procfs interface, as the firmware doesn't care if an attempt is made to access an invalid LED. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/laptops/thinkpad-acpi.txt | 23 +++++++-- drivers/platform/x86/thinkpad_acpi.c | 88 ++++++++++++++++++++++++++++----- 2 files changed, 96 insertions(+), 15 deletions(-) (limited to 'drivers/platform/x86/thinkpad_acpi.c') diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt index e7e9a69069e..88fc0661de5 100644 --- a/Documentation/laptops/thinkpad-acpi.txt +++ b/Documentation/laptops/thinkpad-acpi.txt @@ -920,7 +920,7 @@ The available commands are: echo ' off' >/proc/acpi/ibm/led echo ' blink' >/proc/acpi/ibm/led -The range is 0 to 7. The set of LEDs that can be +The range is 0 to 15. The set of LEDs that can be controlled varies from model to model. Here is the common ThinkPad mapping: @@ -932,6 +932,11 @@ mapping: 5 - UltraBase battery slot 6 - (unknown) 7 - standby + 8 - dock status 1 + 9 - dock status 2 + 10, 11 - (unknown) + 12 - thinkvantage + 13, 14, 15 - (unknown) All of the above can be turned on and off and can be made to blink. @@ -940,10 +945,12 @@ sysfs notes: The ThinkPad LED sysfs interface is described in detail by the LED class documentation, in Documentation/leds-class.txt. -The leds are named (in LED ID order, from 0 to 7): +The LEDs are named (in LED ID order, from 0 to 12): "tpacpi::power", "tpacpi:orange:batt", "tpacpi:green:batt", "tpacpi::dock_active", "tpacpi::bay_active", "tpacpi::dock_batt", -"tpacpi::unknown_led", "tpacpi::standby". +"tpacpi::unknown_led", "tpacpi::standby", "tpacpi::dock_status1", +"tpacpi::dock_status2", "tpacpi::unknown_led2", "tpacpi::unknown_led3", +"tpacpi::thinkvantage". Due to limitations in the sysfs LED class, if the status of the LED indicators cannot be read due to an error, thinkpad-acpi will report it as @@ -958,6 +965,12 @@ ThinkPad indicator LED should blink in hardware accelerated mode, use the "timer" trigger, and leave the delay_on and delay_off parameters set to zero (to request hardware acceleration autodetection). +LEDs that are known not to exist in a given ThinkPad model are not +made available through the sysfs interface. If you have a dock and you +notice there are LEDs listed for your ThinkPad that do not exist (and +are not in the dock), or if you notice that there are missing LEDs, +a report to ibm-acpi-devel@lists.sourceforge.net is appreciated. + ACPI sounds -- /proc/acpi/ibm/beep ---------------------------------- @@ -1555,3 +1568,7 @@ Sysfs interface changelog: 0x020300: hotkey enable/disable support removed, attributes hotkey_bios_enabled and hotkey_enable deprecated and marked for removal. + +0x020400: Marker for 16 LEDs support. Also, LEDs that are known + to not exist in a given model are not registered with + the LED sysfs class anymore. diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index da739d5c921..06c7c03c8f2 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -22,7 +22,7 @@ */ #define TPACPI_VERSION "0.23" -#define TPACPI_SYSFS_VERSION 0x020300 +#define TPACPI_SYSFS_VERSION 0x020400 /* * Changelog: @@ -4815,7 +4815,7 @@ TPACPI_HANDLE(led, ec, "SLED", /* 570 */ "LED", /* all others */ ); /* R30, R31 */ -#define TPACPI_LED_NUMLEDS 8 +#define TPACPI_LED_NUMLEDS 16 static struct tpacpi_led_classdev *tpacpi_leds; static enum led_status_t tpacpi_led_state_cache[TPACPI_LED_NUMLEDS]; static const char * const tpacpi_led_names[TPACPI_LED_NUMLEDS] = { @@ -4828,15 +4828,20 @@ static const char * const tpacpi_led_names[TPACPI_LED_NUMLEDS] = { "tpacpi::dock_batt", "tpacpi::unknown_led", "tpacpi::standby", + "tpacpi::dock_status1", + "tpacpi::dock_status2", + "tpacpi::unknown_led2", + "tpacpi::unknown_led3", + "tpacpi::thinkvantage", }; -#define TPACPI_SAFE_LEDS 0x0081U +#define TPACPI_SAFE_LEDS 0x1081U static inline bool tpacpi_is_led_restricted(const unsigned int led) { #ifdef CONFIG_THINKPAD_ACPI_UNSAFE_LEDS return false; #else - return (TPACPI_SAFE_LEDS & (1 << led)) == 0; + return (1U & (TPACPI_SAFE_LEDS >> led)) == 0; #endif } @@ -4998,6 +5003,10 @@ static int __init tpacpi_init_led(unsigned int led) tpacpi_leds[led].led = led; + /* LEDs with no name don't get registered */ + if (!tpacpi_led_names[led]) + return 0; + tpacpi_leds[led].led_classdev.brightness_set = &led_sysfs_set; tpacpi_leds[led].led_classdev.blink_set = &led_sysfs_blink_set; if (led_supported == TPACPI_LED_570) @@ -5016,10 +5025,59 @@ static int __init tpacpi_init_led(unsigned int led) return rc; } +static const struct tpacpi_quirk led_useful_qtable[] __initconst = { + TPACPI_Q_IBM('1', 'E', 0x009f), /* A30 */ + TPACPI_Q_IBM('1', 'N', 0x009f), /* A31 */ + TPACPI_Q_IBM('1', 'G', 0x009f), /* A31 */ + + TPACPI_Q_IBM('1', 'I', 0x0097), /* T30 */ + TPACPI_Q_IBM('1', 'R', 0x0097), /* T40, T41, T42, R50, R51 */ + TPACPI_Q_IBM('7', '0', 0x0097), /* T43, R52 */ + TPACPI_Q_IBM('1', 'Y', 0x0097), /* T43 */ + TPACPI_Q_IBM('1', 'W', 0x0097), /* R50e */ + TPACPI_Q_IBM('1', 'V', 0x0097), /* R51 */ + TPACPI_Q_IBM('7', '8', 0x0097), /* R51e */ + TPACPI_Q_IBM('7', '6', 0x0097), /* R52 */ + + TPACPI_Q_IBM('1', 'K', 0x00bf), /* X30 */ + TPACPI_Q_IBM('1', 'Q', 0x00bf), /* X31, X32 */ + TPACPI_Q_IBM('1', 'U', 0x00bf), /* X40 */ + TPACPI_Q_IBM('7', '4', 0x00bf), /* X41 */ + TPACPI_Q_IBM('7', '5', 0x00bf), /* X41t */ + + TPACPI_Q_IBM('7', '9', 0x1f97), /* T60 (1) */ + TPACPI_Q_IBM('7', '7', 0x1f97), /* Z60* (1) */ + TPACPI_Q_IBM('7', 'F', 0x1f97), /* Z61* (1) */ + TPACPI_Q_IBM('7', 'B', 0x1fb7), /* X60 (1) */ + + /* (1) - may have excess leds enabled on MSB */ + + /* Defaults (order matters, keep last, don't reorder!) */ + { /* Lenovo */ + .vendor = PCI_VENDOR_ID_LENOVO, + .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY, + .quirks = 0x1fffU, + }, + { /* IBM ThinkPads with no EC version string */ + .vendor = PCI_VENDOR_ID_IBM, + .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_UNKNOWN, + .quirks = 0x00ffU, + }, + { /* IBM ThinkPads with EC version string */ + .vendor = PCI_VENDOR_ID_IBM, + .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY, + .quirks = 0x00bfU, + }, +}; + +#undef TPACPI_LEDQ_IBM +#undef TPACPI_LEDQ_LNV + static int __init led_init(struct ibm_init_struct *iibm) { unsigned int i; int rc; + unsigned long useful_leds; vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n"); @@ -5041,6 +5099,9 @@ static int __init led_init(struct ibm_init_struct *iibm) 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 1; + tpacpi_leds = kzalloc(sizeof(*tpacpi_leds) * TPACPI_LED_NUMLEDS, GFP_KERNEL); if (!tpacpi_leds) { @@ -5048,8 +5109,12 @@ static int __init led_init(struct ibm_init_struct *iibm) return -ENOMEM; } + useful_leds = tpacpi_check_quirks(led_useful_qtable, + ARRAY_SIZE(led_useful_qtable)); + for (i = 0; i < TPACPI_LED_NUMLEDS; i++) { - if (!tpacpi_is_led_restricted(i)) { + if (!tpacpi_is_led_restricted(i) && + test_bit(i, &useful_leds)) { rc = tpacpi_init_led(i); if (rc < 0) { led_exit(); @@ -5059,12 +5124,11 @@ static int __init led_init(struct ibm_init_struct *iibm) } #ifdef CONFIG_THINKPAD_ACPI_UNSAFE_LEDS - if (led_supported != TPACPI_LED_NONE) - printk(TPACPI_NOTICE - "warning: userspace override of important " - "firmware LEDs is enabled\n"); + printk(TPACPI_NOTICE + "warning: userspace override of important " + "firmware LEDs is enabled\n"); #endif - return (led_supported != TPACPI_LED_NONE)? 0 : 1; + return 0; } #define str_led_status(s) \ @@ -5094,7 +5158,7 @@ static int led_read(char *p) } len += sprintf(p + len, "commands:\t" - " on, off, blink ( is 0-7)\n"); + " on, off, blink ( is 0-15)\n"); return len; } @@ -5109,7 +5173,7 @@ static int led_write(char *buf) return -ENODEV; while ((cmd = next_cmd(&buf))) { - if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 7) + if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 15) return -EINVAL; if (strstr(cmd, "off")) { -- cgit v1.2.3-70-g09d2 From 8bf3d4c535c2b9689c2979b281c24e9f59c2f4ad Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 30 May 2009 13:25:09 -0300 Subject: thinkpad-acpi: silence bogus warning when ACPI video is disabled Make use of acpi_video_backlight_support() also in hotkey_init, to make sure this doesn't happen: thinkpad_acpi: This ThinkPad has standard ACPI backlight brightness control, supported by the ACPI video driver thinkpad_acpi: Disabling thinkpad-acpi brightness events by default... thinkpad_acpi: Standard ACPI backlight interface not available, thinkpad_acpi native brightness control enabled thinkpad_acpi: detected a 16-level brightness capable ThinkPad Note that this is purely cosmetic, there is absolutely _no_ change in behaviour. Those events are sometimes enabled at runtime by userspace, but the driver never enables them by itself unless someone messed with the default keymaps. Signed-off-by: Henrique de Moraes Holschuh Reported-by: Jochen Schulz Signed-off-by: Len Brown --- drivers/platform/x86/thinkpad_acpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/platform/x86/thinkpad_acpi.c') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 06c7c03c8f2..5a22a064222 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -2655,7 +2655,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) /* update bright_acpimode... */ tpacpi_check_std_acpi_brightness_support(); - if (tp_features.bright_acpimode) { + if (tp_features.bright_acpimode && acpi_video_backlight_support()) { printk(TPACPI_INFO "This ThinkPad has standard ACPI backlight " "brightness control, supported by the ACPI " -- cgit v1.2.3-70-g09d2 From d7880f10c5d42ba182a97c1fd41d41d0b8837097 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Thu, 18 Jun 2009 00:40:16 -0300 Subject: thinkpad-acpi: forbid the use of HBRV on Lenovo ThinkPads Forcing thinkpad-acpi to do EC-based brightness control (HBRV) on a X61 has very... interesting effects, instead of doing nothing (since it doesn't have EC-based backlight control), it causes "weirdness" in the fan tachometer readings, for example. This means the EC register that used to be HBRV has been reused by Lenovo for something else, but they didn't remove it from the DSDT. Make sure the documentation reflects this data, and forbid the user from forcing the driver to access HBRV on Lenovo ThinkPads. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/laptops/thinkpad-acpi.txt | 14 ++++++++------ drivers/platform/x86/thinkpad_acpi.c | 10 ++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) (limited to 'drivers/platform/x86/thinkpad_acpi.c') diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt index 88fc0661de5..f2ff638cce8 100644 --- a/Documentation/laptops/thinkpad-acpi.txt +++ b/Documentation/laptops/thinkpad-acpi.txt @@ -1169,17 +1169,19 @@ may not be distinct. Later Lenovo models that implement the ACPI display backlight brightness control methods have 16 levels, ranging from 0 to 15. -There are two interfaces to the firmware for direct brightness control, -EC and UCMS (or CMOS). To select which one should be used, use the -brightness_mode module parameter: brightness_mode=1 selects EC mode, -brightness_mode=2 selects UCMS mode, brightness_mode=3 selects EC -mode with NVRAM backing (so that brightness changes are remembered -across shutdown/reboot). +For IBM ThinkPads, there are two interfaces to the firmware for direct +brightness control, EC and UCMS (or CMOS). To select which one should be +used, use the brightness_mode module parameter: brightness_mode=1 selects +EC mode, brightness_mode=2 selects UCMS mode, brightness_mode=3 selects EC +mode with NVRAM backing (so that brightness changes are remembered across +shutdown/reboot). The driver tries to select which interface to use from a table of defaults for each ThinkPad model. If it makes a wrong choice, please report this as a bug, so that we can fix it. +Lenovo ThinkPads only support brightness_mode=2 (UCMS). + When display backlight brightness controls are available through the standard ACPI interface, it is best to use it instead of this direct ThinkPad-specific interface. The driver will disable its native diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 5a22a064222..c8d74dbacbb 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -5696,6 +5696,10 @@ static struct ibm_struct ecdump_driver_data = { * 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... */ enum { @@ -5996,6 +6000,12 @@ static int __init brightness_init(struct ibm_init_struct *iibm) brightness_mode); } + /* Safety */ + if (thinkpad_id.vendor != PCI_VENDOR_ID_IBM && + (brightness_mode == TPACPI_BRGHT_MODE_ECNVRAM || + brightness_mode == TPACPI_BRGHT_MODE_EC)) + return -EINVAL; + if (tpacpi_brightness_get_raw(&b) < 0) return 1; -- cgit v1.2.3-70-g09d2 From d73772474f6ebbacbe820c31c0fa1cffa7160246 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Thu, 18 Jun 2009 00:40:17 -0300 Subject: thinkpad-acpi: support the second fan on the X61 Support reading the tachometer of the auxiliary fan of a X60/X61. It was found out by sheer luck, that bit 0 of EC register 0x31 (formely HBRV) selects which fan is active for tachometer readings through EC 0x84/0x085: 0 for fan1, 1 for fan2. Many thanks to Christoph Kl??nter, to Whoopie, and to weasel, who helped confirm that behaviour. Fan control through EC HFSP applies to both fans equally, regardless of the state of bit 0 of EC 0x31. That matches the way the DSDT uses HFSP. In order to better support the secondary fan, export a second tachometer over hwmon, and add defensive measures to make sure we are reading the correct tachometer. Support for the second fan is whitelist-based, as I have not found anything obvious to look for in the DSDT to detect the presence of the second fan. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/laptops/thinkpad-acpi.txt | 10 ++- drivers/platform/x86/thinkpad_acpi.c | 122 +++++++++++++++++++++++++++++++- 2 files changed, 130 insertions(+), 2 deletions(-) (limited to 'drivers/platform/x86/thinkpad_acpi.c') diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt index f2ff638cce8..0d5e91379ae 100644 --- a/Documentation/laptops/thinkpad-acpi.txt +++ b/Documentation/laptops/thinkpad-acpi.txt @@ -1269,7 +1269,7 @@ Fan control and monitoring: fan speed, fan enable/disable procfs: /proc/acpi/ibm/fan sysfs device attributes: (hwmon "thinkpad") fan1_input, pwm1, - pwm1_enable + pwm1_enable, fan2_input sysfs hwmon driver attributes: fan_watchdog NOTE NOTE NOTE: fan control operations are disabled by default for @@ -1282,6 +1282,9 @@ from the hardware registers of the embedded controller. This is known to work on later R, T, X and Z series ThinkPads but may show a bogus value on other models. +Some Lenovo ThinkPads support a secondary fan. This fan cannot be +controlled separately, it shares the main fan control. + Fan levels: Most ThinkPad fans work in "levels" at the firmware interface. Level 0 @@ -1412,6 +1415,11 @@ hwmon device attribute fan1_input: which can take up to two minutes. May return rubbish on older ThinkPads. +hwmon device attribute fan2_input: + Fan tachometer reading, in RPM, for the secondary fan. + Available only on some ThinkPads. If the secondary fan is + not installed, will always read 0. + hwmon driver attribute fan_watchdog: Fan safety watchdog timer interval, in seconds. Minimum is 1 second, maximum is 120 seconds. 0 disables the watchdog. diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index c8d74dbacbb..27ca676a709 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -264,6 +264,7 @@ static struct { u32 wan:1; u32 uwb:1; u32 fan_ctrl_status_undef:1; + u32 second_fan:1; u32 beep_needs_two_args:1; u32 input_device_registered:1; u32 platform_drv_registered:1; @@ -6298,6 +6299,21 @@ static struct ibm_struct volume_driver_data = { * For firmware bugs, refer to: * http://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. * @@ -6324,6 +6340,8 @@ 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 */ TP_EC_FAN_FULLSPEED = 0x40, /* EC fan mode: full speed */ TP_EC_FAN_AUTO = 0x80, /* EC fan mode: auto fan control */ @@ -6417,6 +6435,38 @@ static void fan_quirk1_handle(u8 *fan_status) } } +/* Select main fan on X60/X61, NOOP on others */ +static bool fan_select_fan1(void) +{ + if (tp_features.second_fan) { + u8 val; + + if (ec_read(fan_select_offset, &val) < 0) + return false; + val &= 0xFEU; + if (ec_write(fan_select_offset, val) < 0) + return false; + } + return true; +} + +/* Select secondary fan on X60/X61 */ +static bool fan_select_fan2(void) +{ + u8 val; + + if (!tp_features.second_fan) + return false; + + if (ec_read(fan_select_offset, &val) < 0) + return false; + val |= 0x01U; + if (ec_write(fan_select_offset, val) < 0) + return false; + + return true; +} + /* * Call with fan_mutex held */ @@ -6494,6 +6544,8 @@ static int fan_get_speed(unsigned int *speed) switch (fan_status_access_mode) { case TPACPI_FAN_RD_TPEC: /* all except 570, 600e/x, 770e, 770x */ + if (unlikely(!fan_select_fan1())) + return -EIO; if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) || !acpi_ec_read(fan_rpm_offset + 1, &hi))) return -EIO; @@ -6510,6 +6562,34 @@ static int fan_get_speed(unsigned int *speed) return 0; } +static int fan2_get_speed(unsigned int *speed) +{ + u8 hi, lo; + bool rc; + + switch (fan_status_access_mode) { + case TPACPI_FAN_RD_TPEC: + /* all except 570, 600e/x, 770e, 770x */ + if (unlikely(!fan_select_fan2())) + return -EIO; + rc = !acpi_ec_read(fan_rpm_offset, &lo) || + !acpi_ec_read(fan_rpm_offset + 1, &hi); + fan_select_fan1(); /* play it safe */ + if (rc) + return -EIO; + + if (likely(speed)) + *speed = (hi << 8) | lo; + + break; + + default: + return -ENXIO; + } + + return 0; +} + static int fan_set_level(int level) { if (!fan_control_allowed) @@ -6915,6 +6995,25 @@ static struct device_attribute dev_attr_fan_fan1_input = __ATTR(fan1_input, S_IRUGO, fan_fan1_input_show, NULL); +/* sysfs fan fan2_input ------------------------------------------------ */ +static ssize_t fan_fan2_input_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res; + unsigned int speed; + + res = fan2_get_speed(&speed); + if (res < 0) + return res; + + return snprintf(buf, PAGE_SIZE, "%u\n", speed); +} + +static struct device_attribute dev_attr_fan_fan2_input = + __ATTR(fan2_input, S_IRUGO, + fan_fan2_input_show, NULL); + /* sysfs fan fan_watchdog (hwmon driver) ------------------------------- */ static ssize_t fan_fan_watchdog_show(struct device_driver *drv, char *buf) @@ -6948,6 +7047,7 @@ static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO, static struct attribute *fan_attributes[] = { &dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr, &dev_attr_fan_fan1_input.attr, + NULL, /* for fan2_input */ NULL }; @@ -6955,7 +7055,8 @@ static const struct attribute_group fan_attr_group = { .attrs = fan_attributes, }; -#define TPACPI_FAN_Q1 0x0001 +#define TPACPI_FAN_Q1 0x0001 /* Unitialized HFSP */ +#define TPACPI_FAN_2FAN 0x0002 /* EC 0x31 bit 0 selects fan2 */ #define TPACPI_FAN_QI(__id1, __id2, __quirks) \ { .vendor = PCI_VENDOR_ID_IBM, \ @@ -6963,13 +7064,21 @@ static const struct attribute_group fan_attr_group = { .ec = TPID(__id1, __id2), \ .quirks = __quirks } +#define TPACPI_FAN_QL(__id1, __id2, __quirks) \ + { .vendor = PCI_VENDOR_ID_LENOVO, \ + .bios = TPACPI_MATCH_ANY, \ + .ec = TPID(__id1, __id2), \ + .quirks = __quirks } + static const struct tpacpi_quirk fan_quirk_table[] __initconst = { TPACPI_FAN_QI('1', 'Y', TPACPI_FAN_Q1), TPACPI_FAN_QI('7', '8', TPACPI_FAN_Q1), TPACPI_FAN_QI('7', '6', TPACPI_FAN_Q1), TPACPI_FAN_QI('7', '0', TPACPI_FAN_Q1), + TPACPI_FAN_QL('7', 'M', TPACPI_FAN_2FAN), }; +#undef TPACPI_FAN_QL #undef TPACPI_FAN_QI static int __init fan_init(struct ibm_init_struct *iibm) @@ -6986,6 +7095,7 @@ static int __init fan_init(struct ibm_init_struct *iibm) fan_control_commands = 0; fan_watchdog_maxinterval = 0; tp_features.fan_ctrl_status_undef = 0; + tp_features.second_fan = 0; fan_control_desired_level = 7; TPACPI_ACPIHANDLE_INIT(fans); @@ -7006,6 +7116,11 @@ static int __init fan_init(struct ibm_init_struct *iibm) fan_status_access_mode = TPACPI_FAN_RD_TPEC; if (quirks & TPACPI_FAN_Q1) fan_quirk1_setup(); + if (quirks & TPACPI_FAN_2FAN) { + tp_features.second_fan = 1; + dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN, + "secondary fan support enabled\n"); + } } else { printk(TPACPI_ERR "ThinkPad ACPI EC access misbehaving, " @@ -7061,6 +7176,11 @@ static int __init fan_init(struct ibm_init_struct *iibm) if (fan_status_access_mode != TPACPI_FAN_NONE || fan_control_access_mode != TPACPI_FAN_WR_NONE) { + if (tp_features.second_fan) { + /* attach second fan tachometer */ + fan_attributes[ARRAY_SIZE(fan_attributes)-2] = + &dev_attr_fan_fan2_input.attr; + } rc = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj, &fan_attr_group); if (rc < 0) -- cgit v1.2.3-70-g09d2 From 06d5caf47ef4fbd9efdceae33293c42778cb7b0c Mon Sep 17 00:00:00 2001 From: Alan Jenkins Date: Tue, 16 Jun 2009 15:39:51 +0100 Subject: rfkill: don't restore software blocked state on persistent devices The setting of the "persistent" flag is also made more explicit using a new rfkill_init_sw_state() function, instead of special-casing rfkill_set_sw_state() when it is called before registration. Suspend is a bit of a corner case so we try to get away without adding another hack to rfkill-input - it's going to be removed soon. If the state does change over suspend, users will simply have to prod rfkill-input twice in order to toggle the state. Userspace policy agents will be able to implement a more consistent user experience. For example, they can avoid the above problem if they toggle devices individually. Then there would be no "global state" to get out of sync. Currently there are only two rfkill drivers with persistent soft-blocked state. thinkpad-acpi already checks the software state on resume. eeepc-laptop will require modification. Signed-off-by: Alan Jenkins CC: Marcel Holtmann Acked-by: Henrique de Moraes Holschuh Signed-off-by: John W. Linville --- drivers/platform/x86/eeepc-laptop.c | 8 ++++---- drivers/platform/x86/thinkpad_acpi.c | 14 ++++++------- include/linux/rfkill.h | 32 ++++++++++++++++++++++++----- net/rfkill/core.c | 40 ++++++++++++++++++++++-------------- 4 files changed, 63 insertions(+), 31 deletions(-) (limited to 'drivers/platform/x86/thinkpad_acpi.c') diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index 03bf522bd7a..01682eca436 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -675,8 +675,8 @@ static int eeepc_hotk_add(struct acpi_device *device) if (!ehotk->eeepc_wlan_rfkill) goto wlan_fail; - rfkill_set_sw_state(ehotk->eeepc_wlan_rfkill, - get_acpi(CM_ASL_WLAN) != 1); + rfkill_init_sw_state(ehotk->eeepc_wlan_rfkill, + get_acpi(CM_ASL_WLAN) != 1); result = rfkill_register(ehotk->eeepc_wlan_rfkill); if (result) goto wlan_fail; @@ -693,8 +693,8 @@ static int eeepc_hotk_add(struct acpi_device *device) if (!ehotk->eeepc_bluetooth_rfkill) goto bluetooth_fail; - rfkill_set_sw_state(ehotk->eeepc_bluetooth_rfkill, - get_acpi(CM_ASL_BLUETOOTH) != 1); + rfkill_init_sw_state(ehotk->eeepc_bluetooth_rfkill, + get_acpi(CM_ASL_BLUETOOTH) != 1); result = rfkill_register(ehotk->eeepc_bluetooth_rfkill); if (result) goto bluetooth_fail; diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 86e958539f4..40d64c03278 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -1163,8 +1163,8 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, { struct tpacpi_rfk *atp_rfk; int res; - bool initial_sw_state = false; - int initial_sw_status; + bool sw_state = false; + int sw_status; BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]); @@ -1185,17 +1185,17 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, atp_rfk->id = id; atp_rfk->ops = tp_rfkops; - initial_sw_status = (tp_rfkops->get_status)(); - if (initial_sw_status < 0) { + sw_status = (tp_rfkops->get_status)(); + if (sw_status < 0) { printk(TPACPI_ERR "failed to read initial state for %s, error %d\n", - name, initial_sw_status); + name, sw_status); } else { - initial_sw_state = (initial_sw_status == TPACPI_RFK_RADIO_OFF); + 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_set_sw_state(atp_rfk->rfkill, initial_sw_state); + rfkill_init_sw_state(atp_rfk->rfkill, sw_state); } } rfkill_set_hw_state(atp_rfk->rfkill, tpacpi_rfk_check_hwblock_state()); diff --git a/include/linux/rfkill.h b/include/linux/rfkill.h index 16e39c7a67f..dcac724340d 100644 --- a/include/linux/rfkill.h +++ b/include/linux/rfkill.h @@ -160,8 +160,9 @@ struct rfkill * __must_check rfkill_alloc(const char *name, * the rfkill structure. Before calling this function the driver needs * to be ready to service method calls from rfkill. * - * If the software blocked state is not set before registration, - * set_block will be called to initialize it to a default value. + * If rfkill_init_sw_state() is not called before registration, + * set_block() will be called to initialize the software blocked state + * to a default value. * * If the hardware blocked state is not set before registration, * it is assumed to be unblocked. @@ -234,9 +235,11 @@ bool __must_check rfkill_set_hw_state(struct rfkill *rfkill, bool blocked); * rfkill drivers that get events when the soft-blocked state changes * (yes, some platforms directly act on input but allow changing again) * use this function to notify the rfkill core (and through that also - * userspace) of the current state. It is not necessary to notify on - * resume; since hibernation can always change the soft-blocked state, - * the rfkill core will unconditionally restore the previous state. + * userspace) of the current state. + * + * Drivers should also call this function after resume if the state has + * been changed by the user. This only makes sense for "persistent" + * devices (see rfkill_init_sw_state()). * * This function can be called in any context, even from within rfkill * callbacks. @@ -246,6 +249,21 @@ bool __must_check rfkill_set_hw_state(struct rfkill *rfkill, bool blocked); */ bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked); +/** + * rfkill_init_sw_state - Initialize persistent software block state + * @rfkill: pointer to the rfkill class to modify. + * @state: the current software block state to set + * + * rfkill drivers that preserve their software block state over power off + * use this function to notify the rfkill core (and through that also + * userspace) of their initial state. It should only be used before + * registration. + * + * In addition, it marks the device as "persistent". Persistent devices + * are expected to preserve preserve their own state when suspended. + */ +void rfkill_init_sw_state(struct rfkill *rfkill, bool blocked); + /** * rfkill_set_states - Set the internal rfkill block states * @rfkill: pointer to the rfkill class to modify. @@ -307,6 +325,10 @@ static inline bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked) return blocked; } +static inline void rfkill_init_sw_state(struct rfkill *rfkill, bool blocked) +{ +} + static inline void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw) { } diff --git a/net/rfkill/core.c b/net/rfkill/core.c index 868d79f8ac1..dcf8df7c573 100644 --- a/net/rfkill/core.c +++ b/net/rfkill/core.c @@ -56,7 +56,6 @@ struct rfkill { u32 idx; bool registered; - bool suspended; bool persistent; const struct rfkill_ops *ops; @@ -224,7 +223,7 @@ static void rfkill_send_events(struct rfkill *rfkill, enum rfkill_operation op) static void rfkill_event(struct rfkill *rfkill) { - if (!rfkill->registered || rfkill->suspended) + if (!rfkill->registered) return; kobject_uevent(&rfkill->dev.kobj, KOBJ_CHANGE); @@ -508,19 +507,32 @@ bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked) blocked = blocked || hwblock; spin_unlock_irqrestore(&rfkill->lock, flags); - if (!rfkill->registered) { - rfkill->persistent = true; - } else { - if (prev != blocked && !hwblock) - schedule_work(&rfkill->uevent_work); + if (!rfkill->registered) + return blocked; - rfkill_led_trigger_event(rfkill); - } + if (prev != blocked && !hwblock) + schedule_work(&rfkill->uevent_work); + + rfkill_led_trigger_event(rfkill); return blocked; } EXPORT_SYMBOL(rfkill_set_sw_state); +void rfkill_init_sw_state(struct rfkill *rfkill, bool blocked) +{ + unsigned long flags; + + BUG_ON(!rfkill); + BUG_ON(rfkill->registered); + + spin_lock_irqsave(&rfkill->lock, flags); + __rfkill_set_sw_state(rfkill, blocked); + rfkill->persistent = true; + spin_unlock_irqrestore(&rfkill->lock, flags); +} +EXPORT_SYMBOL(rfkill_init_sw_state); + void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw) { unsigned long flags; @@ -718,8 +730,6 @@ static int rfkill_suspend(struct device *dev, pm_message_t state) rfkill_pause_polling(rfkill); - rfkill->suspended = true; - return 0; } @@ -728,10 +738,10 @@ static int rfkill_resume(struct device *dev) struct rfkill *rfkill = to_rfkill(dev); bool cur; - cur = !!(rfkill->state & RFKILL_BLOCK_SW); - rfkill_set_block(rfkill, cur); - - rfkill->suspended = false; + if (!rfkill->persistent) { + cur = !!(rfkill->state & RFKILL_BLOCK_SW); + rfkill_set_block(rfkill, cur); + } rfkill_resume_polling(rfkill); -- cgit v1.2.3-70-g09d2