983bd62dd0
The c6 series fixed several host-side bookkeeping bugs around PSM
transitions, but didn't address the underlying contract: this chip's
firmware (BES2600 with the Bestechnic Dec 2023 build that ships on
PineTab2 and most danctnix images) silently drops every WSM_set_pm
request without emitting the corresponding PM_INDICATION. The driver's
own power_down_work delayed work calls bes2600_pwr_enter_lp_mode every
~10s; without firmware acknowledgment each call burns 5s on
wait_for_completion_timeout(pm_enter_cmpl, 5*HZ) and produces a
recurring three-line cascade in dmesg:
bes2600_pwr_enter_lp_mode, wait pm ind timeout
bes2600_sdio_active failed, subsys:0
bes2600_pwr_device_exit_lp_mode, active mcu fail
Confirmed by tripwire instrumentation on PineTab2 (linux-pinetab2
6.19.10-danctnix1, ohm) running the c5+c6 stack: zero
wsm_set_pm_indication() invocations across an entire boot, while
bes2600_pwr_enter_lp_mode timed out repeatedly, and
bes2600_sdio_active() consistently saw BES_SLAVE_STATUS_REG_ID return
0x2f (every "ready" bit set except MCU_WAKEUP_READY (bit 4) - the
firmware reports "I'm awake, there's nothing to wake from").
This patch makes the driver self-heal:
* struct bes2600_pwr_t gains pm_unsupported (bool) and
pm_consecutive_timeouts (unsigned int). Both initialised to
0/false.
* bes2600_pwr_enter_lp_mode early-returns -EOPNOTSUPP when
pm_unsupported is set. Skips the per-VIF set_pm round-trip and
the wait_for_completion entirely.
* On the cmpxchg-success branch of the timeout path, we increment
pm_consecutive_timeouts. When it crosses
BES2600_PM_UNSUPPORTED_THRESHOLD (3, ~15s of trying), we latch
pm_unsupported = true and force chip_pm_state = ACTIVE so that
bes2600_pwr_device_exit_lp_mode's c6.2 skip branch covers the
wake side (no gpio_wake / sbus_active / WSM_set_operational_mode
reissue past the first one).
* bes2600_pwr_notify_ps_changed resets pm_consecutive_timeouts to 0
on any incoming PM indication, and clears pm_unsupported if it
was previously latched. So a firmware update that fixes PM_IND
delivery automatically re-enables PSM transitions without a
driver rebuild.
mac80211's PSM requests via bes2600_set_pm() still flow to the
firmware unchanged; they just don't have host-side timeouts so they
remain silent regardless of firmware acknowledgment. Power
consumption goes up if the firmware actually CAN do PSM (we'd be
keeping the chip awake unnecessarily), but on a chip where the
counter trips this trade-off is forced anyway: the chip stayed awake
under the broken cascade as well, just with constant SDIO churn.
Net effect on dmesg: after ~15s of boot, the three-line cascade stops
firing entirely. The firmware-side wedge is observed once per boot
(captured by the pm_unsupported latch) instead of per-cycle.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
193 lines
8.6 KiB
C
193 lines
8.6 KiB
C
/*
|
|
* Mac80211 driver for BES2600 device
|
|
*
|
|
* Copyright (c) 2022, Bestechnic
|
|
* Author:
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
#ifndef __BES_PWR_H__
|
|
#define __BES_PWR_H__
|
|
|
|
#include "bes2600.h"
|
|
|
|
enum bes2600_pwr_event_type {
|
|
BES_PWR_LOCK_ON_SCAN = 1,
|
|
BES_PWR_LOCK_ON_JOIN,
|
|
BES_PWR_LOCK_ON_TX,
|
|
BES_PWR_LOCK_ON_RX,
|
|
BES_PWR_LOCK_ON_FLUSH,
|
|
BES_PWR_LOCK_ON_ROC,
|
|
BES_PWR_LOCK_ON_WSM_TX,
|
|
BES_PWR_LOCK_ON_WSM_OPER,
|
|
BES_PWR_LOCK_ON_BSS_LOST,
|
|
BES_PWR_LOCK_ON_GET_IP,
|
|
BES_PWR_LOCK_ON_PS_ACTIVE,
|
|
BES_PWR_LOCK_ON_LMAC_RSP,
|
|
BES_PWR_LOCK_ON_AP,
|
|
BES_PWR_LOCK_ON_TEST_CMD,
|
|
BES_PWR_LOCK_ON_MUL_REQ,
|
|
BES_PWR_LOCK_ON_ADV_SCAN,
|
|
BES_PWR_LOCK_ON_DISCON,
|
|
BES_PWR_LOCK_ON_QUEUE_GC,
|
|
BES_PWR_LOCK_ON_AP_LP_BAD,
|
|
/* add new lock event here */
|
|
BES_PWR_LOCK_ON_EVENT_MAX,
|
|
};
|
|
|
|
#define BES_PWR_IS_CONSTANT_EVENT(x) ((x) & 0x80000000)
|
|
#define BES_PWR_EVENT_SET_CONSTANT(x) (x = (x | 0x80000000))
|
|
#define BES_PWR_EVENT_NUMBER(x) ((x) & 0x7fffffff)
|
|
|
|
#define BES2600_POWER_DOWN_DELAY 50 // unit in millisecond
|
|
#define BES2600_DELAY_EVENT_NUM (BES_PWR_LOCK_ON_EVENT_MAX << 1)
|
|
|
|
#define BES_PWR_EVENT_TX_TIMEOUT 1500 // unit in millisecond
|
|
#define BES_PWR_EVENT_RX_TIMEOUT 100 // unit in millisecond
|
|
|
|
struct bes2600_pwr_event_t
|
|
{
|
|
struct list_head link;
|
|
unsigned long timeout;
|
|
unsigned long delay;
|
|
u8 idx;
|
|
enum bes2600_pwr_event_type event;
|
|
};
|
|
|
|
enum power_down_state
|
|
{
|
|
POWER_DOWN_STATE_LOCKED = 0,
|
|
POWER_DOWN_STATE_LOCKING,
|
|
POWER_DOWN_STATE_UNLOCKING,
|
|
POWER_DOWN_STATE_UNLOCKED,
|
|
};
|
|
|
|
/*
|
|
* Confirmed PM state of the firmware-side chip. Tracks what the host
|
|
* has *seen* the firmware acknowledge, not what the host has
|
|
* requested. UNKNOWN means a host-initiated transition timed out
|
|
* before the firmware indication arrived; the next wake path should
|
|
* treat it as "we don't know" and probe before issuing GPIO/SDIO
|
|
* wakeup ops.
|
|
*/
|
|
enum bes2600_chip_pm_state {
|
|
BES2600_CHIP_PM_ACTIVE = 0,
|
|
BES2600_CHIP_PM_LP,
|
|
BES2600_CHIP_PM_UNKNOWN,
|
|
};
|
|
|
|
typedef void (*bes_pwr_enter_lp_cb)(struct bes2600_common *hw_priv);
|
|
typedef void (*bes_pwr_exit_lp_cb)(struct bes2600_common *hw_priv);
|
|
|
|
struct bes2600_pwr_enter_cb_item
|
|
{
|
|
struct list_head link;
|
|
bes_pwr_enter_lp_cb cb;
|
|
};
|
|
|
|
struct bes2600_pwr_exit_cb_item
|
|
{
|
|
struct list_head link;
|
|
bes_pwr_exit_lp_cb cb;
|
|
};
|
|
|
|
struct bes2600_pwr_t
|
|
{
|
|
spinlock_t pwr_lock;
|
|
struct delayed_work power_down_work;
|
|
struct work_struct power_async_work;
|
|
struct work_struct power_mcu_down_work;
|
|
struct list_head async_timeout_list;
|
|
struct list_head pending_event_list;
|
|
struct list_head free_event_list;
|
|
enum power_down_state power_state;
|
|
struct task_struct *power_down_task;
|
|
struct task_struct *power_up_task;
|
|
struct task_struct *sys_suspend_task;
|
|
struct task_struct *sys_resume_task;
|
|
struct semaphore sync_lock;
|
|
bool pending_lock;
|
|
atomic_t dev_state;
|
|
struct mutex pwr_mutex;
|
|
struct bes2600_common *hw_priv;
|
|
struct completion pm_enter_cmpl;
|
|
struct mutex pwr_cb_mutex;
|
|
struct list_head enter_cb_list;
|
|
struct list_head exit_cb_list;
|
|
wait_queue_head_t dev_lp_wq;
|
|
bool ap_lp_bad;
|
|
struct bes2600_pwr_event_t pwr_events[BES2600_DELAY_EVENT_NUM];
|
|
atomic_t pm_set_in_process;
|
|
atomic_t chip_pm_state;
|
|
/*
|
|
* Sticky flag set after BES2600_PM_UNSUPPORTED_THRESHOLD
|
|
* consecutive enter_lp_mode timeouts with zero PM_INDICATIONs
|
|
* received from firmware. Indicates this chip's firmware does
|
|
* not honor host-driven PSM transitions; further attempts are
|
|
* skipped to avoid the 5s timeout cascade.
|
|
*/
|
|
bool pm_unsupported;
|
|
unsigned int pm_consecutive_timeouts;
|
|
};
|
|
|
|
#ifdef CONFIG_BES2600_WOWLAN
|
|
void bes2600_pwr_init(struct bes2600_common *hw_priv);
|
|
void bes2600_pwr_exit(struct bes2600_common *hw_priv);
|
|
void bes2600_pwr_prepare(struct bes2600_common *hw_priv);
|
|
void bes2600_pwr_complete(struct bes2600_common *hw_priv);
|
|
void bes2600_pwr_start(struct bes2600_common *hw_priv);
|
|
void bes2600_pwr_stop(struct bes2600_common *hw_priv);
|
|
bool bes2600_pwr_constant_event_is_pending(struct bes2600_common *hw_priv, u32 event);
|
|
int bes2600_pwr_set_busy_event(struct bes2600_common *hw_priv, u32 event);
|
|
int bes2600_pwr_set_busy_event_async(struct bes2600_common *hw_priv, u32 event);
|
|
int bes2600_pwr_set_busy_event_with_timeout(struct bes2600_common *hw_priv, u32 event, u32 timeout);
|
|
int bes2600_pwr_set_busy_event_with_timeout_async(struct bes2600_common *hw_priv, u32 event, u32 timeout);
|
|
int bes2600_pwr_clear_busy_event(struct bes2600_common *hw_priv, u32 event);
|
|
void bes2600_pwr_notify_ps_changed(struct bes2600_common *hw_priv, u8 psmode);
|
|
bool bes2600_pwr_device_is_idle(struct bes2600_common *hw_priv);
|
|
void bes2600_pwr_register_en_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb);
|
|
void bes2600_pwr_unregister_en_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb);
|
|
void bes2600_pwr_register_exit_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb);
|
|
void bes2600_pwr_unregister_exit_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb);
|
|
void bes2600_pwr_suspend_start(struct bes2600_common *hw_priv);
|
|
void bes2600_pwr_suspend_end(struct bes2600_common *hw_priv);
|
|
void bes2600_pwr_resume_start(struct bes2600_common *hw_priv);
|
|
void bes2600_pwr_resume_end(struct bes2600_common *hw_priv);
|
|
void bes2600_pwr_mcu_sleep_directly(struct bes2600_common *hw_priv);
|
|
void bes2600_pwr_mark_ap_lp_bad(struct bes2600_common *hw_priv);
|
|
void bes2600_pwr_clear_ap_lp_bad_mark(struct bes2600_common *hw_priv);
|
|
int bes2600_pwr_busy_event_dump(struct bes2600_common *hw_priv, char *buffer, u32 buf_len);
|
|
int bes2600_pwr_busy_event_record(struct bes2600_common *hw_priv, char *buffer, u32 buf_len);
|
|
#else
|
|
static inline void bes2600_pwr_init(struct bes2600_common *hw_priv) { }
|
|
static inline void bes2600_pwr_exit(struct bes2600_common *hw_priv) { }
|
|
static inline void bes2600_pwr_prepare(struct bes2600_common *hw_priv) { }
|
|
static inline void bes2600_pwr_complete(struct bes2600_common *hw_priv) { }
|
|
static inline void bes2600_pwr_start(struct bes2600_common *hw_priv) { }
|
|
static inline void bes2600_pwr_stop(struct bes2600_common *hw_priv) { }
|
|
static inline bool bes2600_pwr_constant_event_is_pending(struct bes2600_common *hw_priv, u32 event) { return false; }
|
|
static inline int bes2600_pwr_set_busy_event(struct bes2600_common *hw_priv, u32 event) { return 0; }
|
|
static inline int bes2600_pwr_set_busy_event_async(struct bes2600_common *hw_priv, u32 event) {return 0; }
|
|
static inline int bes2600_pwr_set_busy_event_with_timeout(struct bes2600_common *hw_priv, u32 event, u32 timeout) { return 0; }
|
|
static inline int bes2600_pwr_set_busy_event_with_timeout_async(struct bes2600_common *hw_priv, u32 event, u32 timeout) { return 0; }
|
|
static inline int bes2600_pwr_clear_busy_event(struct bes2600_common *hw_priv, u32 event) { return 0; }
|
|
static inline void bes2600_pwr_notify_ps_changed(struct bes2600_common *hw_priv, u8 psmode) { }
|
|
static inline bool bes2600_pwr_device_is_idle(struct bes2600_common *hw_priv) { return false; }
|
|
static inline void bes2600_pwr_register_en_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb) { }
|
|
static inline void bes2600_pwr_unregister_en_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb) { }
|
|
static inline void bes2600_pwr_register_exit_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb) { }
|
|
static inline void bes2600_pwr_unregister_exit_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb) { }
|
|
static inline void bes2600_pwr_suspend_start(struct bes2600_common *hw_priv) { }
|
|
static inline void bes2600_pwr_suspend_end(struct bes2600_common *hw_priv) { }
|
|
static inline void bes2600_pwr_resume_start(struct bes2600_common *hw_priv) { }
|
|
static inline void bes2600_pwr_resume_end(struct bes2600_common *hw_priv) { }
|
|
static inline void bes2600_pwr_mcu_sleep_directly(struct bes2600_common *hw_priv) { }
|
|
static inline void bes2600_pwr_mark_ap_lp_bad(struct bes2600_common *hw_priv) { }
|
|
static inline void bes2600_pwr_clear_ap_lp_bad_mark(struct bes2600_common *hw_priv) { }
|
|
static inline int bes2600_pwr_busy_event_dump(struct bes2600_common *hw_priv, char *buffer, u32 buf_len) { return 0; }
|
|
static inline int bes2600_pwr_busy_event_record(struct bes2600_common *hw_priv, char *buffer, u32 buf_len) { return 0; }
|
|
#endif
|
|
|
|
#endif |