Files
android_kernel_samsung_sm86…/hal/wifi3.0/hal_srng.c
Vevek Venkatesan 38af510319 qcacmn: add dedicated workqueue for Tx ring delayed reg write
Add delayed SRNG register writes support for Tx Ring, also add
dedicated workqueue to do the delayed Tx SRNG register writes.

Change-Id: I8dd157d341f3035e988804eab50d1ca681ab789b
CRs-Fixed: 2868989
2021-02-12 14:40:11 -08:00

1848 خطوط
51 KiB
C

/*
* Copyright (c) 2016-2021 The Linux Foundation. All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "hal_hw_headers.h"
#include "hal_api.h"
#include "target_type.h"
#include "wcss_version.h"
#include "qdf_module.h"
#ifdef QCA_WIFI_QCA8074
void hal_qca6290_attach(struct hal_soc *hal);
#endif
#ifdef QCA_WIFI_QCA8074
void hal_qca8074_attach(struct hal_soc *hal);
#endif
#if defined(QCA_WIFI_QCA8074V2) || defined(QCA_WIFI_QCA6018)
void hal_qca8074v2_attach(struct hal_soc *hal);
#endif
#ifdef QCA_WIFI_QCA6390
void hal_qca6390_attach(struct hal_soc *hal);
#endif
#ifdef QCA_WIFI_QCA6490
void hal_qca6490_attach(struct hal_soc *hal);
#endif
#ifdef QCA_WIFI_QCN9000
void hal_qcn9000_attach(struct hal_soc *hal);
#endif
#ifdef QCA_WIFI_QCN6122
void hal_qcn6122_attach(struct hal_soc *hal);
#endif
#ifdef QCA_WIFI_QCA6750
void hal_qca6750_attach(struct hal_soc *hal);
#endif
#ifdef QCA_WIFI_QCA5018
void hal_qca5018_attach(struct hal_soc *hal);
#endif
#ifdef ENABLE_VERBOSE_DEBUG
bool is_hal_verbose_debug_enabled;
#endif
#ifdef ENABLE_HAL_REG_WR_HISTORY
struct hal_reg_write_fail_history hal_reg_wr_hist;
void hal_reg_wr_fail_history_add(struct hal_soc *hal_soc,
uint32_t offset,
uint32_t wr_val, uint32_t rd_val)
{
struct hal_reg_write_fail_entry *record;
int idx;
idx = hal_history_get_next_index(&hal_soc->reg_wr_fail_hist->index,
HAL_REG_WRITE_HIST_SIZE);
record = &hal_soc->reg_wr_fail_hist->record[idx];
record->timestamp = qdf_get_log_timestamp();
record->reg_offset = offset;
record->write_val = wr_val;
record->read_val = rd_val;
}
static void hal_reg_write_fail_history_init(struct hal_soc *hal)
{
hal->reg_wr_fail_hist = &hal_reg_wr_hist;
qdf_atomic_set(&hal->reg_wr_fail_hist->index, -1);
}
#else
static void hal_reg_write_fail_history_init(struct hal_soc *hal)
{
}
#endif
/**
* hal_get_srng_ring_id() - get the ring id of a descriped ring
* @hal: hal_soc data structure
* @ring_type: type enum describing the ring
* @ring_num: which ring of the ring type
* @mac_id: which mac does the ring belong to (or 0 for non-lmac rings)
*
* Return: the ring id or -EINVAL if the ring does not exist.
*/
static int hal_get_srng_ring_id(struct hal_soc *hal, int ring_type,
int ring_num, int mac_id)
{
struct hal_hw_srng_config *ring_config =
HAL_SRNG_CONFIG(hal, ring_type);
int ring_id;
if (ring_num >= ring_config->max_rings) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO,
"%s: ring_num exceeded maximum no. of supported rings",
__func__);
/* TODO: This is a programming error. Assert if this happens */
return -EINVAL;
}
if (ring_config->lmac_ring) {
ring_id = ring_config->start_ring_id + ring_num +
(mac_id * HAL_MAX_RINGS_PER_LMAC);
} else {
ring_id = ring_config->start_ring_id + ring_num;
}
return ring_id;
}
static struct hal_srng *hal_get_srng(struct hal_soc *hal, int ring_id)
{
/* TODO: Should we allocate srng structures dynamically? */
return &(hal->srng_list[ring_id]);
}
#define HP_OFFSET_IN_REG_START 1
#define OFFSET_FROM_HP_TO_TP 4
static void hal_update_srng_hp_tp_address(struct hal_soc *hal_soc,
int shadow_config_index,
int ring_type,
int ring_num)
{
struct hal_srng *srng;
int ring_id;
struct hal_hw_srng_config *ring_config =
HAL_SRNG_CONFIG(hal_soc, ring_type);
ring_id = hal_get_srng_ring_id(hal_soc, ring_type, ring_num, 0);
if (ring_id < 0)
return;
srng = hal_get_srng(hal_soc, ring_id);
if (ring_config->ring_dir == HAL_SRNG_DST_RING) {
srng->u.dst_ring.tp_addr = SHADOW_REGISTER(shadow_config_index)
+ hal_soc->dev_base_addr;
hal_debug("tp_addr=%pK dev base addr %pK index %u",
srng->u.dst_ring.tp_addr, hal_soc->dev_base_addr,
shadow_config_index);
} else {
srng->u.src_ring.hp_addr = SHADOW_REGISTER(shadow_config_index)
+ hal_soc->dev_base_addr;
hal_debug("hp_addr=%pK dev base addr %pK index %u",
srng->u.src_ring.hp_addr,
hal_soc->dev_base_addr, shadow_config_index);
}
}
#ifdef GENERIC_SHADOW_REGISTER_ACCESS_ENABLE
void hal_set_one_target_reg_config(struct hal_soc *hal,
uint32_t target_reg_offset,
int list_index)
{
int i = list_index;
qdf_assert_always(i < MAX_GENERIC_SHADOW_REG);
hal->list_shadow_reg_config[i].target_register =
target_reg_offset;
hal->num_generic_shadow_regs_configured++;
}
qdf_export_symbol(hal_set_one_target_reg_config);
#define REO_R0_DESTINATION_RING_CTRL_ADDR_OFFSET 0x4
#define MAX_REO_REMAP_SHADOW_REGS 4
QDF_STATUS hal_set_shadow_regs(void *hal_soc)
{
uint32_t target_reg_offset;
struct hal_soc *hal = (struct hal_soc *)hal_soc;
int i;
struct hal_hw_srng_config *srng_config =
&hal->hw_srng_table[WBM2SW_RELEASE];
target_reg_offset =
HWIO_REO_R0_DESTINATION_RING_CTRL_IX_0_ADDR(
SEQ_WCSS_UMAC_REO_REG_OFFSET);
for (i = 0; i < MAX_REO_REMAP_SHADOW_REGS; i++) {
hal_set_one_target_reg_config(hal, target_reg_offset, i);
target_reg_offset += REO_R0_DESTINATION_RING_CTRL_ADDR_OFFSET;
}
target_reg_offset = srng_config->reg_start[HP_OFFSET_IN_REG_START];
target_reg_offset += (srng_config->reg_size[HP_OFFSET_IN_REG_START]
* HAL_IPA_TX_COMP_RING_IDX);
hal_set_one_target_reg_config(hal, target_reg_offset, i);
return QDF_STATUS_SUCCESS;
}
qdf_export_symbol(hal_set_shadow_regs);
QDF_STATUS hal_construct_shadow_regs(void *hal_soc)
{
struct hal_soc *hal = (struct hal_soc *)hal_soc;
int shadow_config_index = hal->num_shadow_registers_configured;
int i;
int num_regs = hal->num_generic_shadow_regs_configured;
for (i = 0; i < num_regs; i++) {
qdf_assert_always(shadow_config_index < MAX_SHADOW_REGISTERS);
hal->shadow_config[shadow_config_index].addr =
hal->list_shadow_reg_config[i].target_register;
hal->list_shadow_reg_config[i].shadow_config_index =
shadow_config_index;
hal->list_shadow_reg_config[i].va =
SHADOW_REGISTER(shadow_config_index) +
(uintptr_t)hal->dev_base_addr;
hal_debug("target_reg %x, shadow register 0x%x shadow_index 0x%x",
hal->shadow_config[shadow_config_index].addr,
SHADOW_REGISTER(shadow_config_index),
shadow_config_index);
shadow_config_index++;
hal->num_shadow_registers_configured++;
}
return QDF_STATUS_SUCCESS;
}
qdf_export_symbol(hal_construct_shadow_regs);
#endif
QDF_STATUS hal_set_one_shadow_config(void *hal_soc,
int ring_type,
int ring_num)
{
uint32_t target_register;
struct hal_soc *hal = (struct hal_soc *)hal_soc;
struct hal_hw_srng_config *srng_config = &hal->hw_srng_table[ring_type];
int shadow_config_index = hal->num_shadow_registers_configured;
if (shadow_config_index >= MAX_SHADOW_REGISTERS) {
QDF_ASSERT(0);
return QDF_STATUS_E_RESOURCES;
}
hal->num_shadow_registers_configured++;
target_register = srng_config->reg_start[HP_OFFSET_IN_REG_START];
target_register += (srng_config->reg_size[HP_OFFSET_IN_REG_START]
*ring_num);
/* if the ring is a dst ring, we need to shadow the tail pointer */
if (srng_config->ring_dir == HAL_SRNG_DST_RING)
target_register += OFFSET_FROM_HP_TO_TP;
hal->shadow_config[shadow_config_index].addr = target_register;
/* update hp/tp addr in the hal_soc structure*/
hal_update_srng_hp_tp_address(hal_soc, shadow_config_index, ring_type,
ring_num);
hal_debug("target_reg %x, shadow register 0x%x shadow_index 0x%x, ring_type %d, ring num %d",
target_register,
SHADOW_REGISTER(shadow_config_index),
shadow_config_index,
ring_type, ring_num);
return QDF_STATUS_SUCCESS;
}
qdf_export_symbol(hal_set_one_shadow_config);
QDF_STATUS hal_construct_srng_shadow_regs(void *hal_soc)
{
int ring_type, ring_num;
struct hal_soc *hal = (struct hal_soc *)hal_soc;
for (ring_type = 0; ring_type < MAX_RING_TYPES; ring_type++) {
struct hal_hw_srng_config *srng_config =
&hal->hw_srng_table[ring_type];
if (ring_type == CE_SRC ||
ring_type == CE_DST ||
ring_type == CE_DST_STATUS)
continue;
if (srng_config->lmac_ring)
continue;
for (ring_num = 0; ring_num < srng_config->max_rings;
ring_num++)
hal_set_one_shadow_config(hal_soc, ring_type, ring_num);
}
return QDF_STATUS_SUCCESS;
}
qdf_export_symbol(hal_construct_srng_shadow_regs);
void hal_get_shadow_config(void *hal_soc,
struct pld_shadow_reg_v2_cfg **shadow_config,
int *num_shadow_registers_configured)
{
struct hal_soc *hal = (struct hal_soc *)hal_soc;
*shadow_config = hal->shadow_config;
*num_shadow_registers_configured =
hal->num_shadow_registers_configured;
}
qdf_export_symbol(hal_get_shadow_config);
static void hal_validate_shadow_register(struct hal_soc *hal,
uint32_t *destination,
uint32_t *shadow_address)
{
unsigned int index;
uint32_t *shadow_0_offset = SHADOW_REGISTER(0) + hal->dev_base_addr;
int destination_ba_offset =
((char *)destination) - (char *)hal->dev_base_addr;
index = shadow_address - shadow_0_offset;
if (index >= MAX_SHADOW_REGISTERS) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s: index %x out of bounds", __func__, index);
goto error;
} else if (hal->shadow_config[index].addr != destination_ba_offset) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s: sanity check failure, expected %x, found %x",
__func__, destination_ba_offset,
hal->shadow_config[index].addr);
goto error;
}
return;
error:
qdf_print("baddr %pK, desination %pK, shadow_address %pK s0offset %pK index %x",
hal->dev_base_addr, destination, shadow_address,
shadow_0_offset, index);
QDF_BUG(0);
return;
}
static void hal_target_based_configure(struct hal_soc *hal)
{
/**
* Indicate Initialization of srngs to avoid force wake
* as umac power collapse is not enabled yet
*/
hal->init_phase = true;
switch (hal->target_type) {
#ifdef QCA_WIFI_QCA6290
case TARGET_TYPE_QCA6290:
hal->use_register_windowing = true;
hal_qca6290_attach(hal);
break;
#endif
#ifdef QCA_WIFI_QCA6390
case TARGET_TYPE_QCA6390:
hal->use_register_windowing = true;
hal_qca6390_attach(hal);
break;
#endif
#ifdef QCA_WIFI_QCA6490
case TARGET_TYPE_QCA6490:
hal->use_register_windowing = true;
hal_qca6490_attach(hal);
hal->init_phase = false;
break;
#endif
#ifdef QCA_WIFI_QCA6750
case TARGET_TYPE_QCA6750:
hal->use_register_windowing = true;
hal->static_window_map = true;
hal_qca6750_attach(hal);
break;
#endif
#if defined(QCA_WIFI_QCA8074) && defined(WIFI_TARGET_TYPE_3_0)
case TARGET_TYPE_QCA8074:
hal_qca8074_attach(hal);
break;
#endif
#if defined(QCA_WIFI_QCA8074V2)
case TARGET_TYPE_QCA8074V2:
hal_qca8074v2_attach(hal);
break;
#endif
#if defined(QCA_WIFI_QCA6018)
case TARGET_TYPE_QCA6018:
hal_qca8074v2_attach(hal);
break;
#endif
#if defined(QCA_WIFI_QCN6122)
case TARGET_TYPE_QCN6122:
hal->use_register_windowing = true;
/*
* Static window map is enabled for qcn9000 to use 2mb bar
* size and use multiple windows to write into registers.
*/
hal->static_window_map = true;
hal_qcn6122_attach(hal);
break;
#endif
#ifdef QCA_WIFI_QCN9000
case TARGET_TYPE_QCN9000:
hal->use_register_windowing = true;
/*
* Static window map is enabled for qcn9000 to use 2mb bar
* size and use multiple windows to write into registers.
*/
hal->static_window_map = true;
hal_qcn9000_attach(hal);
break;
#endif
#ifdef QCA_WIFI_QCA5018
case TARGET_TYPE_QCA5018:
hal->use_register_windowing = true;
hal->static_window_map = true;
hal_qca5018_attach(hal);
break;
#endif
default:
break;
}
}
uint32_t hal_get_target_type(hal_soc_handle_t hal_soc_hdl)
{
struct hal_soc *hal_soc = (struct hal_soc *)hal_soc_hdl;
struct hif_target_info *tgt_info =
hif_get_target_info_handle(hal_soc->hif_handle);
return tgt_info->target_type;
}
qdf_export_symbol(hal_get_target_type);
#if defined(FEATURE_HAL_DELAYED_REG_WRITE) || \
defined(FEATURE_HAL_DELAYED_REG_WRITE_V2)
/**
* hal_is_reg_write_tput_level_high() - throughput level for delayed reg writes
* @hal: hal_soc pointer
*
* Return: true if throughput is high, else false.
*/
static inline bool hal_is_reg_write_tput_level_high(struct hal_soc *hal)
{
int bw_level = hif_get_bandwidth_level(hal->hif_handle);
return (bw_level >= PLD_BUS_WIDTH_MEDIUM) ? true : false;
}
static inline
char *hal_fill_reg_write_srng_stats(struct hal_srng *srng,
char *buf, qdf_size_t size)
{
qdf_scnprintf(buf, size, "enq %u deq %u coal %u direct %u",
srng->wstats.enqueues, srng->wstats.dequeues,
srng->wstats.coalesces, srng->wstats.direct);
return buf;
}
/* bytes for local buffer */
#define HAL_REG_WRITE_SRNG_STATS_LEN 100
void hal_dump_reg_write_srng_stats(hal_soc_handle_t hal_soc_hdl)
{
struct hal_srng *srng;
char buf[HAL_REG_WRITE_SRNG_STATS_LEN];
struct hal_soc *hal = (struct hal_soc *)hal_soc_hdl;
srng = hal_get_srng(hal, HAL_SRNG_SW2TCL1);
hal_debug("SW2TCL1: %s",
hal_fill_reg_write_srng_stats(srng, buf, sizeof(buf)));
srng = hal_get_srng(hal, HAL_SRNG_WBM2SW0_RELEASE);
hal_debug("WBM2SW0: %s",
hal_fill_reg_write_srng_stats(srng, buf, sizeof(buf)));
srng = hal_get_srng(hal, HAL_SRNG_REO2SW1);
hal_debug("REO2SW1: %s",
hal_fill_reg_write_srng_stats(srng, buf, sizeof(buf)));
srng = hal_get_srng(hal, HAL_SRNG_REO2SW2);
hal_debug("REO2SW2: %s",
hal_fill_reg_write_srng_stats(srng, buf, sizeof(buf)));
srng = hal_get_srng(hal, HAL_SRNG_REO2SW3);
hal_debug("REO2SW3: %s",
hal_fill_reg_write_srng_stats(srng, buf, sizeof(buf)));
}
#ifdef FEATURE_HAL_DELAYED_REG_WRITE_V2
/**
* hal_dump_tcl_stats() - dump the TCL reg write stats
* @hal: hal_soc pointer
*
* Return: None
*/
static inline void hal_dump_tcl_stats(struct hal_soc *hal)
{
struct hal_srng *srng = hal_get_srng(hal, HAL_SRNG_SW2TCL1);
uint32_t *hist = hal->tcl_stats.sched_delay;
char buf[HAL_REG_WRITE_SRNG_STATS_LEN];
hal_debug("TCL: %s sched-delay hist %u %u %u %u",
hal_fill_reg_write_srng_stats(srng, buf, sizeof(buf)),
hist[REG_WRITE_SCHED_DELAY_SUB_100us],
hist[REG_WRITE_SCHED_DELAY_SUB_1000us],
hist[REG_WRITE_SCHED_DELAY_SUB_5000us],
hist[REG_WRITE_SCHED_DELAY_GT_5000us]);
hal_debug("wq_dly %u wq_dir %u tim_enq %u tim_dir %u enq_tim_cnt %u dir_tim_cnt %u rst_tim_cnt %u",
hal->tcl_stats.wq_delayed,
hal->tcl_stats.wq_direct,
hal->tcl_stats.timer_enq,
hal->tcl_stats.timer_direct,
hal->tcl_stats.enq_timer_set,
hal->tcl_stats.direct_timer_set,
hal->tcl_stats.timer_reset);
}
#else
static inline void hal_dump_tcl_stats(struct hal_soc *hal)
{
}
#endif
void hal_dump_reg_write_stats(hal_soc_handle_t hal_soc_hdl)
{
uint32_t *hist;
struct hal_soc *hal = (struct hal_soc *)hal_soc_hdl;
hist = hal->stats.wstats.sched_delay;
hal_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(&hal->stats.wstats.enqueues),
hal->stats.wstats.dequeues,
qdf_atomic_read(&hal->stats.wstats.coalesces),
qdf_atomic_read(&hal->stats.wstats.direct),
qdf_atomic_read(&hal->stats.wstats.q_depth),
hal->stats.wstats.max_q_depth,
hist[REG_WRITE_SCHED_DELAY_SUB_100us],
hist[REG_WRITE_SCHED_DELAY_SUB_1000us],
hist[REG_WRITE_SCHED_DELAY_SUB_5000us],
hist[REG_WRITE_SCHED_DELAY_GT_5000us]);
hal_dump_tcl_stats(hal);
}
int hal_get_reg_write_pending_work(void *hal_soc)
{
struct hal_soc *hal = (struct hal_soc *)hal_soc;
return qdf_atomic_read(&hal->active_work_cnt);
}
#endif
#ifdef FEATURE_HAL_DELAYED_REG_WRITE
#ifdef MEMORY_DEBUG
/*
* Length of the queue(array) used to hold delayed register writes.
* Must be a multiple of 2.
*/
#define HAL_REG_WRITE_QUEUE_LEN 128
#else
#define HAL_REG_WRITE_QUEUE_LEN 32
#endif
/**
* hal_process_reg_write_q_elem() - process a regiter write queue element
* @hal: hal_soc pointer
* @q_elem: pointer to hal regiter write queue element
*
* Return: The value which was written to the address
*/
static uint32_t
hal_process_reg_write_q_elem(struct hal_soc *hal,
struct hal_reg_write_q_elem *q_elem)
{
struct hal_srng *srng = q_elem->srng;
uint32_t write_val;
SRNG_LOCK(&srng->lock);
srng->reg_write_in_progress = false;
srng->wstats.dequeues++;
if (srng->ring_dir == HAL_SRNG_SRC_RING) {
q_elem->dequeue_val = srng->u.src_ring.hp;
hal_write_address_32_mb(hal,
srng->u.src_ring.hp_addr,
srng->u.src_ring.hp, false);
write_val = srng->u.src_ring.hp;
} else {
q_elem->dequeue_val = srng->u.dst_ring.tp;
hal_write_address_32_mb(hal,
srng->u.dst_ring.tp_addr,
srng->u.dst_ring.tp, false);
write_val = srng->u.dst_ring.tp;
}
q_elem->valid = 0;
SRNG_UNLOCK(&srng->lock);
return write_val;
}
/**
* hal_reg_write_fill_sched_delay_hist() - fill reg write delay histogram in hal
* @hal: hal_soc pointer
* @delay: delay in us
*
* Return: None
*/
static inline void hal_reg_write_fill_sched_delay_hist(struct hal_soc *hal,
uint64_t delay_us)
{
uint32_t *hist;
hist = hal->stats.wstats.sched_delay;
if (delay_us < 100)
hist[REG_WRITE_SCHED_DELAY_SUB_100us]++;
else if (delay_us < 1000)
hist[REG_WRITE_SCHED_DELAY_SUB_1000us]++;
else if (delay_us < 5000)
hist[REG_WRITE_SCHED_DELAY_SUB_5000us]++;
else
hist[REG_WRITE_SCHED_DELAY_GT_5000us]++;
}
/**
* hal_reg_write_work() - Worker to process delayed writes
* @arg: hal_soc pointer
*
* Return: None
*/
static void hal_reg_write_work(void *arg)
{
int32_t q_depth, write_val;
struct hal_soc *hal = arg;
struct hal_reg_write_q_elem *q_elem;
uint64_t delta_us;
uint8_t ring_id;
uint32_t *addr;
uint32_t num_processed = 0;
q_elem = &hal->reg_write_queue[(hal->read_idx)];
q_elem->work_scheduled_time = qdf_get_log_timestamp();
/* Make sure q_elem consistent in the memory for multi-cores */
qdf_rmb();
if (!q_elem->valid)
return;
q_depth = qdf_atomic_read(&hal->stats.wstats.q_depth);
if (q_depth > hal->stats.wstats.max_q_depth)
hal->stats.wstats.max_q_depth = q_depth;
if (hif_prevent_link_low_power_states(hal->hif_handle)) {
hal->stats.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->srng->ring_id;
addr = q_elem->addr;
delta_us = qdf_log_timestamp_to_usecs(q_elem->dequeue_time -
q_elem->enqueue_time);
hal_reg_write_fill_sched_delay_hist(hal, delta_us);
hal->stats.wstats.dequeues++;
qdf_atomic_dec(&hal->stats.wstats.q_depth);
write_val = hal_process_reg_write_q_elem(hal, q_elem);
hal_verbose_debug("read_idx %u srng 0x%x, addr 0x%pK dequeue_val %u sched delay %llu us",
hal->read_idx, ring_id, addr, write_val, delta_us);
num_processed++;
hal->read_idx = (hal->read_idx + 1) &
(HAL_REG_WRITE_QUEUE_LEN - 1);
q_elem = &hal->reg_write_queue[(hal->read_idx)];
}
hif_allow_link_low_power_states(hal->hif_handle);
/*
* 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, &hal->active_work_cnt);
}
static void __hal_flush_reg_write_work(struct hal_soc *hal)
{
qdf_cancel_work(&hal->reg_write_work);
}
void hal_flush_reg_write_work(hal_soc_handle_t hal_handle)
{ __hal_flush_reg_write_work((struct hal_soc *)hal_handle);
}
/**
* hal_reg_write_enqueue() - enqueue register writes into kworker
* @hal_soc: hal_soc pointer
* @srng: srng pointer
* @addr: iomem address of regiter
* @value: value to be written to iomem address
*
* This function executes from within the SRNG LOCK
*
* Return: None
*/
static void hal_reg_write_enqueue(struct hal_soc *hal_soc,
struct hal_srng *srng,
void __iomem *addr,
uint32_t value)
{
struct hal_reg_write_q_elem *q_elem;
uint32_t write_idx;
if (srng->reg_write_in_progress) {
hal_verbose_debug("Already in progress srng ring id 0x%x addr 0x%pK val %u",
srng->ring_id, addr, value);
qdf_atomic_inc(&hal_soc->stats.wstats.coalesces);
srng->wstats.coalesces++;
return;
}
write_idx = qdf_atomic_inc_return(&hal_soc->write_idx);
write_idx = write_idx & (HAL_REG_WRITE_QUEUE_LEN - 1);
q_elem = &hal_soc->reg_write_queue[write_idx];
if (q_elem->valid) {
hal_err("queue full");
QDF_BUG(0);
return;
}
qdf_atomic_inc(&hal_soc->stats.wstats.enqueues);
srng->wstats.enqueues++;
qdf_atomic_inc(&hal_soc->stats.wstats.q_depth);
q_elem->srng = srng;
q_elem->addr = addr;
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();
srng->reg_write_in_progress = true;
qdf_atomic_inc(&hal_soc->active_work_cnt);
hal_verbose_debug("write_idx %u srng ring id 0x%x addr 0x%pK val %u",
write_idx, srng->ring_id, addr, value);
qdf_queue_work(hal_soc->qdf_dev, hal_soc->reg_write_wq,
&hal_soc->reg_write_work);
}
/**
* hal_delayed_reg_write_init() - Initialization function for delayed reg writes
* @hal_soc: hal_soc pointer
*
* Initialize main data structures to process register writes in a delayed
* workqueue.
*
* Return: QDF_STATUS_SUCCESS on success else a QDF error.
*/
static QDF_STATUS hal_delayed_reg_write_init(struct hal_soc *hal)
{
hal->reg_write_wq =
qdf_alloc_high_prior_ordered_workqueue("hal_register_write_wq");
qdf_create_work(0, &hal->reg_write_work, hal_reg_write_work, hal);
hal->reg_write_queue = qdf_mem_malloc(HAL_REG_WRITE_QUEUE_LEN *
sizeof(*hal->reg_write_queue));
if (!hal->reg_write_queue) {
hal_err("unable to allocate memory");
QDF_BUG(0);
return QDF_STATUS_E_NOMEM;
}
/* Initial value of indices */
hal->read_idx = 0;
qdf_atomic_set(&hal->write_idx, -1);
return QDF_STATUS_SUCCESS;
}
/**
* hal_delayed_reg_write_deinit() - De-Initialize delayed reg write processing
* @hal_soc: hal_soc pointer
*
* De-initialize main data structures to process register writes in a delayed
* workqueue.
*
* Return: None
*/
static void hal_delayed_reg_write_deinit(struct hal_soc *hal)
{
__hal_flush_reg_write_work(hal);
qdf_flush_workqueue(0, hal->reg_write_wq);
qdf_destroy_workqueue(0, hal->reg_write_wq);
qdf_mem_free(hal->reg_write_queue);
}
#else
static inline QDF_STATUS hal_delayed_reg_write_init(struct hal_soc *hal)
{
return QDF_STATUS_SUCCESS;
}
static inline void hal_delayed_reg_write_deinit(struct hal_soc *hal)
{
}
#endif
#ifdef FEATURE_HAL_DELAYED_REG_WRITE_V2
#ifdef MEMORY_DEBUG
/**
* hal_reg_write_get_timestamp() - Function to get the timestamp
*
* Return: return present simestamp
*/
static inline qdf_time_t hal_del_reg_write_get_ts(void)
{
return qdf_get_log_timestamp();
}
/**
* hal_del_reg_write_ts_usecs() - Convert the timestamp to micro secs
* @ts: timestamp value to be converted
*
* Return: return the timestamp in micro secs
*/
static inline qdf_time_t hal_del_reg_write_ts_usecs(qdf_time_t ts)
{
return qdf_log_timestamp_to_usecs(ts);
}
/**
* hal_tcl_write_fill_sched_delay_hist() - fill TCL reg write delay histogram
* @hal: hal_soc pointer
* @delay: delay in us
*
* Return: None
*/
static inline void hal_tcl_write_fill_sched_delay_hist(struct hal_soc *hal)
{
uint32_t *hist;
uint32_t delay_us;
hal->tcl_stats.deq_time = hal_del_reg_write_get_ts();
delay_us = hal_del_reg_write_ts_usecs(hal->tcl_stats.deq_time -
hal->tcl_stats.enq_time);
hist = hal->tcl_stats.sched_delay;
if (delay_us < 100)
hist[REG_WRITE_SCHED_DELAY_SUB_100us]++;
else if (delay_us < 1000)
hist[REG_WRITE_SCHED_DELAY_SUB_1000us]++;
else if (delay_us < 5000)
hist[REG_WRITE_SCHED_DELAY_SUB_5000us]++;
else
hist[REG_WRITE_SCHED_DELAY_GT_5000us]++;
}
#else
static inline qdf_time_t hal_del_reg_write_get_ts(void)
{
return 0;
}
static inline qdf_time_t hal_del_reg_write_ts_usecs(qdf_time_t ts)
{
return 0;
}
static inline void hal_tcl_write_fill_sched_delay_hist(struct hal_soc *hal)
{
}
#endif
/**
* hal_tcl_reg_write_work() - Worker to process delayed SW2TCL1 writes
* @arg: hal_soc pointer
*
* Return: None
*/
static void hal_tcl_reg_write_work(void *arg)
{
struct hal_soc *hal = arg;
struct hal_srng *srng = hal_get_srng(hal, HAL_SRNG_SW2TCL1);
SRNG_LOCK(&srng->lock);
srng->wstats.dequeues++;
hal_tcl_write_fill_sched_delay_hist(hal);
/*
* During the tranition of low to high tput scenario, reg write moves
* from delayed to direct write context, there is a little chance that
* worker thread gets scheduled later than direct context write which
* already wrote the latest HP value. This check can catch that case
* and avoid the repetitive writing of the same HP value.
*/
if (srng->last_reg_wr_val != srng->u.src_ring.hp) {
srng->last_reg_wr_val = srng->u.src_ring.hp;
if (hal->tcl_direct) {
/*
* TCL reg writes have been moved to direct context and
* the assumption is that PCIe bus stays in Active state
* during high tput, hence its fine to write the HP
* while the SRNG_LOCK is being held.
*/
hal->tcl_stats.wq_direct++;
hal_write_address_32_mb(hal, srng->u.src_ring.hp_addr,
srng->last_reg_wr_val, false);
srng->reg_write_in_progress = false;
SRNG_UNLOCK(&srng->lock);
} else {
/*
* TCL reg write to happen in delayed context,
* write operation might take time due to possibility of
* PCIe bus stays in low power state during low tput,
* Hence release the SRNG_LOCK before writing.
*/
hal->tcl_stats.wq_delayed++;
srng->reg_write_in_progress = false;
SRNG_UNLOCK(&srng->lock);
hal_write_address_32_mb(hal, srng->u.src_ring.hp_addr,
srng->last_reg_wr_val, false);
}
} else {
srng->reg_write_in_progress = false;
SRNG_UNLOCK(&srng->lock);
}
/*
* Decrement active_work_cnt to make sure that hif_try_complete_tasks
* will wait. 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_dec(&hal->active_work_cnt);
qdf_atomic_set(&hal->tcl_work_active, false);
}
static void __hal_flush_tcl_reg_write_work(struct hal_soc *hal)
{
qdf_cancel_work(&hal->tcl_reg_write_work);
}
/**
* hal_tcl_reg_write_enqueue() - enqueue TCL register writes into kworker
* @hal_soc: hal_soc pointer
* @srng: srng pointer
* @addr: iomem address of regiter
* @value: value to be written to iomem address
*
* This function executes from within the SRNG LOCK
*
* Return: None
*/
static void hal_tcl_reg_write_enqueue(struct hal_soc *hal_soc,
struct hal_srng *srng,
void __iomem *addr,
uint32_t value)
{
hal_soc->tcl_stats.enq_time = hal_del_reg_write_get_ts();
if (qdf_queue_work(hal_soc->qdf_dev, hal_soc->tcl_reg_write_wq,
&hal_soc->tcl_reg_write_work)) {
srng->reg_write_in_progress = true;
qdf_atomic_inc(&hal_soc->active_work_cnt);
qdf_atomic_set(&hal_soc->tcl_work_active, true);
srng->wstats.enqueues++;
} else {
hal_soc->tcl_stats.enq_timer_set++;
qdf_timer_mod(&hal_soc->tcl_reg_write_timer, 1);
}
}
/**
* hal_tcl_reg_write_timer() - timer handler to take care of pending TCL writes
* @arg: srng handle
*
* This function handles the pending TCL reg writes missed due to the previous
* scheduled worker running.
*
* Return: None
*/
static void hal_tcl_reg_write_timer(void *arg)
{
hal_ring_handle_t srng_hdl = arg;
struct hal_srng *srng;
struct hal_soc *hal;
srng = (struct hal_srng *)srng_hdl;
hal = srng->hal_soc;
if (hif_pm_runtime_get(hal->hif_handle, RTPM_ID_DW_TX_HW_ENQUEUE,
true)) {
hal_srng_set_event(srng_hdl, HAL_SRNG_FLUSH_EVENT);
hal_srng_inc_flush_cnt(srng_hdl);
goto fail;
}
SRNG_LOCK(&srng->lock);
if (hal->tcl_direct) {
/*
* Due to the previous scheduled worker still running,
* direct reg write cannot be performed, so posted the
* pending writes to timer context.
*/
if (srng->last_reg_wr_val != srng->u.src_ring.hp) {
srng->last_reg_wr_val = srng->u.src_ring.hp;
srng->wstats.direct++;
hal->tcl_stats.timer_direct++;
hal_write_address_32_mb(hal, srng->u.src_ring.hp_addr,
srng->last_reg_wr_val, false);
}
} else {
/*
* Due to the previous scheduled worker still running,
* queue_work from delayed context would fail,
* so retry from timer context.
*/
if (qdf_queue_work(hal->qdf_dev, hal->tcl_reg_write_wq,
&hal->tcl_reg_write_work)) {
srng->reg_write_in_progress = true;
qdf_atomic_inc(&hal->active_work_cnt);
qdf_atomic_set(&hal->tcl_work_active, true);
srng->wstats.enqueues++;
hal->tcl_stats.timer_enq++;
} else {
if (srng->last_reg_wr_val != srng->u.src_ring.hp) {
hal->tcl_stats.timer_reset++;
qdf_timer_mod(&hal->tcl_reg_write_timer, 1);
}
}
}
SRNG_UNLOCK(&srng->lock);
hif_pm_runtime_put(hal->hif_handle, RTPM_ID_DW_TX_HW_ENQUEUE);
fail:
return;
}
/**
* hal_delayed_tcl_reg_write_init() - Initialization for delayed TCL reg writes
* @hal_soc: hal_soc pointer
*
* Initialize main data structures to process TCL register writes in a delayed
* workqueue.
*
* Return: QDF_STATUS_SUCCESS on success else a QDF error.
*/
static QDF_STATUS hal_delayed_tcl_reg_write_init(struct hal_soc *hal)
{
struct hal_srng *srng = hal_get_srng(hal, HAL_SRNG_SW2TCL1);
QDF_STATUS status;
hal->tcl_reg_write_wq =
qdf_alloc_high_prior_ordered_workqueue("hal_tcl_reg_write_wq");
if (!hal->tcl_reg_write_wq) {
hal_err("hal_tcl_reg_write_wq alloc failed");
return QDF_STATUS_E_NOMEM;
}
status = qdf_create_work(0, &hal->tcl_reg_write_work,
hal_tcl_reg_write_work, hal);
if (status != QDF_STATUS_SUCCESS) {
hal_err("tcl_reg_write_work create failed");
goto fail;
}
status = qdf_timer_init(hal->qdf_dev, &hal->tcl_reg_write_timer,
hal_tcl_reg_write_timer, (void *)srng,
QDF_TIMER_TYPE_WAKE_APPS);
if (status != QDF_STATUS_SUCCESS) {
hal_err("tcl_reg_write_timer init failed");
goto fail;
}
qdf_atomic_init(&hal->tcl_work_active);
return QDF_STATUS_SUCCESS;
fail:
qdf_destroy_workqueue(0, hal->tcl_reg_write_wq);
return status;
}
/**
* hal_delayed_tcl_reg_write_deinit() - De-Initialize delayed TCL reg writes
* @hal_soc: hal_soc pointer
*
* De-initialize main data structures to process TCL register writes in a
* delayed workqueue.
*
* Return: None
*/
static void hal_delayed_tcl_reg_write_deinit(struct hal_soc *hal)
{
qdf_timer_stop(&hal->tcl_reg_write_timer);
qdf_timer_free(&hal->tcl_reg_write_timer);
__hal_flush_tcl_reg_write_work(hal);
qdf_flush_workqueue(0, hal->tcl_reg_write_wq);
qdf_destroy_workqueue(0, hal->tcl_reg_write_wq);
}
#else
static inline QDF_STATUS hal_delayed_tcl_reg_write_init(struct hal_soc *hal)
{
return QDF_STATUS_SUCCESS;
}
static inline void hal_delayed_tcl_reg_write_deinit(struct hal_soc *hal)
{
}
#endif
#ifdef FEATURE_HAL_DELAYED_REG_WRITE_V2
void hal_delayed_reg_write(struct hal_soc *hal_soc,
struct hal_srng *srng,
void __iomem *addr,
uint32_t value)
{
switch (srng->ring_type) {
case TCL_DATA:
if (hal_is_reg_write_tput_level_high(hal_soc)) {
hal_soc->tcl_direct = true;
if (srng->reg_write_in_progress ||
!qdf_atomic_read(&hal_soc->tcl_work_active)) {
/*
* Now the delayed work have either completed
* the writing or not even scheduled and would
* be blocked by SRNG_LOCK, hence it is fine to
* do direct write here.
*/
srng->last_reg_wr_val = srng->u.src_ring.hp;
srng->wstats.direct++;
hal_write_address_32_mb(hal_soc, addr,
srng->last_reg_wr_val,
false);
} else {
hal_soc->tcl_stats.direct_timer_set++;
qdf_timer_mod(&hal_soc->tcl_reg_write_timer, 1);
}
} else {
hal_soc->tcl_direct = false;
if (srng->reg_write_in_progress) {
srng->wstats.coalesces++;
} else {
hal_tcl_reg_write_enqueue(hal_soc, srng,
addr, value);
}
}
break;
default:
qdf_atomic_inc(&hal_soc->stats.wstats.direct);
srng->wstats.direct++;
hal_write_address_32_mb(hal_soc, addr, value, false);
break;
}
}
#else
#ifdef FEATURE_HAL_DELAYED_REG_WRITE
void hal_delayed_reg_write(struct hal_soc *hal_soc,
struct hal_srng *srng,
void __iomem *addr,
uint32_t value)
{
if (pld_is_device_awake(hal_soc->qdf_dev->dev) ||
hal_is_reg_write_tput_level_high(hal_soc)) {
qdf_atomic_inc(&hal_soc->stats.wstats.direct);
srng->wstats.direct++;
hal_write_address_32_mb(hal_soc, addr, value, false);
} else {
hal_reg_write_enqueue(hal_soc, srng, addr, value);
}
}
#endif
#endif
/**
* hal_attach - Initialize HAL layer
* @hif_handle: Opaque HIF handle
* @qdf_dev: QDF device
*
* Return: Opaque HAL SOC handle
* NULL on failure (if given ring is not available)
*
* This function should be called as part of HIF initialization (for accessing
* copy engines). DP layer will get hal_soc handle using hif_get_hal_handle()
*
*/
void *hal_attach(struct hif_opaque_softc *hif_handle, qdf_device_t qdf_dev)
{
struct hal_soc *hal;
int i;
hal = qdf_mem_malloc(sizeof(*hal));
if (!hal) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s: hal_soc allocation failed", __func__);
goto fail0;
}
hal->hif_handle = hif_handle;
hal->dev_base_addr = hif_get_dev_ba(hif_handle); /* UMAC */
hal->dev_base_addr_ce = hif_get_dev_ba_ce(hif_handle); /* CE */
hal->qdf_dev = qdf_dev;
hal->shadow_rdptr_mem_vaddr = (uint32_t *)qdf_mem_alloc_consistent(
qdf_dev, qdf_dev->dev, sizeof(*(hal->shadow_rdptr_mem_vaddr)) *
HAL_SRNG_ID_MAX, &(hal->shadow_rdptr_mem_paddr));
if (!hal->shadow_rdptr_mem_paddr) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s: hal->shadow_rdptr_mem_paddr allocation failed",
__func__);
goto fail1;
}
qdf_mem_zero(hal->shadow_rdptr_mem_vaddr,
sizeof(*(hal->shadow_rdptr_mem_vaddr)) * HAL_SRNG_ID_MAX);
hal->shadow_wrptr_mem_vaddr =
(uint32_t *)qdf_mem_alloc_consistent(qdf_dev, qdf_dev->dev,
sizeof(*(hal->shadow_wrptr_mem_vaddr)) * HAL_MAX_LMAC_RINGS,
&(hal->shadow_wrptr_mem_paddr));
if (!hal->shadow_wrptr_mem_vaddr) {
QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
"%s: hal->shadow_wrptr_mem_vaddr allocation failed",
__func__);
goto fail2;
}
qdf_mem_zero(hal->shadow_wrptr_mem_vaddr,
sizeof(*(hal->shadow_wrptr_mem_vaddr)) * HAL_MAX_LMAC_RINGS);
for (i = 0; i < HAL_SRNG_ID_MAX; i++) {
hal->srng_list[i].initialized = 0;
hal->srng_list[i].ring_id = i;
}
qdf_spinlock_create(&hal->register_access_lock);
hal->register_window = 0;
hal->target_type = hal_get_target_type(hal_soc_to_hal_soc_handle(hal));
hal_target_based_configure(hal);
hal_reg_write_fail_history_init(hal);
qdf_minidump_log(hal, sizeof(*hal), "hal_soc");
qdf_atomic_init(&hal->active_work_cnt);
hal_delayed_reg_write_init(hal);
hal_delayed_tcl_reg_write_init(hal);
return (void *)hal;
fail2:
qdf_mem_free_consistent(qdf_dev, qdf_dev->dev,
sizeof(*(hal->shadow_rdptr_mem_vaddr)) * HAL_SRNG_ID_MAX,
hal->shadow_rdptr_mem_vaddr, hal->shadow_rdptr_mem_paddr, 0);
fail1:
qdf_mem_free(hal);
fail0:
return NULL;
}
qdf_export_symbol(hal_attach);
/**
* hal_mem_info - Retrieve hal memory base address
*
* @hal_soc: Opaque HAL SOC handle
* @mem: pointer to structure to be updated with hal mem info
*/
void hal_get_meminfo(hal_soc_handle_t hal_soc_hdl, struct hal_mem_info *mem)
{
struct hal_soc *hal = (struct hal_soc *)hal_soc_hdl;
mem->dev_base_addr = (void *)hal->dev_base_addr;
mem->shadow_rdptr_mem_vaddr = (void *)hal->shadow_rdptr_mem_vaddr;
mem->shadow_wrptr_mem_vaddr = (void *)hal->shadow_wrptr_mem_vaddr;
mem->shadow_rdptr_mem_paddr = (void *)hal->shadow_rdptr_mem_paddr;
mem->shadow_wrptr_mem_paddr = (void *)hal->shadow_wrptr_mem_paddr;
hif_read_phy_mem_base((void *)hal->hif_handle,
(qdf_dma_addr_t *)&mem->dev_base_paddr);
return;
}
qdf_export_symbol(hal_get_meminfo);
/**
* hal_detach - Detach HAL layer
* @hal_soc: HAL SOC handle
*
* Return: Opaque HAL SOC handle
* NULL on failure (if given ring is not available)
*
* This function should be called as part of HIF initialization (for accessing
* copy engines). DP layer will get hal_soc handle using hif_get_hal_handle()
*
*/
extern void hal_detach(void *hal_soc)
{
struct hal_soc *hal = (struct hal_soc *)hal_soc;
hal_delayed_reg_write_deinit(hal);
hal_delayed_tcl_reg_write_deinit(hal);
qdf_mem_free_consistent(hal->qdf_dev, hal->qdf_dev->dev,
sizeof(*(hal->shadow_rdptr_mem_vaddr)) * HAL_SRNG_ID_MAX,
hal->shadow_rdptr_mem_vaddr, hal->shadow_rdptr_mem_paddr, 0);
qdf_mem_free_consistent(hal->qdf_dev, hal->qdf_dev->dev,
sizeof(*(hal->shadow_wrptr_mem_vaddr)) * HAL_MAX_LMAC_RINGS,
hal->shadow_wrptr_mem_vaddr, hal->shadow_wrptr_mem_paddr, 0);
qdf_minidump_remove(hal);
qdf_mem_free(hal);
return;
}
qdf_export_symbol(hal_detach);
/**
* hal_ce_dst_setup - Initialize CE destination ring registers
* @hal_soc: HAL SOC handle
* @srng: SRNG ring pointer
*/
static inline void hal_ce_dst_setup(struct hal_soc *hal, struct hal_srng *srng,
int ring_num)
{
uint32_t reg_val = 0;
uint32_t reg_addr;
struct hal_hw_srng_config *ring_config =
HAL_SRNG_CONFIG(hal, CE_DST);
/* set DEST_MAX_LENGTH according to ce assignment */
reg_addr = HWIO_WFSS_CE_CHANNEL_DST_R0_DEST_CTRL_ADDR(
ring_config->reg_start[R0_INDEX] +
(ring_num * ring_config->reg_size[R0_INDEX]));
reg_val = HAL_REG_READ(hal, reg_addr);
reg_val &= ~HWIO_WFSS_CE_CHANNEL_DST_R0_DEST_CTRL_DEST_MAX_LENGTH_BMSK;
reg_val |= srng->u.dst_ring.max_buffer_length &
HWIO_WFSS_CE_CHANNEL_DST_R0_DEST_CTRL_DEST_MAX_LENGTH_BMSK;
HAL_REG_WRITE(hal, reg_addr, reg_val);
if (srng->prefetch_timer) {
reg_addr = HWIO_WFSS_CE_CHANNEL_DST_R0_DEST_RING_CONSUMER_PREFETCH_TIMER_ADDR(
ring_config->reg_start[R0_INDEX] +
(ring_num * ring_config->reg_size[R0_INDEX]));
reg_val = HAL_REG_READ(hal, reg_addr);
reg_val &= ~HWIO_WFSS_CE_CHANNEL_DST_R0_DEST_RING_CONSUMER_PREFETCH_TIMER_RMSK;
reg_val |= srng->prefetch_timer;
HAL_REG_WRITE(hal, reg_addr, reg_val);
reg_val = HAL_REG_READ(hal, reg_addr);
}
}
/**
* hal_reo_read_write_ctrl_ix - Read or write REO_DESTINATION_RING_CTRL_IX
* @hal: HAL SOC handle
* @read: boolean value to indicate if read or write
* @ix0: pointer to store IX0 reg value
* @ix1: pointer to store IX1 reg value
* @ix2: pointer to store IX2 reg value
* @ix3: pointer to store IX3 reg value
*/
void hal_reo_read_write_ctrl_ix(hal_soc_handle_t hal_soc_hdl, bool read,
uint32_t *ix0, uint32_t *ix1,
uint32_t *ix2, uint32_t *ix3)
{
uint32_t reg_offset;
struct hal_soc *hal = (struct hal_soc *)hal_soc_hdl;
if (read) {
if (ix0) {
reg_offset =
HWIO_REO_R0_DESTINATION_RING_CTRL_IX_0_ADDR(
SEQ_WCSS_UMAC_REO_REG_OFFSET);
*ix0 = HAL_REG_READ(hal, reg_offset);
}
if (ix1) {
reg_offset =
HWIO_REO_R0_DESTINATION_RING_CTRL_IX_1_ADDR(
SEQ_WCSS_UMAC_REO_REG_OFFSET);
*ix1 = HAL_REG_READ(hal, reg_offset);
}
if (ix2) {
reg_offset =
HWIO_REO_R0_DESTINATION_RING_CTRL_IX_2_ADDR(
SEQ_WCSS_UMAC_REO_REG_OFFSET);
*ix2 = HAL_REG_READ(hal, reg_offset);
}
if (ix3) {
reg_offset =
HWIO_REO_R0_DESTINATION_RING_CTRL_IX_3_ADDR(
SEQ_WCSS_UMAC_REO_REG_OFFSET);
*ix3 = HAL_REG_READ(hal, reg_offset);
}
} else {
if (ix0) {
reg_offset =
HWIO_REO_R0_DESTINATION_RING_CTRL_IX_0_ADDR(
SEQ_WCSS_UMAC_REO_REG_OFFSET);
HAL_REG_WRITE_CONFIRM_RETRY(hal, reg_offset,
*ix0, true);
}
if (ix1) {
reg_offset =
HWIO_REO_R0_DESTINATION_RING_CTRL_IX_1_ADDR(
SEQ_WCSS_UMAC_REO_REG_OFFSET);
HAL_REG_WRITE_CONFIRM_RETRY(hal, reg_offset,
*ix1, true);
}
if (ix2) {
reg_offset =
HWIO_REO_R0_DESTINATION_RING_CTRL_IX_2_ADDR(
SEQ_WCSS_UMAC_REO_REG_OFFSET);
HAL_REG_WRITE_CONFIRM_RETRY(hal, reg_offset,
*ix2, true);
}
if (ix3) {
reg_offset =
HWIO_REO_R0_DESTINATION_RING_CTRL_IX_3_ADDR(
SEQ_WCSS_UMAC_REO_REG_OFFSET);
HAL_REG_WRITE_CONFIRM_RETRY(hal, reg_offset,
*ix3, true);
}
}
}
/**
* hal_srng_dst_set_hp_paddr_confirm() - Set physical address to dest ring head
* pointer and confirm that write went through by reading back the value
* @srng: sring pointer
* @paddr: physical address
*
* Return: None
*/
void hal_srng_dst_set_hp_paddr_confirm(struct hal_srng *srng, uint64_t paddr)
{
SRNG_DST_REG_WRITE_CONFIRM(srng, HP_ADDR_LSB, paddr & 0xffffffff);
SRNG_DST_REG_WRITE_CONFIRM(srng, HP_ADDR_MSB, paddr >> 32);
}
/**
* hal_srng_dst_init_hp() - Initialize destination ring head
* pointer
* @hal_soc: hal_soc handle
* @srng: sring pointer
* @vaddr: virtual address
*/
void hal_srng_dst_init_hp(struct hal_soc_handle *hal_soc,
struct hal_srng *srng,
uint32_t *vaddr)
{
uint32_t reg_offset;
struct hal_soc *hal = (struct hal_soc *)hal_soc;
if (!srng)
return;
srng->u.dst_ring.hp_addr = vaddr;
reg_offset = SRNG_DST_ADDR(srng, HP) - hal->dev_base_addr;
HAL_REG_WRITE_CONFIRM_RETRY(
hal, reg_offset, srng->u.dst_ring.cached_hp, true);
if (vaddr) {
*srng->u.dst_ring.hp_addr = srng->u.dst_ring.cached_hp;
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR,
"hp_addr=%pK, cached_hp=%d, hp=%d",
(void *)srng->u.dst_ring.hp_addr,
srng->u.dst_ring.cached_hp,
*srng->u.dst_ring.hp_addr);
}
}
/**
* hal_srng_hw_init - Private function to initialize SRNG HW
* @hal_soc: HAL SOC handle
* @srng: SRNG ring pointer
*/
static inline void hal_srng_hw_init(struct hal_soc *hal,
struct hal_srng *srng)
{
if (srng->ring_dir == HAL_SRNG_SRC_RING)
hal_srng_src_hw_init(hal, srng);
else
hal_srng_dst_hw_init(hal, srng);
}
#ifdef CONFIG_SHADOW_V2
#define ignore_shadow false
#define CHECK_SHADOW_REGISTERS true
#else
#define ignore_shadow true
#define CHECK_SHADOW_REGISTERS false
#endif
/**
* hal_srng_setup - Initialize HW SRNG ring.
* @hal_soc: Opaque HAL SOC handle
* @ring_type: one of the types from hal_ring_type
* @ring_num: Ring number if there are multiple rings of same type (staring
* from 0)
* @mac_id: valid MAC Id should be passed if ring type is one of lmac rings
* @ring_params: SRNG ring params in hal_srng_params structure.
* Callers are expected to allocate contiguous ring memory of size
* 'num_entries * entry_size' bytes and pass the physical and virtual base
* addresses through 'ring_base_paddr' and 'ring_base_vaddr' in
* hal_srng_params structure. Ring base address should be 8 byte aligned
* and size of each ring entry should be queried using the API
* hal_srng_get_entrysize
*
* Return: Opaque pointer to ring on success
* NULL on failure (if given ring is not available)
*/
void *hal_srng_setup(void *hal_soc, int ring_type, int ring_num,
int mac_id, struct hal_srng_params *ring_params)
{
int ring_id;
struct hal_soc *hal = (struct hal_soc *)hal_soc;
struct hal_srng *srng;
struct hal_hw_srng_config *ring_config =
HAL_SRNG_CONFIG(hal, ring_type);
void *dev_base_addr;
int i;
ring_id = hal_get_srng_ring_id(hal_soc, ring_type, ring_num, mac_id);
if (ring_id < 0)
return NULL;
hal_verbose_debug("mac_id %d ring_id %d", mac_id, ring_id);
srng = hal_get_srng(hal_soc, ring_id);
if (srng->initialized) {
hal_verbose_debug("Ring (ring_type, ring_num) already initialized");
return NULL;
}
dev_base_addr = hal->dev_base_addr;
srng->ring_id = ring_id;
srng->ring_type = ring_type;
srng->ring_dir = ring_config->ring_dir;
srng->ring_base_paddr = ring_params->ring_base_paddr;
srng->ring_base_vaddr = ring_params->ring_base_vaddr;
srng->entry_size = ring_config->entry_size;
srng->num_entries = ring_params->num_entries;
srng->ring_size = srng->num_entries * srng->entry_size;
srng->ring_size_mask = srng->ring_size - 1;
srng->msi_addr = ring_params->msi_addr;
srng->msi_data = ring_params->msi_data;
srng->intr_timer_thres_us = ring_params->intr_timer_thres_us;
srng->intr_batch_cntr_thres_entries =
ring_params->intr_batch_cntr_thres_entries;
srng->prefetch_timer = ring_params->prefetch_timer;
srng->hal_soc = hal_soc;
for (i = 0 ; i < MAX_SRNG_REG_GROUPS; i++) {
srng->hwreg_base[i] = dev_base_addr + ring_config->reg_start[i]
+ (ring_num * ring_config->reg_size[i]);
}
/* Zero out the entire ring memory */
qdf_mem_zero(srng->ring_base_vaddr, (srng->entry_size *
srng->num_entries) << 2);
srng->flags = ring_params->flags;
#ifdef BIG_ENDIAN_HOST
/* TODO: See if we should we get these flags from caller */
srng->flags |= HAL_SRNG_DATA_TLV_SWAP;
srng->flags |= HAL_SRNG_MSI_SWAP;
srng->flags |= HAL_SRNG_RING_PTR_SWAP;
#endif
if (srng->ring_dir == HAL_SRNG_SRC_RING) {
srng->u.src_ring.hp = 0;
srng->u.src_ring.reap_hp = srng->ring_size -
srng->entry_size;
srng->u.src_ring.tp_addr =
&(hal->shadow_rdptr_mem_vaddr[ring_id]);
srng->u.src_ring.low_threshold =
ring_params->low_threshold * srng->entry_size;
if (ring_config->lmac_ring) {
/* For LMAC rings, head pointer updates will be done
* through FW by writing to a shared memory location
*/
srng->u.src_ring.hp_addr =
&(hal->shadow_wrptr_mem_vaddr[ring_id -
HAL_SRNG_LMAC1_ID_START]);
srng->flags |= HAL_SRNG_LMAC_RING;
} else if (ignore_shadow || (srng->u.src_ring.hp_addr == 0)) {
srng->u.src_ring.hp_addr =
hal_get_window_address(hal,
SRNG_SRC_ADDR(srng, HP));
if (CHECK_SHADOW_REGISTERS) {
QDF_TRACE(QDF_MODULE_ID_TXRX,
QDF_TRACE_LEVEL_ERROR,
"%s: Ring (%d, %d) missing shadow config",
__func__, ring_type, ring_num);
}
} else {
hal_validate_shadow_register(hal,
SRNG_SRC_ADDR(srng, HP),
srng->u.src_ring.hp_addr);
}
} else {
/* During initialization loop count in all the descriptors
* will be set to zero, and HW will set it to 1 on completing
* descriptor update in first loop, and increments it by 1 on
* subsequent loops (loop count wraps around after reaching
* 0xffff). The 'loop_cnt' in SW ring state is the expected
* loop count in descriptors updated by HW (to be processed
* by SW).
*/
srng->u.dst_ring.loop_cnt = 1;
srng->u.dst_ring.tp = 0;
srng->u.dst_ring.hp_addr =
&(hal->shadow_rdptr_mem_vaddr[ring_id]);
if (ring_config->lmac_ring) {
/* For LMAC rings, tail pointer updates will be done
* through FW by writing to a shared memory location
*/
srng->u.dst_ring.tp_addr =
&(hal->shadow_wrptr_mem_vaddr[ring_id -
HAL_SRNG_LMAC1_ID_START]);
srng->flags |= HAL_SRNG_LMAC_RING;
} else if (ignore_shadow || srng->u.dst_ring.tp_addr == 0) {
srng->u.dst_ring.tp_addr =
hal_get_window_address(hal,
SRNG_DST_ADDR(srng, TP));
if (CHECK_SHADOW_REGISTERS) {
QDF_TRACE(QDF_MODULE_ID_TXRX,
QDF_TRACE_LEVEL_ERROR,
"%s: Ring (%d, %d) missing shadow config",
__func__, ring_type, ring_num);
}
} else {
hal_validate_shadow_register(hal,
SRNG_DST_ADDR(srng, TP),
srng->u.dst_ring.tp_addr);
}
}
if (!(ring_config->lmac_ring)) {
hal_srng_hw_init(hal, srng);
if (ring_type == CE_DST) {
srng->u.dst_ring.max_buffer_length = ring_params->max_buffer_length;
hal_ce_dst_setup(hal, srng, ring_num);
}
}
SRNG_LOCK_INIT(&srng->lock);
srng->srng_event = 0;
srng->initialized = true;
return (void *)srng;
}
qdf_export_symbol(hal_srng_setup);
/**
* hal_srng_cleanup - Deinitialize HW SRNG ring.
* @hal_soc: Opaque HAL SOC handle
* @hal_srng: Opaque HAL SRNG pointer
*/
void hal_srng_cleanup(void *hal_soc, hal_ring_handle_t hal_ring_hdl)
{
struct hal_srng *srng = (struct hal_srng *)hal_ring_hdl;
SRNG_LOCK_DESTROY(&srng->lock);
srng->initialized = 0;
}
qdf_export_symbol(hal_srng_cleanup);
/**
* hal_srng_get_entrysize - Returns size of ring entry in bytes
* @hal_soc: Opaque HAL SOC handle
* @ring_type: one of the types from hal_ring_type
*
*/
uint32_t hal_srng_get_entrysize(void *hal_soc, int ring_type)
{
struct hal_soc *hal = (struct hal_soc *)hal_soc;
struct hal_hw_srng_config *ring_config =
HAL_SRNG_CONFIG(hal, ring_type);
return ring_config->entry_size << 2;
}
qdf_export_symbol(hal_srng_get_entrysize);
/**
* hal_srng_max_entries - Returns maximum possible number of ring entries
* @hal_soc: Opaque HAL SOC handle
* @ring_type: one of the types from hal_ring_type
*
* Return: Maximum number of entries for the given ring_type
*/
uint32_t hal_srng_max_entries(void *hal_soc, int ring_type)
{
struct hal_soc *hal = (struct hal_soc *)hal_soc;
struct hal_hw_srng_config *ring_config =
HAL_SRNG_CONFIG(hal, ring_type);
return ring_config->max_size / ring_config->entry_size;
}
qdf_export_symbol(hal_srng_max_entries);
enum hal_srng_dir hal_srng_get_dir(void *hal_soc, int ring_type)
{
struct hal_soc *hal = (struct hal_soc *)hal_soc;
struct hal_hw_srng_config *ring_config =
HAL_SRNG_CONFIG(hal, ring_type);
return ring_config->ring_dir;
}
/**
* hal_srng_dump - Dump ring status
* @srng: hal srng pointer
*/
void hal_srng_dump(struct hal_srng *srng)
{
if (srng->ring_dir == HAL_SRNG_SRC_RING) {
hal_debug("=== SRC RING %d ===", srng->ring_id);
hal_debug("hp %u, reap_hp %u, tp %u, cached tp %u",
srng->u.src_ring.hp,
srng->u.src_ring.reap_hp,
*srng->u.src_ring.tp_addr,
srng->u.src_ring.cached_tp);
} else {
hal_debug("=== DST RING %d ===", srng->ring_id);
hal_debug("tp %u, hp %u, cached tp %u, loop_cnt %u",
srng->u.dst_ring.tp,
*srng->u.dst_ring.hp_addr,
srng->u.dst_ring.cached_hp,
srng->u.dst_ring.loop_cnt);
}
}
/**
* hal_get_srng_params - Retrieve SRNG parameters for a given ring from HAL
*
* @hal_soc: Opaque HAL SOC handle
* @hal_ring: Ring pointer (Source or Destination ring)
* @ring_params: SRNG parameters will be returned through this structure
*/
extern void hal_get_srng_params(hal_soc_handle_t hal_soc_hdl,
hal_ring_handle_t hal_ring_hdl,
struct hal_srng_params *ring_params)
{
struct hal_srng *srng = (struct hal_srng *)hal_ring_hdl;
int i =0;
ring_params->ring_id = srng->ring_id;
ring_params->ring_dir = srng->ring_dir;
ring_params->entry_size = srng->entry_size;
ring_params->ring_base_paddr = srng->ring_base_paddr;
ring_params->ring_base_vaddr = srng->ring_base_vaddr;
ring_params->num_entries = srng->num_entries;
ring_params->msi_addr = srng->msi_addr;
ring_params->msi_data = srng->msi_data;
ring_params->intr_timer_thres_us = srng->intr_timer_thres_us;
ring_params->intr_batch_cntr_thres_entries =
srng->intr_batch_cntr_thres_entries;
ring_params->low_threshold = srng->u.src_ring.low_threshold;
ring_params->flags = srng->flags;
ring_params->ring_id = srng->ring_id;
for (i = 0 ; i < MAX_SRNG_REG_GROUPS; i++)
ring_params->hwreg_base[i] = srng->hwreg_base[i];
}
qdf_export_symbol(hal_get_srng_params);
void hal_set_low_threshold(hal_ring_handle_t hal_ring_hdl,
uint32_t low_threshold)
{
struct hal_srng *srng = (struct hal_srng *)hal_ring_hdl;
srng->u.src_ring.low_threshold = low_threshold * srng->entry_size;
}
qdf_export_symbol(hal_set_low_threshold);
#ifdef FORCE_WAKE
void hal_set_init_phase(hal_soc_handle_t soc, bool init_phase)
{
struct hal_soc *hal_soc = (struct hal_soc *)soc;
hal_soc->init_phase = init_phase;
}
#endif /* FORCE_WAKE */