qcacmn: Add delayed reg write support for wcn6450
Current HAL delayed reg write is tied to SRNG notions, hence implement delayed reg write logic in HIF since WCN6450 does not use SRNG interface. New feature flag FEATURE_HIF_DELAYED_REG_WRITE is introduced to disable/enable this support. Change-Id: Id7087ad53cd5879cf49ee0e84dd727de61137541 CRs-Fixed: 3519702
This commit is contained in:

committed by
Rahul Choudhary

parent
219a541da6
commit
a3a5a72bbd
@@ -55,6 +55,8 @@
|
||||
#include <linux/cpumask.h>
|
||||
|
||||
#include <pld_common.h>
|
||||
#include "ce_internal.h"
|
||||
#include <qdf_tracepoint.h>
|
||||
|
||||
void hif_dump(struct hif_opaque_softc *hif_ctx, uint8_t cmd_id, bool start)
|
||||
{
|
||||
@@ -1392,6 +1394,11 @@ static inline int hif_get_num_pending_work(struct hif_softc *scn)
|
||||
{
|
||||
return hal_get_reg_write_pending_work(scn->hal_soc);
|
||||
}
|
||||
#elif defined(FEATURE_HIF_DELAYED_REG_WRITE)
|
||||
static inline int hif_get_num_pending_work(struct hif_softc *scn)
|
||||
{
|
||||
return qdf_atomic_read(&scn->active_work_cnt);
|
||||
}
|
||||
#else
|
||||
|
||||
static inline int hif_get_num_pending_work(struct hif_softc *scn)
|
||||
@@ -1527,6 +1534,354 @@ uint8_t hif_get_ep_vote_access(struct hif_opaque_softc *hif_ctx,
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef FEATURE_HIF_DELAYED_REG_WRITE
|
||||
#ifdef MEMORY_DEBUG
|
||||
#define HIF_REG_WRITE_QUEUE_LEN 128
|
||||
#else
|
||||
#define HIF_REG_WRITE_QUEUE_LEN 32
|
||||
#endif
|
||||
|
||||
/**
|
||||
* hif_print_reg_write_stats() - Print hif delayed reg write stats
|
||||
* @hif_ctx: hif opaque handle
|
||||
*
|
||||
* Return: None
|
||||
*/
|
||||
void hif_print_reg_write_stats(struct hif_opaque_softc *hif_ctx)
|
||||
{
|
||||
struct hif_softc *scn = HIF_GET_SOFTC(hif_ctx);
|
||||
struct CE_state *ce_state;
|
||||
uint32_t *hist;
|
||||
int i;
|
||||
|
||||
hist = scn->wstats.sched_delay;
|
||||
hif_debug("wstats: enq %u deq %u coal %u direct %u q_depth %u max_q %u sched-delay hist %u %u %u %u",
|
||||
qdf_atomic_read(&scn->wstats.enqueues),
|
||||
scn->wstats.dequeues,
|
||||
qdf_atomic_read(&scn->wstats.coalesces),
|
||||
qdf_atomic_read(&scn->wstats.direct),
|
||||
qdf_atomic_read(&scn->wstats.q_depth),
|
||||
scn->wstats.max_q_depth,
|
||||
hist[HIF_REG_WRITE_SCHED_DELAY_SUB_100us],
|
||||
hist[HIF_REG_WRITE_SCHED_DELAY_SUB_1000us],
|
||||
hist[HIF_REG_WRITE_SCHED_DELAY_SUB_5000us],
|
||||
hist[HIF_REG_WRITE_SCHED_DELAY_GT_5000us]);
|
||||
|
||||
for (i = 0; i < scn->ce_count; i++) {
|
||||
ce_state = scn->ce_id_to_state[i];
|
||||
if (!ce_state)
|
||||
continue;
|
||||
|
||||
hif_debug("ce%d: enq %u deq %u coal %u direct %u",
|
||||
i, ce_state->wstats.enqueues,
|
||||
ce_state->wstats.dequeues,
|
||||
ce_state->wstats.coalesces,
|
||||
ce_state->wstats.direct);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* hif_is_reg_write_tput_level_high() - throughput level for delayed reg writes
|
||||
* @scn: hif_softc pointer
|
||||
*
|
||||
* Return: true if throughput is high, else false.
|
||||
*/
|
||||
static inline bool hif_is_reg_write_tput_level_high(struct hif_softc *scn)
|
||||
{
|
||||
int bw_level = hif_get_bandwidth_level(GET_HIF_OPAQUE_HDL(scn));
|
||||
|
||||
return (bw_level >= PLD_BUS_WIDTH_MEDIUM) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* hif_reg_write_fill_sched_delay_hist() - fill reg write delay histogram
|
||||
* @scn: hif_softc pointer
|
||||
* @delay_us: delay in us
|
||||
*
|
||||
* Return: None
|
||||
*/
|
||||
static inline void hif_reg_write_fill_sched_delay_hist(struct hif_softc *scn,
|
||||
uint64_t delay_us)
|
||||
{
|
||||
uint32_t *hist;
|
||||
|
||||
hist = scn->wstats.sched_delay;
|
||||
|
||||
if (delay_us < 100)
|
||||
hist[HIF_REG_WRITE_SCHED_DELAY_SUB_100us]++;
|
||||
else if (delay_us < 1000)
|
||||
hist[HIF_REG_WRITE_SCHED_DELAY_SUB_1000us]++;
|
||||
else if (delay_us < 5000)
|
||||
hist[HIF_REG_WRITE_SCHED_DELAY_SUB_5000us]++;
|
||||
else
|
||||
hist[HIF_REG_WRITE_SCHED_DELAY_GT_5000us]++;
|
||||
}
|
||||
|
||||
/**
|
||||
* hif_process_reg_write_q_elem() - process a register write queue element
|
||||
* @scn: hif_softc pointer
|
||||
* @q_elem: pointer to hal register write queue element
|
||||
*
|
||||
* Return: The value which was written to the address
|
||||
*/
|
||||
static int32_t
|
||||
hif_process_reg_write_q_elem(struct hif_softc *scn,
|
||||
struct hif_reg_write_q_elem *q_elem)
|
||||
{
|
||||
struct CE_state *ce_state = q_elem->ce_state;
|
||||
uint32_t write_val = -1;
|
||||
|
||||
qdf_spin_lock_bh(&ce_state->ce_index_lock);
|
||||
|
||||
ce_state->reg_write_in_progress = false;
|
||||
ce_state->wstats.dequeues++;
|
||||
|
||||
if (ce_state->src_ring) {
|
||||
q_elem->dequeue_val = ce_state->src_ring->write_index;
|
||||
hal_write32_mb(scn->hal_soc, ce_state->ce_wrt_idx_offset,
|
||||
ce_state->src_ring->write_index);
|
||||
write_val = ce_state->src_ring->write_index;
|
||||
} else if (ce_state->dest_ring) {
|
||||
q_elem->dequeue_val = ce_state->dest_ring->write_index;
|
||||
hal_write32_mb(scn->hal_soc, ce_state->ce_wrt_idx_offset,
|
||||
ce_state->dest_ring->write_index);
|
||||
write_val = ce_state->dest_ring->write_index;
|
||||
} else {
|
||||
hif_debug("invalid reg write received");
|
||||
qdf_assert(0);
|
||||
}
|
||||
|
||||
q_elem->valid = 0;
|
||||
ce_state->last_dequeue_time = q_elem->dequeue_time;
|
||||
|
||||
qdf_spin_unlock_bh(&ce_state->ce_index_lock);
|
||||
|
||||
return write_val;
|
||||
}
|
||||
|
||||
/**
|
||||
* hif_reg_write_work() - Worker to process delayed writes
|
||||
* @arg: hif_softc pointer
|
||||
*
|
||||
* Return: None
|
||||
*/
|
||||
static void hif_reg_write_work(void *arg)
|
||||
{
|
||||
struct hif_softc *scn = arg;
|
||||
struct hif_reg_write_q_elem *q_elem;
|
||||
uint32_t offset;
|
||||
uint64_t delta_us;
|
||||
int32_t q_depth, write_val;
|
||||
uint32_t num_processed = 0;
|
||||
int32_t ring_id;
|
||||
|
||||
q_elem = &scn->reg_write_queue[scn->read_idx];
|
||||
q_elem->work_scheduled_time = qdf_get_log_timestamp();
|
||||
q_elem->cpu_id = qdf_get_cpu();
|
||||
|
||||
/* Make sure q_elem consistent in the memory for multi-cores */
|
||||
qdf_rmb();
|
||||
if (!q_elem->valid)
|
||||
return;
|
||||
|
||||
q_depth = qdf_atomic_read(&scn->wstats.q_depth);
|
||||
if (q_depth > scn->wstats.max_q_depth)
|
||||
scn->wstats.max_q_depth = q_depth;
|
||||
|
||||
if (hif_prevent_link_low_power_states(GET_HIF_OPAQUE_HDL(scn))) {
|
||||
scn->wstats.prevent_l1_fails++;
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
qdf_rmb();
|
||||
if (!q_elem->valid)
|
||||
break;
|
||||
|
||||
q_elem->dequeue_time = qdf_get_log_timestamp();
|
||||
ring_id = q_elem->ce_state->id;
|
||||
offset = q_elem->offset;
|
||||
delta_us = qdf_log_timestamp_to_usecs(q_elem->dequeue_time -
|
||||
q_elem->enqueue_time);
|
||||
hif_reg_write_fill_sched_delay_hist(scn, delta_us);
|
||||
|
||||
scn->wstats.dequeues++;
|
||||
qdf_atomic_dec(&scn->wstats.q_depth);
|
||||
|
||||
write_val = hif_process_reg_write_q_elem(scn, q_elem);
|
||||
hif_debug("read_idx %u ce_id %d offset 0x%x dequeue_val %d",
|
||||
scn->read_idx, ring_id, offset, write_val);
|
||||
|
||||
qdf_trace_dp_del_reg_write(ring_id, q_elem->enqueue_val,
|
||||
q_elem->dequeue_val,
|
||||
q_elem->enqueue_time,
|
||||
q_elem->dequeue_time);
|
||||
num_processed++;
|
||||
scn->read_idx = (scn->read_idx + 1) &
|
||||
(HIF_REG_WRITE_QUEUE_LEN - 1);
|
||||
q_elem = &scn->reg_write_queue[scn->read_idx];
|
||||
}
|
||||
|
||||
hif_allow_link_low_power_states(GET_HIF_OPAQUE_HDL(scn));
|
||||
|
||||
/*
|
||||
* Decrement active_work_cnt by the number of elements dequeued after
|
||||
* hif_allow_link_low_power_states.
|
||||
* This makes sure that hif_try_complete_tasks will wait till we make
|
||||
* the bus access in hif_allow_link_low_power_states. This will avoid
|
||||
* race condition between delayed register worker and bus suspend
|
||||
* (system suspend or runtime suspend).
|
||||
*
|
||||
* The following decrement should be done at the end!
|
||||
*/
|
||||
qdf_atomic_sub(num_processed, &scn->active_work_cnt);
|
||||
}
|
||||
|
||||
/**
|
||||
* hif_delayed_reg_write_deinit() - De-Initialize delayed reg write processing
|
||||
* @scn: hif_softc pointer
|
||||
*
|
||||
* De-initialize main data structures to process register writes in a delayed
|
||||
* workqueue.
|
||||
*
|
||||
* Return: None
|
||||
*/
|
||||
static void hif_delayed_reg_write_deinit(struct hif_softc *scn)
|
||||
{
|
||||
qdf_flush_work(&scn->reg_write_work);
|
||||
qdf_disable_work(&scn->reg_write_work);
|
||||
qdf_flush_workqueue(0, scn->reg_write_wq);
|
||||
qdf_destroy_workqueue(0, scn->reg_write_wq);
|
||||
qdf_mem_free(scn->reg_write_queue);
|
||||
}
|
||||
|
||||
/**
|
||||
* hif_delayed_reg_write_init() - Initialization function for delayed reg writes
|
||||
* @scn: hif_softc pointer
|
||||
*
|
||||
* Initialize main data structures to process register writes in a delayed
|
||||
* workqueue.
|
||||
*/
|
||||
|
||||
static QDF_STATUS hif_delayed_reg_write_init(struct hif_softc *scn)
|
||||
{
|
||||
qdf_atomic_init(&scn->active_work_cnt);
|
||||
scn->reg_write_wq =
|
||||
qdf_alloc_high_prior_ordered_workqueue("hif_register_write_wq");
|
||||
qdf_create_work(0, &scn->reg_write_work, hif_reg_write_work, scn);
|
||||
scn->reg_write_queue = qdf_mem_malloc(HIF_REG_WRITE_QUEUE_LEN *
|
||||
sizeof(*scn->reg_write_queue));
|
||||
if (!scn->reg_write_queue) {
|
||||
hif_err("unable to allocate memory for delayed reg write");
|
||||
QDF_BUG(0);
|
||||
return QDF_STATUS_E_NOMEM;
|
||||
}
|
||||
|
||||
/* Initial value of indices */
|
||||
scn->read_idx = 0;
|
||||
qdf_atomic_set(&scn->write_idx, -1);
|
||||
|
||||
return QDF_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static void hif_reg_write_enqueue(struct hif_softc *scn,
|
||||
struct CE_state *ce_state,
|
||||
uint32_t value)
|
||||
{
|
||||
struct hif_reg_write_q_elem *q_elem;
|
||||
uint32_t write_idx;
|
||||
|
||||
if (ce_state->reg_write_in_progress) {
|
||||
hif_debug("Already in progress ce_id %d offset 0x%x value %u",
|
||||
ce_state->id, ce_state->ce_wrt_idx_offset, value);
|
||||
qdf_atomic_inc(&scn->wstats.coalesces);
|
||||
ce_state->wstats.coalesces++;
|
||||
return;
|
||||
}
|
||||
|
||||
write_idx = qdf_atomic_inc_return(&scn->write_idx);
|
||||
write_idx = write_idx & (HIF_REG_WRITE_QUEUE_LEN - 1);
|
||||
|
||||
q_elem = &scn->reg_write_queue[write_idx];
|
||||
if (q_elem->valid) {
|
||||
hif_err("queue full");
|
||||
QDF_BUG(0);
|
||||
return;
|
||||
}
|
||||
|
||||
qdf_atomic_inc(&scn->wstats.enqueues);
|
||||
ce_state->wstats.enqueues++;
|
||||
|
||||
qdf_atomic_inc(&scn->wstats.q_depth);
|
||||
|
||||
q_elem->ce_state = ce_state;
|
||||
q_elem->offset = ce_state->ce_wrt_idx_offset;
|
||||
q_elem->enqueue_val = value;
|
||||
q_elem->enqueue_time = qdf_get_log_timestamp();
|
||||
|
||||
/*
|
||||
* Before the valid flag is set to true, all the other
|
||||
* fields in the q_elem needs to be updated in memory.
|
||||
* Else there is a chance that the dequeuing worker thread
|
||||
* might read stale entries and process incorrect srng.
|
||||
*/
|
||||
qdf_wmb();
|
||||
q_elem->valid = true;
|
||||
|
||||
/*
|
||||
* After all other fields in the q_elem has been updated
|
||||
* in memory successfully, the valid flag needs to be updated
|
||||
* in memory in time too.
|
||||
* Else there is a chance that the dequeuing worker thread
|
||||
* might read stale valid flag and the work will be bypassed
|
||||
* for this round. And if there is no other work scheduled
|
||||
* later, this hal register writing won't be updated any more.
|
||||
*/
|
||||
qdf_wmb();
|
||||
|
||||
ce_state->reg_write_in_progress = true;
|
||||
qdf_atomic_inc(&scn->active_work_cnt);
|
||||
|
||||
hif_debug("write_idx %u ce_id %d offset 0x%x value %u",
|
||||
write_idx, ce_state->id, ce_state->ce_wrt_idx_offset, value);
|
||||
|
||||
qdf_queue_work(scn->qdf_dev, scn->reg_write_wq,
|
||||
&scn->reg_write_work);
|
||||
}
|
||||
|
||||
void hif_delayed_reg_write(struct hif_softc *scn, uint32_t ctrl_addr,
|
||||
uint32_t val)
|
||||
{
|
||||
struct CE_state *ce_state;
|
||||
int ce_id = COPY_ENGINE_ID(ctrl_addr);
|
||||
|
||||
ce_state = scn->ce_id_to_state[ce_id];
|
||||
|
||||
if (!ce_state->htt_tx_data && !ce_state->htt_rx_data) {
|
||||
hif_reg_write_enqueue(scn, ce_state, val);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hif_is_reg_write_tput_level_high(scn) ||
|
||||
(PLD_MHI_STATE_L0 == pld_get_mhi_state(scn->qdf_dev->dev))) {
|
||||
hal_write32_mb(scn->hal_soc, ce_state->ce_wrt_idx_offset, val);
|
||||
qdf_atomic_inc(&scn->wstats.direct);
|
||||
ce_state->wstats.direct++;
|
||||
} else {
|
||||
hif_reg_write_enqueue(scn, ce_state, val);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static inline QDF_STATUS hif_delayed_reg_write_init(struct hif_softc *scn)
|
||||
{
|
||||
return QDF_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static inline void hif_delayed_reg_write_deinit(struct hif_softc *scn)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(QCA_WIFI_WCN6450)
|
||||
static QDF_STATUS hif_hal_attach(struct hif_softc *scn)
|
||||
{
|
||||
@@ -1644,6 +1999,11 @@ QDF_STATUS hif_enable(struct hif_opaque_softc *hif_ctx, struct device *dev,
|
||||
goto disable_bus;
|
||||
}
|
||||
|
||||
if (hif_delayed_reg_write_init(scn) != QDF_STATUS_SUCCESS) {
|
||||
hif_err("unable to initialize delayed reg write");
|
||||
goto hal_detach;
|
||||
}
|
||||
|
||||
if (hif_bus_configure(scn)) {
|
||||
hif_err("Target probe failed");
|
||||
status = QDF_STATUS_E_FAILURE;
|
||||
@@ -1682,6 +2042,7 @@ void hif_disable(struct hif_opaque_softc *hif_ctx, enum hif_disable_type type)
|
||||
if (!scn)
|
||||
return;
|
||||
|
||||
hif_delayed_reg_write_deinit(scn);
|
||||
hif_set_enable_detection(hif_ctx, false);
|
||||
hif_latency_detect_timer_stop(hif_ctx);
|
||||
|
||||
|
Reference in New Issue
Block a user