Files
bes2600-dkms/bes2600/bes_pwr.c
T
test0r b9e340c78c bes2600: Patch G — restore SPDX identifiers + ST-Ericsson attribution
The bes2600 driver is a fork of the upstream cw1200 driver
(drivers/net/wireless/st/cw1200/, ST-Ericsson, Dmitry Tarnyagin
2010-2011).  The fork's file headers have three GPL-compliance issues:

  1. NO SPDX-License-Identifier on any of 48 source files (cw1200
     mainline has them on all 25).  kernel.org-mandated since 2017.

  2. Original "Copyright (c) 2010, ST-Ericsson" lines stripped from
     all files inherited from cw1200, replaced with
     "Copyright (c) 2010, Bestechnic" — factually impossible
     (Bestechnic did not author the 2010 work) and a GPL-2.0 §1
     attribution-preservation violation.

  3. The "GPL version 2 as published by the Free Software Foundation"
     boilerplate paragraph is redundant alongside SPDX and is the
     legacy form modern kernel sources have replaced.

This patch corrects all three for the 48 .c/.h files in bes2600/:

  - Adds `// SPDX-License-Identifier: GPL-2.0-only` (or `/* ... */`
    for headers) as line 1 of every file.
  - Restores `Copyright (c) 2010, ST-Ericsson` + `Author: Dmitry
    Tarnyagin <dmitry.tarnyagin@lockless.no>` as the FIRST copyright
    chain entry on all 22 files derived from cw1200 (bh.{c,h},
    debug.{c,h}, fwio.{c,h}, hwio.{c,h}, main.c, pm.{c,h},
    queue.{c,h}, scan.{c,h}, sta.{c,h}, txrx.{c,h}, wsm.{c,h}).
  - Keeps `Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.` as
    the SECOND chain entry where Bestechnic genuinely contributed.
  - Notes "Derived from cw1200_sdio.c" + ST-Ericsson copyright on
    bes2600_sdio.c (heavy derivation, not a literal rename).
  - Notes "Replaces hwbus.h from cw1200/" + ST-Ericsson copyright
    on sbus.h.
  - Preserves the prism54/islsm authorship chain on main.c and
    bes2600.h (Michael Wu 2006 + Jean-Baptiste Note 2004-2006).
  - Drops the GPL-2.0 boilerplate paragraph in favour of SPDX.

No code changes — only file-header comment blocks.  Module build is
unaffected (verified by header-only diff scope).

This is a prerequisite for any kernel.org submission attempt.  The
existing MODULE_LICENSE("GPL") + MODULE_AUTHOR(Tarnyagin@stericsson.com)
declarations were already present and are unchanged here; the
mismatch between MODULE_AUTHOR and the (since-corrected) per-file
copyrights is now resolved.
2026-05-19 09:06:07 +02:00

1645 lines
53 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Chip-side power state machine for BES2600
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
*
*/
#include <linux/list.h>
#include <linux/pm.h>
#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;
}