// SPDX-License-Identifier: GPL-2.0-only /* * Chip-side power state machine for BES2600 * * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. * */ #include #include #include "bes2600.h" #include "sbus.h" #include "bes_pwr.h" #include "sta.h" #include "bes_chardev.h" #include "bes_log.h" static void bes2600_add_power_delay_event(struct bes2600_pwr_t *bes_pwr, u32 event, u32 timeout); static void bes2600_dump_power_busy_event(struct bes2600_pwr_t *bes_pwr, char* location) { struct bes2600_pwr_event_t *item = NULL; char *async_dump_str = NULL; char *pending_dump_str = NULL; char *free_dump_str = NULL; int used_len = 0; bool dump_async_ok = true; bool dump_pending_ok = true; bool dump_free_ok = true; bool async_have = false; bool pending_have = false; bool free_have = false; const int single_buffer_len = 4096; const int all_bufer_len = single_buffer_len * 3; bes_devel("power busy event dump at %s\n", location); async_dump_str = kzalloc(all_bufer_len, GFP_ATOMIC); if(async_dump_str == NULL) { bes_err("%s alloc buffer failed\n", __func__); return; } used_len = snprintf(async_dump_str, single_buffer_len, "async list["); if(!list_empty(&bes_pwr->async_timeout_list)) { list_for_each_entry(item, &bes_pwr->async_timeout_list, link) { if(used_len + 10 < single_buffer_len) { async_have = true; used_len += snprintf(async_dump_str + used_len, 10, "%d(%d) ", BES_PWR_EVENT_NUMBER(item->event), (item->event >> 31)); } else { dump_async_ok = false; goto out; } } } if(used_len + 3 < single_buffer_len) { used_len = async_have ? used_len - 1 : used_len; used_len += sprintf(async_dump_str + used_len, "]\n"); } else { dump_async_ok = false; goto out; } pending_dump_str = async_dump_str + single_buffer_len; used_len = snprintf(pending_dump_str, single_buffer_len, "pending list["); if(!list_empty(&bes_pwr->pending_event_list)) { list_for_each_entry(item, &bes_pwr->pending_event_list, link) { if(used_len + 10 < single_buffer_len) { pending_have = true; used_len += snprintf(pending_dump_str + used_len, 10, "%d(%d) ", BES_PWR_EVENT_NUMBER(item->event), (item->event >> 31)); } else { dump_pending_ok = false; goto out; } } } if(used_len + 3 < single_buffer_len) { used_len = pending_have ? used_len - 1 : used_len; used_len += sprintf(pending_dump_str + used_len, "]\n"); } else { dump_pending_ok = false; goto out; } free_dump_str = pending_dump_str + single_buffer_len; used_len = snprintf(free_dump_str, single_buffer_len, "free list["); if(!list_empty(&bes_pwr->free_event_list)) { list_for_each_entry(item, &bes_pwr->free_event_list, link) { if(used_len + 10 < single_buffer_len) { free_have = true; used_len += snprintf(free_dump_str + used_len, 10, "%d(%d) ", BES_PWR_EVENT_NUMBER(item->event), (item->event >> 31)); } else { dump_free_ok = false; goto out; } } } if(used_len + 3 < single_buffer_len) { used_len = free_have ? used_len - 1 : used_len; used_len += sprintf(free_dump_str + used_len, "]\n"); } else { dump_free_ok = false; goto out; } out: if(!dump_async_ok) { bes_err("buffer is not enough for dumping async event\n"); } else { bes_devel("%s", async_dump_str); } if(!dump_pending_ok) { bes_err("buffer is not enough for dumping pending event\n"); } else { bes_devel("%s", pending_dump_str); } if(!dump_free_ok) { bes_err("buffer is not enough for dumping free event\n"); } else { bes_devel("%s", free_dump_str); } kfree(async_dump_str); } static char *bes2600_get_ps_mode_str(u8 mode) { char *ps_mode_str = NULL; ps_mode_str = (mode == WSM_PSM_ACTIVE ? "WSM_PSM_ACTIVE" : mode == WSM_PSM_PS ? "WSM_PSM_PS" : mode == WSM_PSM_FAST_PS ? "WSM_PSM_FAST_PS" : "UNKNOWN"); return ps_mode_str; } static char *bes2600_get_mac_str(char *buffer, u32 ip) { sprintf(buffer, "%d.%d.%d.%d", (ip & 0xff),((ip >> 8) & 0xff), ((ip >> 16) & 0xff), ((ip >> 24) & 0xff)); return buffer; } static char *bes2600_get_pwr_busy_event_name(struct bes2600_pwr_event_t *item) { char *name = NULL; switch(BES_PWR_EVENT_NUMBER(item->event)) { case BES_PWR_LOCK_ON_SCAN: name = "SCAN"; break; case BES_PWR_LOCK_ON_JOIN: name = "JOIN"; break; case BES_PWR_LOCK_ON_TX: name = "TX"; break; case BES_PWR_LOCK_ON_RX: name = "RX"; break; case BES_PWR_LOCK_ON_FLUSH: name = "FLUSH"; break; case BES_PWR_LOCK_ON_ROC: name = "ROC"; break; case BES_PWR_LOCK_ON_WSM_TX: name = "WSM_TX"; break; case BES_PWR_LOCK_ON_WSM_OPER: name = "WSM_OPER"; break; case BES_PWR_LOCK_ON_BSS_LOST: name = "BSS_LOST"; break; case BES_PWR_LOCK_ON_GET_IP: name = "GET_IP"; break; case BES_PWR_LOCK_ON_PS_ACTIVE: name = "PS_ACTIVE"; break; case BES_PWR_LOCK_ON_LMAC_RSP: name = "LMAC_RSP"; break; case BES_PWR_LOCK_ON_AP: name = "AP"; break; case BES_PWR_LOCK_ON_TEST_CMD: name = "TEST_CMD"; break; case BES_PWR_LOCK_ON_MUL_REQ: name = "MUL_REQ"; break; case BES_PWR_LOCK_ON_ADV_SCAN: name = "ADV_SCAN"; break; case BES_PWR_LOCK_ON_DISCON: name = "DISCON"; break; case BES_PWR_LOCK_ON_QUEUE_GC: name = "QUEUE_GC"; break; case BES_PWR_LOCK_ON_AP_LP_BAD: name = "AP_LP_BAD"; break; default: name = "UNKNOW"; break; } return name; } static unsigned long bes2600_get_pwr_busy_event_timeout(struct bes2600_pwr_event_t *item) { unsigned long timeout = 0; if(BES_PWR_IS_CONSTANT_EVENT(item->event)) return 0; if(time_after(jiffies, item->timeout)) return 0; timeout = (item->timeout >= jiffies) ? (item->timeout - jiffies) : (ULONG_MAX - jiffies + item->timeout); return timeout; } static bool bes2600_update_power_delay_events(struct bes2600_pwr_t *bes_pwr, unsigned long *timeout) { struct bes2600_pwr_event_t *item = NULL, *temp = NULL; unsigned long max_timeout = 0; bool constant_event_exist = false; /* move event from async_timeout_list to pending_event_list */ if(!list_empty(&bes_pwr->async_timeout_list)) { list_for_each_entry_safe(item, temp, &bes_pwr->async_timeout_list, link) { bes2600_add_power_delay_event(bes_pwr, item->event, item->delay); list_move_tail(&item->link, &bes_pwr->free_event_list); } } /* age power event and get max timeout */ list_for_each_entry_safe(item, temp, &bes_pwr->pending_event_list, link) { if(BES_PWR_IS_CONSTANT_EVENT(item->event)) { constant_event_exist = true; continue; } if(time_after(jiffies, item->timeout)) { bes_devel("power busy event:0x%08x timeout\n", item->event); list_move_tail(&item->link, &bes_pwr->free_event_list); } else { if(max_timeout == 0) { max_timeout = item->timeout; } else { max_timeout = time_after(item->timeout, max_timeout) ? item->timeout : max_timeout; } } } bes2600_dump_power_busy_event(bes_pwr, "refresh"); if(timeout) { *timeout = max_timeout; } return constant_event_exist; } static void bes2600_add_async_timeout_power_delay_event(struct bes2600_pwr_t *bes_pwr, u32 event, u32 timeout) { struct bes2600_pwr_event_t *item = NULL; unsigned long max_timeout = 0; bool match = false; /* check if the event is already in pending list */ if (!list_empty(&bes_pwr->async_timeout_list)) { list_for_each_entry(item, &bes_pwr->async_timeout_list, link) { if(item->event == event) { match = true; break; } } } /* update event information */ if (match && (item != NULL)) { item->delay = timeout; } else { /* delete expired event if free_event_list is empty */ if(list_empty(&bes_pwr->free_event_list)) { bes_devel("%s, update delay event\n", __func__); bes2600_update_power_delay_events(bes_pwr, &max_timeout); } /* throw out exception if free_event_list is empty */ BUG_ON(list_empty(&bes_pwr->free_event_list)); /* add event instance to pending list */ bes_devel("%s, add async event:%d(%d) timeout:%d\n", __func__, BES_PWR_EVENT_NUMBER(event), event >> 31, timeout); item = list_first_entry(&bes_pwr->free_event_list, struct bes2600_pwr_event_t, link); list_move_tail(&item->link, &bes_pwr->async_timeout_list); item->event = event; item->delay = timeout; } } static void bes2600_add_power_delay_event(struct bes2600_pwr_t *bes_pwr, u32 event, u32 timeout) { struct bes2600_pwr_event_t *item = NULL; unsigned long max_timeout = 0; bool match = false; /* check if the event is already in pending list */ if(!list_empty(&bes_pwr->pending_event_list)) { list_for_each_entry(item, &bes_pwr->pending_event_list, link) { if(item->event == event) { match = true; break; } } } /* update event or add a new event */ if(match && (item != NULL)) { /* duplicate event */ item->timeout = jiffies + (timeout * HZ + HZ * BES2600_POWER_DOWN_DELAY) / 1000; bes_devel("%s, update event:%d(%d) timeout:%d\n", __func__, BES_PWR_EVENT_NUMBER(event), event >> 31, timeout); } else { /* delete expired event if free_event_list is empty */ if(list_empty(&bes_pwr->free_event_list)) { bes_devel("%s, update delay event\n", __func__); bes2600_update_power_delay_events(bes_pwr, &max_timeout); } /* throw out exception if free_event_list is empty */ BUG_ON(list_empty(&bes_pwr->free_event_list)); /* add event instance to pending list */ bes_devel("%s, add event:%d(%d) timeout:%d\n", __func__, BES_PWR_EVENT_NUMBER(event), event >> 31, timeout); item = list_first_entry(&bes_pwr->free_event_list, struct bes2600_pwr_event_t, link); list_move_tail(&item->link, &bes_pwr->pending_event_list); item->event = event; item->timeout = jiffies + (timeout * HZ + HZ * BES2600_POWER_DOWN_DELAY) / 1000; bes2600_dump_power_busy_event(bes_pwr, "add new event"); } } static bool bes2600_del_pending_event(struct bes2600_pwr_t *bes_pwr, u32 event) { struct bes2600_pwr_event_t *item = NULL, *temp = NULL; bool matched = false; list_for_each_entry_safe(item, temp, &bes_pwr->pending_event_list, link) { if(event == item->event) { matched = true; list_move_tail(&item->link, &bes_pwr->free_event_list); bes_devel("delete pending event:%d(%d)\n", BES_PWR_EVENT_NUMBER(event), event >> 31); break; } } bes2600_dump_power_busy_event(bes_pwr, "delete"); return matched; } static void bes2600_flush_pending_events(struct bes2600_pwr_t *bes_pwr) { struct bes2600_pwr_event_t *item = NULL, *temp = NULL; list_for_each_entry_safe(item, temp, &bes_pwr->pending_event_list, link) { list_move_tail(&item->link, &bes_pwr->free_event_list); bes_devel("flush pending event:%d(%d)\n", BES_PWR_EVENT_NUMBER(item->event), item->event >> 31); } bes2600_dump_power_busy_event(bes_pwr, "flush"); } static void bes2600_trigger_power_delay_down(struct bes2600_pwr_t *bes_pwr, unsigned long max_timeout) { /* get the gap of the two timestamp */ max_timeout = (max_timeout >= jiffies) ? (max_timeout - jiffies) : (ULONG_MAX - jiffies + max_timeout); bes_devel("restart delayed work, timeout:%lu\n", max_timeout); queue_delayed_work(bes_pwr->hw_priv->workqueue, &bes_pwr->power_down_work, max_timeout); } static void bes2600_pwr_lock_tx(struct bes2600_common *hw_priv) { unsigned long flags; spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); if(!hw_priv->bes_power.pending_lock) { hw_priv->bes_power.pending_lock = true; bes_devel("bes pwr lock tx\n"); wsm_lock_tx_async(hw_priv); } spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); } static void bes2600_pwr_unlock_tx(struct bes2600_common *hw_priv) { unsigned long flags; spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); if(hw_priv->bes_power.pending_lock) { hw_priv->bes_power.pending_lock = false; bes_devel("bes pwr unlock tx\n"); wsm_unlock_tx(hw_priv); } spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); } static void bes2600_pwr_call_enter_lp_cb(struct bes2600_common *hw_priv) { struct bes2600_pwr_enter_cb_item *item = NULL; mutex_lock(&hw_priv->bes_power.pwr_cb_mutex); if (!list_empty(&hw_priv->bes_power.enter_cb_list)) { list_for_each_entry(item, &hw_priv->bes_power.enter_cb_list, link) { if(item->cb != NULL) item->cb(hw_priv); } } mutex_unlock(&hw_priv->bes_power.pwr_cb_mutex); } static void bes2600_pwr_call_exit_lp_cb(struct bes2600_common *hw_priv) { struct bes2600_pwr_exit_cb_item *item = NULL; mutex_lock(&hw_priv->bes_power.pwr_cb_mutex); if (!list_empty(&hw_priv->bes_power.exit_cb_list)) { list_for_each_entry(item, &hw_priv->bes_power.exit_cb_list, link) { if(item->cb != NULL) item->cb(hw_priv); } } mutex_unlock(&hw_priv->bes_power.pwr_cb_mutex); } static void bes2600_pwr_delete_all_cb(struct bes2600_common *hw_priv) { struct bes2600_pwr_enter_cb_item *item = NULL, *temp = NULL; struct bes2600_pwr_exit_cb_item *item1 = NULL, *temp1 = NULL; mutex_lock(&hw_priv->bes_power.pwr_cb_mutex); /* delete all cb in enter_cb_list */ list_for_each_entry_safe(item, temp, &hw_priv->bes_power.enter_cb_list, link) { list_del(&item->link); kfree(item); } /* delete all cb in exit_cb_list */ list_for_each_entry_safe(item1, temp1, &hw_priv->bes_power.exit_cb_list, link) { list_del(&item1->link); kfree(item1); } mutex_unlock(&hw_priv->bes_power.pwr_cb_mutex); } static void bes2600_pwr_device_enter_lp_mode(struct bes2600_common *hw_priv) { int ret = 0; struct wsm_operational_mode mode = { .power_mode = wsm_power_mode_quiescent, .disableMoreFlagUsage = true, }; bes_devel("host unlock lmac\n"); ret = wsm_set_operational_mode(hw_priv, &mode, 0); if (ret) bes_err("%s, set operation mode fail\n", __func__); /* wait other module to finish work */ bes2600_pwr_call_enter_lp_cb(hw_priv); if(hw_priv->sbus_ops->sbus_deactive) { ret = hw_priv->sbus_ops->sbus_deactive(hw_priv->sbus_priv, SUBSYSTEM_MCU); if (ret) bes_err("%s, deactive mcu fail\n", __func__); } if(hw_priv->sbus_ops->gpio_sleep) hw_priv->sbus_ops->gpio_sleep(hw_priv->sbus_priv, GPIO_WAKE_FLAG_MCU); bes_devel("device enter sleep\n"); } /* * Number of consecutive bes2600_pwr_enter_lp_mode timeouts (with zero * PM_INDICATIONs received) before we conclude the firmware does not * honor host-driven PSM and switch to a sticky skip path. */ #define BES2600_PM_UNSUPPORTED_THRESHOLD 3 /* * Latch pm_unsupported = true and force chip_pm_state = ACTIVE so the * c6.2 wake-side skip branch covers bes2600_pwr_device_exit_lp_mode. * Called after BES2600_PM_UNSUPPORTED_THRESHOLD consecutive enter_lp_mode * timeouts with zero PM_INDICATIONs. */ static void bes2600_pwr_latch_pm_unsupported(struct bes2600_common *hw_priv) { bes_warn("PSM not honored (%u timeouts), switching to skip mode\n", hw_priv->bes_power.pm_consecutive_timeouts); hw_priv->bes_power.pm_unsupported = true; atomic_set(&hw_priv->bes_power.chip_pm_state, BES2600_CHIP_PM_ACTIVE); /* * Hold the MCU wake-flag bit permanently. Without this, every * sdio_rx_work invocation hits bes2600_gpio_wakeup_mcu(SDIO_RX) * when gpio_wakup_flags == 0, drives the GPIO high and msleeps * 10 ms per RX. With ~50 RX/s of beacons + multicast that's * ~50%% of the bes_sdio workqueue thread blocked in msleep, * which directly caps RX throughput. Holding the MCU bit makes * those calls bit-only bookkeeping (gpio_wakeup = (flags == 0) * stays false, no GPIO toggle, no msleep). The bit is never * cleared once pm_unsupported is set because * bes2600_pwr_device_enter_lp_mode is unreachable under the * early-return. */ if (hw_priv->sbus_ops->gpio_wake) hw_priv->sbus_ops->gpio_wake(hw_priv->sbus_priv, GPIO_WAKE_FLAG_MCU); } static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv) { int i = 0; struct bes2600_vif *priv; int ret = 0; int timeouts = 0; char ip_str[20]; unsigned long status = 0; /* * Sticky early-return when we've previously concluded the firmware * doesn't honor PSM. Each attempt would otherwise burn 5s on a * doomed wait_for_completion_timeout and produce a noisy three-line * cascade in dmesg every time power_down_work retries (every * ~10s). The chip stays in active mode, which on this firmware is * the de-facto state anyway. */ if (hw_priv->bes_power.pm_unsupported) return -EOPNOTSUPP; /* set interface low power configuration */ bes2600_for_each_vif(hw_priv, priv, i) { #ifdef P2P_MULTIVIF if (i == (CW12XX_MAX_VIFS - 1)) continue; #endif if (!priv) continue; if (priv->join_status == BES2600_JOIN_STATUS_STA && priv->bss_params.aid && priv->setbssparams_done) { /* enable arp filter */ if (priv->filter4.enable) { bes_devel("%s, arp filter, enable:%d addr:%s\n", __func__, priv->filter4.enable, bes2600_get_mac_str(ip_str, priv->filter4.ipv4Address[0])); ret = wsm_set_arp_ipv4_filter(hw_priv, &priv->filter4, priv->if_id); if (ret) bes_err("%s, set arp filter failed\n", __func__); } /* skip beacon receive if applications don't have muticast service */ if(priv->join_dtim_period && !priv->has_multicast_subscription) { unsigned listen_interval = 1; if(priv->join_dtim_period >= CONFIG_BES2600_LISTEN_INTERVAL) { listen_interval = priv->join_dtim_period; } else { listen_interval = CONFIG_BES2600_LISTEN_INTERVAL / priv->join_dtim_period * priv->join_dtim_period; } ret = wsm_set_beacon_wakeup_period(hw_priv, 1, listen_interval, priv->if_id); if (ret) bes_err("%s, set wakeup period failed\n", __func__); } /* Set Enable Broadcast Address Filter */ priv->broadcast_filter.action_mode = priv->filter4.enable ? WSM_FILTER_ACTION_FILTER_OUT : WSM_FILTER_ACTION_IGNORE; if (priv->join_status == BES2600_JOIN_STATUS_AP) priv->broadcast_filter.address_mode = WSM_FILTER_ADDR_MODE_A3; ret = bes2600_set_macaddrfilter(hw_priv, priv, (u8 *)&priv->broadcast_filter); if (ret) bes_err("%s, set bc filter failed\n", __func__); /* enter low power mode */ if(!hw_priv->bes_power.ap_lp_bad) { bes_devel("%s, psMode:%s, fastPsmIdlePeriod:%d apPsmChangePeriod:%d minAutoPsPollPeriod:%d\n", __func__, bes2600_get_ps_mode_str(priv->powersave_mode.pmMode), priv->powersave_mode.fastPsmIdlePeriod, priv->powersave_mode.apPsmChangePeriod, priv->powersave_mode.minAutoPsPollPeriod); /* * Reinit BEFORE the WSM goes out, so a stale * indication from a previous cycle cannot have * primed pm_enter_cmpl. From here until the * indication callback's cmpxchg(1->0) on * pm_set_in_process, only the indication for * THIS request can complete the wait. */ reinit_completion(&hw_priv->bes_power.pm_enter_cmpl); atomic_set(&hw_priv->bes_power.pm_set_in_process, 1); ret = bes2600_set_pm(priv, &priv->powersave_mode); if (ret) { atomic_set(&hw_priv->bes_power.pm_set_in_process, 0); bes_err("%s, set operation mode fail\n", __func__); timeouts++; continue; } /* wait power save mode changed indication */ status = wait_for_completion_timeout(&hw_priv->bes_power.pm_enter_cmpl, 5 * HZ); if (!status) { /* * The indication callback only fires * complete() when it observes * pm_set_in_process == 1; cmpxchg it * to 0 here so a late indication * cannot prime the next wait. * * If we win the cmpxchg, this is a * real timeout: the firmware's PS * state is unknown to us. Mark it as * such so the next wake path can * probe before assuming the chip is * still active. * * If we lose the cmpxchg, the * indication arrived between the * wait timing out and us getting * here; treat as success. */ if (atomic_cmpxchg(&hw_priv->bes_power.pm_set_in_process, 1, 0) == 1) { bes_devel("%s, wait pm ind timeout\n", __func__); atomic_set(&hw_priv->bes_power.chip_pm_state, BES2600_CHIP_PM_UNKNOWN); timeouts++; if (++hw_priv->bes_power.pm_consecutive_timeouts >= BES2600_PM_UNSUPPORTED_THRESHOLD) bes2600_pwr_latch_pm_unsupported(hw_priv); } } } else { bes_devel("skip enter lp mode\n"); } } } /* * Enter the device-end of the LP transition only if every per-VIF * mac80211 handshake reached firmware-ACKed completion. Doing the * device-LP setup while any VIF is still pending leaves the driver * in an inconsistent state that cascades into SDIO TX errors on * the BES2600. */ if (timeouts == 0) { bes2600_pwr_device_enter_lp_mode(hw_priv); } else { /* * device_enter_lp_mode() was skipped (one or more VIFs * timed out waiting for the firmware indication) so its * gpio_sleep(MCU) - which drops the wake-flag bit and, if * no other subsystem holds the wake, drives the GPIO low - * never ran. Without it the bit stays asserted, and the * next bes2600_pwr_device_exit_lp_mode() calls * gpio_wake(MCU) into a "bit already set" no-op: the GPIO * never re-edges, sbus_active() exhausts its 200x2ms * MCU_WAKEUP_READY budget against an unwoken chip, and * the first TX after idle stalls for several seconds. * * Drop the MCU wake-flag bit explicitly here so the next * wake injects a real GPIO edge. gpio_allow_mcu_sleep * preserves multi-subsystem semantics: it only drives the * GPIO low when no other subsystem still holds wake; if * BT or another holder is keeping the chip awake, the * GPIO stays high and the bit clear here is purely * bookkeeping (so the next gpio_wake doesn't no-op). */ if (!hw_priv->bes_power.pm_unsupported && hw_priv->sbus_ops->gpio_sleep) hw_priv->sbus_ops->gpio_sleep(hw_priv->sbus_priv, GPIO_WAKE_FLAG_MCU); ret = -ETIMEDOUT; } return ret; } static void bes2600_pwr_device_exit_lp_mode(struct bes2600_common *hw_priv) { int ret = 0; enum bes2600_chip_pm_state state; struct wsm_operational_mode mode = { .power_mode = wsm_power_mode_active, .disableMoreFlagUsage = true, }; /* * Consult chip_pm_state set by bes2600_pwr_notify_ps_changed(). * If we last saw the firmware confirm ACTIVE, skip ONLY the * gpio_wake + sbus_active wake handshake - the GPIO is already * asserted high and the SDIO MCU subsystem is already running, * so another sbus_active() round-trip just hits its 200x2ms * timeout because the firmware has nothing to do. * * wsm_set_operational_mode() below is NOT part of the wake * handshake; it is the operational-mode setter the firmware * tracks per call. Skipping it leaves the chip's SDIO state * machine without a fresh operational-mode update, which on * PineTab2 wedges the bus (-EBUSY on next sdio_rx_work read) * within a few seconds of probe completion. So it must run * unconditionally. */ state = atomic_read(&hw_priv->bes_power.chip_pm_state); if (state == BES2600_CHIP_PM_ACTIVE) { bes_devel("device_exit_lp_mode: chip already ACTIVE, skipping wake handshake\n"); } else { bes_devel("host lock lmac\n"); if (hw_priv->sbus_ops->gpio_wake) hw_priv->sbus_ops->gpio_wake(hw_priv->sbus_priv, GPIO_WAKE_FLAG_MCU); if (hw_priv->sbus_ops->sbus_active) { ret = hw_priv->sbus_ops->sbus_active(hw_priv->sbus_priv, SUBSYSTEM_MCU); if (ret) { /* * MCU_WAKEUP_READY did not arrive within * the SDIO handshake window. Record state * as UNKNOWN so the next exit_lp_mode call * also runs the full wake sequence (no * skip), but still send operational_mode * below to match pre-c6 behaviour - the * WSM may succeed even if the SDIO active * confirm was lost, and if it fails too, * we just emit a second devel-level error. * Repeated UNKNOWN is the signal for the * LMAC active-monitor to eventually * escalate to bus_reset (c5.2's * mmc_hw_reset path). */ bes_err("%s, active mcu fail\n", __func__); atomic_set(&hw_priv->bes_power.chip_pm_state, BES2600_CHIP_PM_UNKNOWN); } } } ret = wsm_set_operational_mode(hw_priv, &mode, 0); if (ret) bes_err("%s, set operation mode fail\n", __func__); bes_devel("device exit sleep\n"); } static int bes2600_pwr_exit_lp_mode(struct bes2600_common *hw_priv) { int i = 0, ret = 0; struct bes2600_vif *priv; struct wsm_arp_ipv4_filter filter; struct wsm_set_pm pm; char ip_str[20]; /* set device low power configuration */ bes2600_pwr_device_exit_lp_mode(hw_priv); /* set interface low power configutation */ bes2600_for_each_vif(hw_priv, priv, i) { #ifdef P2P_MULTIVIF if (i == (CW12XX_MAX_VIFS - 1)) continue; #endif if (!priv) continue; if (priv->join_status == BES2600_JOIN_STATUS_STA && priv->bss_params.aid && priv->setbssparams_done) { /* disable arp filter */ if (priv->filter4.enable) { filter = priv->filter4; filter.enable = false; bes_devel("%s, arp filter, enable:%d addr:%s\n", __func__, filter.enable, bes2600_get_mac_str(ip_str, filter.ipv4Address[0])); ret = wsm_set_arp_ipv4_filter(hw_priv, &filter, priv->if_id); if (ret) bes_err("%s, set arp filter failed\n", __func__); } /* set wakeup perioid */ wsm_set_beacon_wakeup_period(hw_priv, priv->join_dtim_period, 0, priv->if_id); if (ret) bes_err("%s, set wakeup period failed\n", __func__); /* Set Enable Broadcast Address Filter */ priv->broadcast_filter.action_mode = WSM_FILTER_ACTION_IGNORE; if (priv->join_status == BES2600_JOIN_STATUS_AP) priv->broadcast_filter.address_mode = WSM_FILTER_ADDR_MODE_NONE; bes2600_set_macaddrfilter(hw_priv, priv, (u8 *)&priv->broadcast_filter); if (ret) bes_err("%s, set bc filter failed\n", __func__); /* exit low power mode */ if(!hw_priv->bes_power.ap_lp_bad) { pm = priv->powersave_mode; pm.pmMode = WSM_PSM_ACTIVE; bes_devel("%s, psMode:%s, fastPsmIdlePeriod:%d apPsmChangePeriod:%d minAutoPsPollPeriod:%d\n", __func__, bes2600_get_ps_mode_str(pm.pmMode), pm.fastPsmIdlePeriod, pm.apPsmChangePeriod, pm.minAutoPsPollPeriod); ret = bes2600_set_pm(priv, &pm); if (ret) bes_err("%s, set operation mode fail\n", __func__); } else { bes_devel("skip exit lp mode\n"); } } } /* call all exit lower power callback */ bes2600_pwr_call_exit_lp_cb(hw_priv); return ret; } static void bes2600_pwr_unlock_device(struct bes2600_common *hw_priv) { unsigned long flags; /* set device to low power mode */ mutex_lock(&hw_priv->bes_power.pwr_mutex); bes2600_pwr_lock_tx(hw_priv); spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); if(hw_priv->bes_power.power_state == POWER_DOWN_STATE_LOCKED) { hw_priv->bes_power.power_state = POWER_DOWN_STATE_UNLOCKING; hw_priv->bes_power.power_down_task = current; spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); bes2600_pwr_enter_lp_mode(hw_priv); spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); hw_priv->bes_power.power_down_task = NULL; hw_priv->bes_power.power_state = POWER_DOWN_STATE_UNLOCKED; spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); } else { spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); } mutex_unlock(&hw_priv->bes_power.pwr_mutex); /* allow system to enter suspend mode */ // pm_relax(hw_priv->pdev); } static void bes2600_pwr_lock_device(struct bes2600_common *hw_priv) { unsigned long flags; /* prevent system from entering suspend mode */ // pm_stay_awake(hw_priv->pdev); /* wakeup device from low power mode */ mutex_lock(&hw_priv->bes_power.pwr_mutex); spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); if(hw_priv->bes_power.power_state == POWER_DOWN_STATE_UNLOCKED) { hw_priv->bes_power.power_state = POWER_DOWN_STATE_LOCKING; hw_priv->bes_power.power_up_task = current; spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); bes2600_pwr_exit_lp_mode(hw_priv); spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); hw_priv->bes_power.power_up_task = NULL; hw_priv->bes_power.power_state = POWER_DOWN_STATE_LOCKED; spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); } else { spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); } bes2600_pwr_unlock_tx(hw_priv); mutex_unlock(&hw_priv->bes_power.pwr_mutex); } static void bes2600_pwr_trigger_delayed_work(struct bes2600_common *hw_priv) { unsigned long max_timeout = 0; bool constant_event_exist = false; unsigned long flags; spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); constant_event_exist = bes2600_update_power_delay_events(&hw_priv->bes_power, &max_timeout); if(!constant_event_exist && max_timeout > 0) { bes2600_trigger_power_delay_down(&hw_priv->bes_power, max_timeout); } spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); } static void bes2600_power_down_work(struct work_struct *work) { struct bes2600_common *hw_priv = container_of(work, struct bes2600_common, bes_power.power_down_work.work); unsigned long max_timeout = 0; bool constant_event_exist = false; unsigned long flags; spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); constant_event_exist = bes2600_update_power_delay_events(&hw_priv->bes_power, &max_timeout); spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); if(!constant_event_exist && max_timeout == 0) { /* no pending event, unlock device */ bes_devel("%s no pending event\n", __func__); bes2600_pwr_unlock_device(hw_priv); wake_up(&hw_priv->bes_power.dev_lp_wq); } else { /* have power busy event, lock device */ bes_devel("%s have pending event\n", __func__); bes2600_pwr_lock_device(hw_priv); /* only have delayed power busy event, restart delayed work */ if(!constant_event_exist && max_timeout > 0) { bes_devel("%s restart delayed work\n", __func__); spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); bes2600_trigger_power_delay_down(&hw_priv->bes_power, max_timeout); spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); } } } static void bes2600_power_async_work(struct work_struct *work) { struct bes2600_common *hw_priv = container_of(work, struct bes2600_common, bes_power.power_async_work); bes2600_power_down_work(&hw_priv->bes_power.power_down_work.work); } static void bes2600_power_mcu_down_work(struct work_struct *work) { struct bes2600_common *hw_priv = container_of(work, struct bes2600_common, bes_power.power_mcu_down_work); unsigned long max_timeout = 0; bool constant_event_exist = false; unsigned long flags; enum power_down_state power_state; int ret = 0; spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); constant_event_exist = bes2600_update_power_delay_events(&hw_priv->bes_power, &max_timeout); power_state = hw_priv->bes_power.power_state; spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); if(!constant_event_exist && max_timeout == 0 && power_state == POWER_DOWN_STATE_UNLOCKED) { bes_devel("mcu sleep directly"); mutex_lock(&hw_priv->bes_power.pwr_mutex); if(hw_priv->sbus_ops->gpio_wake) hw_priv->sbus_ops->gpio_wake(hw_priv->sbus_priv, GPIO_WAKE_FLAG_MCU); if(hw_priv->sbus_ops->sbus_active) { ret = hw_priv->sbus_ops->sbus_active(hw_priv->sbus_priv, SUBSYSTEM_MCU); if (ret) bes_err("%s, active mcu fail\n", __func__); } if(hw_priv->sbus_ops->sbus_deactive) { ret = hw_priv->sbus_ops->sbus_deactive(hw_priv->sbus_priv, SUBSYSTEM_MCU); if (ret) bes_err("%s, deactive mcu fail\n", __func__); } if(hw_priv->sbus_ops->gpio_sleep) hw_priv->sbus_ops->gpio_sleep(hw_priv->sbus_priv, GPIO_WAKE_FLAG_MCU); mutex_unlock(&hw_priv->bes_power.pwr_mutex); } } void bes2600_pwr_init(struct bes2600_common *hw_priv) { int i = 0; hw_priv->bes_power.power_state = POWER_DOWN_STATE_UNLOCKED; hw_priv->bes_power.hw_priv = hw_priv; spin_lock_init(&hw_priv->bes_power.pwr_lock); INIT_DELAYED_WORK(&hw_priv->bes_power.power_down_work, bes2600_power_down_work); INIT_WORK(&hw_priv->bes_power.power_async_work, bes2600_power_async_work); INIT_WORK(&hw_priv->bes_power.power_mcu_down_work, bes2600_power_mcu_down_work); INIT_LIST_HEAD(&hw_priv->bes_power.async_timeout_list); INIT_LIST_HEAD(&hw_priv->bes_power.pending_event_list); INIT_LIST_HEAD(&hw_priv->bes_power.free_event_list); mutex_init(&hw_priv->bes_power.pwr_cb_mutex); INIT_LIST_HEAD(&hw_priv->bes_power.enter_cb_list); INIT_LIST_HEAD(&hw_priv->bes_power.exit_cb_list); hw_priv->bes_power.pending_lock = false; hw_priv->bes_power.power_down_task = NULL; hw_priv->bes_power.power_up_task = NULL; mutex_init(&hw_priv->bes_power.pwr_mutex); atomic_set(&hw_priv->bes_power.dev_state, 0); atomic_set(&hw_priv->bes_power.chip_pm_state, BES2600_CHIP_PM_UNKNOWN); hw_priv->bes_power.pm_unsupported = false; hw_priv->bes_power.pm_consecutive_timeouts = 0; init_completion(&hw_priv->bes_power.pm_enter_cmpl); sema_init(&hw_priv->bes_power.sync_lock, 1); device_set_wakeup_capable(hw_priv->pdev, true); init_waitqueue_head(&hw_priv->bes_power.dev_lp_wq); for(i = 0; i < BES2600_DELAY_EVENT_NUM; i++) { hw_priv->bes_power.pwr_events[i].idx = i; list_add_tail(&hw_priv->bes_power.pwr_events[i].link, &hw_priv->bes_power.free_event_list); } } void bes2600_pwr_exit(struct bes2600_common *hw_priv) { bes2600_pwr_delete_all_cb(hw_priv); } void bes2600_pwr_prepare(struct bes2600_common *hw_priv) { /* wait stop operation end */ down(&hw_priv->bes_power.sync_lock); } void bes2600_pwr_complete(struct bes2600_common *hw_priv) { /* notify stop operation end */ up(&hw_priv->bes_power.sync_lock); } void bes2600_pwr_start(struct bes2600_common *hw_priv) { unsigned long flags; if (!bes2600_chrdev_is_signal_mode()) return ; /* start power management state machine */ atomic_set(&hw_priv->bes_power.dev_state, 1); bes_devel("start power management.\n"); /* enable device wakeup function */ device_wakeup_enable(hw_priv->pdev); /* set power_state to busy and clear state */ spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); hw_priv->bes_power.power_state = POWER_DOWN_STATE_LOCKED; hw_priv->bes_power.power_down_task = NULL; hw_priv->bes_power.power_up_task = NULL; hw_priv->bes_power.sys_suspend_task = NULL; hw_priv->bes_power.sys_resume_task = NULL; hw_priv->bes_power.pending_lock = false; hw_priv->bes_power.ap_lp_bad = false; spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); /* set gpio and prevent device from entering sleep mode */ if(hw_priv->sbus_ops->gpio_wake) hw_priv->sbus_ops->gpio_wake(hw_priv->sbus_priv, GPIO_WAKE_FLAG_MCU); /* start idle timer */ queue_delayed_work(hw_priv->workqueue, &hw_priv->bes_power.power_down_work, (HZ * BES2600_POWER_DOWN_DELAY) / 1000); } void bes2600_pwr_stop(struct bes2600_common *hw_priv) { unsigned long flags; unsigned long max_timeout; if (!bes2600_chrdev_is_signal_mode()) return ; /* stop power management state machine */ atomic_set(&hw_priv->bes_power.dev_state, 0); bes_devel("stop power management.\n"); /* cancel pending work */ cancel_delayed_work_sync(&hw_priv->bes_power.power_down_work); flush_delayed_work(&hw_priv->bes_power.power_down_work); cancel_work_sync(&hw_priv->bes_power.power_async_work); flush_work(&hw_priv->bes_power.power_async_work); cancel_work_sync(&hw_priv->bes_power.power_mcu_down_work); flush_work(&hw_priv->bes_power.power_mcu_down_work); /* delete all pending event and clear state */ spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); bes2600_update_power_delay_events(&hw_priv->bes_power, &max_timeout); bes2600_flush_pending_events(&hw_priv->bes_power); spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); /* allow mcu sleep */ bes2600_power_down_work(&hw_priv->bes_power.power_down_work.work); if (hw_priv->bes_power.power_state != POWER_DOWN_STATE_UNLOCKED) bes_warn("power state is not unlocked when stop.\n"); /* unlock tx if tx is locked */ bes2600_pwr_unlock_tx(hw_priv); /* disable device wakeup function */ device_wakeup_disable(hw_priv->pdev); } bool bes2600_pwr_constant_event_is_pending(struct bes2600_common *hw_priv, u32 event) { unsigned long max_timeout = 0; bool constant_event_exist = false; unsigned long flags; bool pending = false; struct bes2600_pwr_event_t *item = NULL; spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); constant_event_exist = bes2600_update_power_delay_events(&hw_priv->bes_power, &max_timeout); if(constant_event_exist) { BES_PWR_EVENT_SET_CONSTANT(event); if(!list_empty(&hw_priv->bes_power.pending_event_list)) { list_for_each_entry(item, &hw_priv->bes_power.pending_event_list, link) { if(item->event == event) { pending = true; break; } } } } spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); return pending; } int bes2600_pwr_set_busy_event(struct bes2600_common *hw_priv, u32 event) { int ret = 0; bool need_lock = false; bool need_wait = false; unsigned long flags; if(atomic_read(&hw_priv->bes_power.dev_state) == 0) { return -1; } spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); /* don't set busy event if the command is for unlocking device */ if((event == BES_PWR_LOCK_ON_WSM_TX) && (hw_priv->bes_power.power_state == POWER_DOWN_STATE_UNLOCKING)) { if(hw_priv->bes_power.power_down_task == current) { /* BES_PWR_LOCK_ON_WSM_TX is from power down work */ spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); return 0; } } /* don't set busy event also if the command is for locking device */ if((event == BES_PWR_LOCK_ON_WSM_TX) && (hw_priv->bes_power.power_state == POWER_DOWN_STATE_LOCKING)) { if(hw_priv->bes_power.power_up_task == current) { /* BES_PWR_LOCK_ON_WSM_TX is from power down work */ spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); return 0; } } /* don't set busy event if the command is for suspend/resume */ if((event == BES_PWR_LOCK_ON_WSM_TX) && (hw_priv->bes_power.power_state == POWER_DOWN_STATE_UNLOCKED)) { if(hw_priv->bes_power.sys_suspend_task == current || hw_priv->bes_power.sys_resume_task == current) { /* BES_PWR_LOCK_ON_WSM_TX is from suspend/resume work */ spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); return 0; } } /* set busy event to pending_event_list */ BES_PWR_EVENT_SET_CONSTANT(event); bes2600_add_power_delay_event(&hw_priv->bes_power, event, 0); /* execute lock device operation or wait lock operation finish */ if((hw_priv->bes_power.power_state == POWER_DOWN_STATE_UNLOCKED) || (hw_priv->bes_power.power_state == POWER_DOWN_STATE_UNLOCKING)) { need_lock = true; } else if(hw_priv->bes_power.power_state == POWER_DOWN_STATE_LOCKING && (hw_priv->bes_power.power_up_task != current)) { need_wait = true; } spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); if(need_wait) { /* lock device is doing, wait operation done */ bes_devel("%s wait lock device done, event:%d\n", __func__, BES_PWR_EVENT_NUMBER(event)); mutex_lock(&hw_priv->bes_power.pwr_mutex); mutex_unlock(&hw_priv->bes_power.pwr_mutex); } if(need_lock) { /* cancel delayed work */ cancel_delayed_work_sync(&hw_priv->bes_power.power_down_work); flush_delayed_work(&hw_priv->bes_power.power_down_work); bes_devel("%s lock device by event:%d\n", __func__, BES_PWR_EVENT_NUMBER(event)); bes2600_pwr_lock_device(hw_priv); } return ret; } int bes2600_pwr_set_busy_event_async(struct bes2600_common *hw_priv, u32 event) { bool need_lock = false; unsigned long flags; if(atomic_read(&hw_priv->bes_power.dev_state) == 0) { return -1; } spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); BES_PWR_EVENT_SET_CONSTANT(event); bes2600_add_power_delay_event(&hw_priv->bes_power, event, 0); if(((hw_priv->bes_power.power_state == POWER_DOWN_STATE_UNLOCKED) || (hw_priv->bes_power.power_state == POWER_DOWN_STATE_UNLOCKING))) { need_lock = true; } spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); if(need_lock && !work_pending(&hw_priv->bes_power.power_async_work)) { bes_devel("%s lock device by event:%d\n", __func__, BES_PWR_EVENT_NUMBER(event)); queue_work(hw_priv->workqueue, &hw_priv->bes_power.power_async_work); } return 0; } int bes2600_pwr_set_busy_event_with_timeout(struct bes2600_common *hw_priv, u32 event, u32 timeout) { int ret = 0; bool need_lock = false; bool need_wait = false; unsigned long flags; if(atomic_read(&hw_priv->bes_power.dev_state) == 0) { return -1; } spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); /* add delayed event to pending_event_list */ bes2600_add_power_delay_event(&hw_priv->bes_power, event, timeout); /* execute lock device operation or wait lock operation finish */ if((hw_priv->bes_power.power_state == POWER_DOWN_STATE_UNLOCKED) || (hw_priv->bes_power.power_state == POWER_DOWN_STATE_UNLOCKING)) { need_lock = true; } else if(hw_priv->bes_power.power_state == POWER_DOWN_STATE_LOCKING && (hw_priv->bes_power.power_up_task != current)) { need_wait = true; } spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); if(need_wait) { /* lock device is doing, wait operation done */ bes_devel("%s wait lock device done, event:%d\n", __func__, event); mutex_lock(&hw_priv->bes_power.pwr_mutex); mutex_unlock(&hw_priv->bes_power.pwr_mutex); } if(need_lock) { /* cancel delayed work */ bes_devel("%s lock device by event:%d\n", __func__, event); cancel_delayed_work_sync(&hw_priv->bes_power.power_down_work); flush_delayed_work(&hw_priv->bes_power.power_down_work); bes2600_pwr_lock_device(hw_priv); } if(!delayed_work_pending(&hw_priv->bes_power.power_down_work)) { bes2600_pwr_trigger_delayed_work(hw_priv); } return ret; } int bes2600_pwr_set_busy_event_with_timeout_async(struct bes2600_common *hw_priv, u32 event, u32 timeout) { int ret = 0; bool need_lock = false; unsigned long flags; if(atomic_read(&hw_priv->bes_power.dev_state) == 0) { return -1; } spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); if ((hw_priv->bes_power.power_state == POWER_DOWN_STATE_UNLOCKED) || (hw_priv->bes_power.power_state == POWER_DOWN_STATE_LOCKED)) { bes2600_add_power_delay_event(&hw_priv->bes_power, event, timeout); if(hw_priv->bes_power.power_state == POWER_DOWN_STATE_UNLOCKED) { need_lock = true; } } else if ((hw_priv->bes_power.power_state == POWER_DOWN_STATE_LOCKING) || (hw_priv->bes_power.power_state == POWER_DOWN_STATE_UNLOCKING)) { bes2600_add_async_timeout_power_delay_event(&hw_priv->bes_power, event, timeout); if(hw_priv->bes_power.power_state == POWER_DOWN_STATE_UNLOCKING) { need_lock = true; } } spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); if(need_lock && !work_pending(&hw_priv->bes_power.power_async_work)) { bes_devel("%s lock device by event:%d\n", __func__, event); queue_work(hw_priv->workqueue, &hw_priv->bes_power.power_async_work); } return ret; } int bes2600_pwr_clear_busy_event(struct bes2600_common *hw_priv, u32 event) { int ret = 0; u32 constant_event = event; unsigned long flags; if(atomic_read(&hw_priv->bes_power.dev_state) == 0) { return -1; } spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); /* don't need to clear busy event if the command is for unlocking device */ if((event == BES_PWR_LOCK_ON_WSM_TX) && (hw_priv->bes_power.power_state == POWER_DOWN_STATE_UNLOCKING)) { if(hw_priv->bes_power.power_down_task == current) { /* BES_PWR_LOCK_ON_WSM_TX is from power down work */ spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); return 0; } } /* don't need also to clear busy event if the command is for locking device */ if((event == BES_PWR_LOCK_ON_WSM_TX) && (hw_priv->bes_power.power_state == POWER_DOWN_STATE_LOCKING)) { if(hw_priv->bes_power.power_up_task == current) { /* BES_PWR_LOCK_ON_WSM_TX is for powering up operation */ spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); return 0; } } /* don't set busy event if the command is for suspend/resume */ if((event == BES_PWR_LOCK_ON_WSM_TX) && (hw_priv->bes_power.power_state == POWER_DOWN_STATE_UNLOCKED)) { if(hw_priv->bes_power.sys_suspend_task == current || hw_priv->bes_power.sys_resume_task == current) { /* BES_PWR_LOCK_ON_WSM_TX is from suspend/resume work */ spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); return 0; } } /* change constant event to delay event */ BES_PWR_EVENT_SET_CONSTANT(constant_event); if(bes2600_del_pending_event(&hw_priv->bes_power, constant_event)) { bes2600_add_power_delay_event(&hw_priv->bes_power, event, 0); } spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); /* trigger power down delayed work */ if(!delayed_work_pending(&hw_priv->bes_power.power_down_work)) { bes_devel("%s restart delayed work\n", __func__); bes2600_pwr_trigger_delayed_work(hw_priv); } return ret; } void bes2600_pwr_notify_ps_changed(struct bes2600_common *hw_priv, u8 psmode) { /* * The firmware sends a PM-changed indication for every transition, * including ones we didn't ask for (firmware-internal coex moves, * idle-driven aging). Update chip_pm_state unconditionally so the * wake path can use it, but only fire pm_enter_cmpl when a host- * initiated set_pm is actually in flight - otherwise a stale * indication can prime a future wait against a freshly * reinit_completion()'ed state. */ /* * Any PM indication, whatever its psmode, proves the firmware is * actually emitting them. Reset the consecutive-timeout counter * so a transient stall doesn't permanently disable PSM, and clear * pm_unsupported if a previous run had latched it. */ hw_priv->bes_power.pm_consecutive_timeouts = 0; if (hw_priv->bes_power.pm_unsupported) { bes_warn("PM indication arrived after pm_unsupported was set; re-enabling PSM transitions\n"); hw_priv->bes_power.pm_unsupported = false; } if ((psmode & 0x01) != WSM_PSM_ACTIVE) { atomic_set(&hw_priv->bes_power.chip_pm_state, BES2600_CHIP_PM_LP); if (atomic_cmpxchg(&hw_priv->bes_power.pm_set_in_process, 1, 0) == 1) { bes_devel("complete pm_enter_cmpl\n"); complete(&hw_priv->bes_power.pm_enter_cmpl); } else { bes_devel("PM ind (LP) without pending wait; state recorded\n"); } } else { atomic_set(&hw_priv->bes_power.chip_pm_state, BES2600_CHIP_PM_ACTIVE); } } bool bes2600_pwr_device_is_idle(struct bes2600_common *hw_priv) { bool idle = false; unsigned long flags; if (!bes2600_chrdev_is_signal_mode()) return false; spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); idle = (hw_priv->bes_power.power_state == POWER_DOWN_STATE_UNLOCKED); spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); return idle; } void bes2600_pwr_register_en_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb) { struct bes2600_pwr_enter_cb_item *item = NULL; mutex_lock(&hw_priv->bes_power.pwr_cb_mutex); list_for_each_entry(item, &hw_priv->bes_power.enter_cb_list, link) { if(item->cb == cb) { bes_warn("the enter cb is already exist\n"); mutex_unlock(&hw_priv->bes_power.pwr_cb_mutex); return; } } item = kzalloc(sizeof(struct bes2600_pwr_enter_cb_item), GFP_KERNEL); if(item) { item->cb = cb; list_add_tail(&item->link, &hw_priv->bes_power.enter_cb_list); } mutex_unlock(&hw_priv->bes_power.pwr_cb_mutex); if (item == NULL) bes_err("register en_lp_cb fail.\n"); } void bes2600_pwr_unregister_en_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb) { struct bes2600_pwr_enter_cb_item *item = NULL, *temp = NULL; mutex_lock(&hw_priv->bes_power.pwr_cb_mutex); list_for_each_entry_safe(item, temp, &hw_priv->bes_power.enter_cb_list, link) { if(cb == item->cb) { list_del(&item->link); kfree(item); break; } } mutex_unlock(&hw_priv->bes_power.pwr_cb_mutex); } void bes2600_pwr_register_exit_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb) { struct bes2600_pwr_exit_cb_item *item = NULL; mutex_lock(&hw_priv->bes_power.pwr_cb_mutex); list_for_each_entry(item, &hw_priv->bes_power.exit_cb_list, link) { if(item->cb == cb) { mutex_unlock(&hw_priv->bes_power.pwr_cb_mutex); bes_warn("the exit cb is already exist\n"); return; } } item = kzalloc(sizeof(struct bes2600_pwr_exit_cb_item), GFP_KERNEL); if(item) { item->cb = cb; list_add_tail(&item->link, &hw_priv->bes_power.exit_cb_list); } mutex_unlock(&hw_priv->bes_power.pwr_cb_mutex); if (item == NULL) bes_err("register en_lp_cb fail.\n"); } void bes2600_pwr_unregister_exit_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb) { struct bes2600_pwr_exit_cb_item *item = NULL, *temp = NULL; mutex_lock(&hw_priv->bes_power.pwr_cb_mutex); list_for_each_entry_safe(item, temp, &hw_priv->bes_power.exit_cb_list, link) { if(cb == item->cb) { list_del(&item->link); kfree(item); break; } } mutex_unlock(&hw_priv->bes_power.pwr_cb_mutex); } void bes2600_pwr_suspend_start(struct bes2600_common *hw_priv) { if(atomic_read(&hw_priv->bes_power.dev_state) == 0) { return; } mutex_lock(&hw_priv->bes_power.pwr_mutex); hw_priv->bes_power.sys_suspend_task = current; bes2600_pwr_device_exit_lp_mode(hw_priv); mutex_unlock(&hw_priv->bes_power.pwr_mutex); } void bes2600_pwr_suspend_end(struct bes2600_common *hw_priv) { unsigned long max_timeout = 0; bool constant_event_exist = false; unsigned long flags; if(atomic_read(&hw_priv->bes_power.dev_state) == 0) { return; } spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); constant_event_exist = bes2600_update_power_delay_events(&hw_priv->bes_power, &max_timeout); spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); mutex_lock(&hw_priv->bes_power.pwr_mutex); if(!constant_event_exist && max_timeout == 0) bes2600_pwr_device_enter_lp_mode(hw_priv); hw_priv->bes_power.sys_suspend_task = NULL; mutex_unlock(&hw_priv->bes_power.pwr_mutex); } void bes2600_pwr_resume_start(struct bes2600_common *hw_priv) { if(atomic_read(&hw_priv->bes_power.dev_state) == 0) { return; } mutex_lock(&hw_priv->bes_power.pwr_mutex); hw_priv->bes_power.sys_resume_task = current; bes2600_pwr_device_exit_lp_mode(hw_priv); mutex_unlock(&hw_priv->bes_power.pwr_mutex); } void bes2600_pwr_resume_end(struct bes2600_common *hw_priv) { unsigned long max_timeout = 0; bool constant_event_exist = false; unsigned long flags; if(atomic_read(&hw_priv->bes_power.dev_state) == 0) { return ; } spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); constant_event_exist = bes2600_update_power_delay_events(&hw_priv->bes_power, &max_timeout); spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); mutex_lock(&hw_priv->bes_power.pwr_mutex); if(!constant_event_exist && max_timeout == 0) bes2600_pwr_device_enter_lp_mode(hw_priv); hw_priv->bes_power.sys_resume_task = NULL; mutex_unlock(&hw_priv->bes_power.pwr_mutex); } void bes2600_pwr_mcu_sleep_directly(struct bes2600_common *hw_priv) { if(atomic_read(&hw_priv->bes_power.dev_state) == 0) { return; } if (bes2600_pwr_device_is_idle(hw_priv)) { if(!work_pending(&hw_priv->bes_power.power_mcu_down_work)) { queue_work(hw_priv->workqueue, &hw_priv->bes_power.power_mcu_down_work); } } } void bes2600_pwr_mark_ap_lp_bad(struct bes2600_common *hw_priv) { if(atomic_read(&hw_priv->bes_power.dev_state) == 0) { return; } bes2600_pwr_set_busy_event_with_timeout(hw_priv, BES_PWR_LOCK_ON_AP_LP_BAD, 100); hw_priv->bes_power.ap_lp_bad = true; bes_devel("mark ap_lp_bad flag\n"); } void bes2600_pwr_clear_ap_lp_bad_mark(struct bes2600_common *hw_priv) { hw_priv->bes_power.ap_lp_bad = false; bes_devel("clear ap_lp_bad flag\n"); } int bes2600_pwr_busy_event_dump(struct bes2600_common *hw_priv, char *buffer, u32 buf_len) { unsigned long max_timeout = 0; int used_len = 0; struct bes2600_pwr_event_t *item = NULL; unsigned long flags; if(!buffer) { return -1; } spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); bes2600_update_power_delay_events(&hw_priv->bes_power, &max_timeout); used_len = snprintf(buffer, buf_len, "Event \t\tFlag\t\ttimeout(ticks)\n"); if(!list_empty(&hw_priv->bes_power.pending_event_list)) { list_for_each_entry(item, &hw_priv->bes_power.pending_event_list, link) { used_len += snprintf(buffer + used_len, buf_len - used_len, "%9s\t\t%s\t\t%lu\n", bes2600_get_pwr_busy_event_name(item), BES_PWR_IS_CONSTANT_EVENT(item->event) ? "C" : "D", bes2600_get_pwr_busy_event_timeout(item)); } } spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); return 0; } int bes2600_pwr_busy_event_record(struct bes2600_common *hw_priv, char *buffer, u32 buf_len) { unsigned long max_timeout = 0; struct bes2600_pwr_event_t *item = NULL; unsigned long flags; if(!buffer) { return -1; } spin_lock_irqsave(&hw_priv->bes_power.pwr_lock, flags); bes2600_update_power_delay_events(&hw_priv->bes_power, &max_timeout); if(!list_empty(&hw_priv->bes_power.pending_event_list)) { list_for_each_entry(item, &hw_priv->bes_power.pending_event_list, link) { snprintf(buffer, buf_len, "Event: %9s. Flag: %s. timeout(ticks): %lu.\n", bes2600_get_pwr_busy_event_name(item), BES_PWR_IS_CONSTANT_EVENT(item->event) ? "C" : "D", bes2600_get_pwr_busy_event_timeout(item)); } } spin_unlock_irqrestore(&hw_priv->bes_power.pwr_lock, flags); return 0; }