[PATCH v5 07/13] wifi: ath12k: add support for fixed QMI firmware memory

Raj Kumar Bhagat
2025-01-30T04:36:19Z

IPQ5332 firmware supports only fixed QMI firmware memory.

Hence, add support to read reserved fixed memory region from
device-tree and provide the reserved memory segments for
firmware to use during QMI firmware memory request.

Note that the ability to set the fixed memory will be introduced in
a subsequent patch. Currently, the flag remains unset by default,
ensuring that existing chipsets are unaffected.

Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.3.1-00130-QCAHKSWPL_SILICONZ-1
Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.1.1-00210-QCAHKSWPL_SILICONZ-1

Signed-off-by: Raj Kumar Bhagat <quic_rajkbhag@quicinc.com>
---
drivers/net/wireless/ath/ath12k/core.c | 24 ++++
drivers/net/wireless/ath/ath12k/core.h | 3 +
drivers/net/wireless/ath/ath12k/hw.c | 4 +
drivers/net/wireless/ath/ath12k/hw.h | 1 +
drivers/net/wireless/ath/ath12k/qmi.c | 188 ++++++++++++++++++++++---
5 files changed, 204 insertions(+), 16 deletions(-)

diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
index 60c077b016b4..cbe4d6274768 100644
--- a/drivers/net/wireless/ath/ath12k/core.c
+++ b/drivers/net/wireless/ath/ath12k/core.c
@@ -609,6 +609,30 @@ u32 ath12k_core_get_max_num_tids(struct ath12k_base *ab)
return TARGET_NUM_TIDS(SINGLE);
}

+struct device_node *ath12k_core_get_reserved_mem_by_name(struct ath12k_base *ab,
+ const char *name)
+{
+ struct device *dev = ab->dev;
+ struct device_node *mem_np;
+ int index;
+
+ index = of_property_match_string(dev->of_node, "memory-region-names", name);
+ if (index < 0) {
+ ath12k_dbg(ab, ATH12K_DBG_BOOT,
+ "memory region %s not found\n", name);
+ return NULL;
+ }
+
+ mem_np = of_parse_phandle(dev->of_node, "memory-region", index);
+ if (!mem_np) {
+ ath12k_dbg(ab, ATH12K_DBG_BOOT,
+ "failed to parse memory region %s\n", name);
+ return NULL;
+ }
+
+ return mem_np;
+}
+
static void ath12k_core_stop(struct ath12k_base *ab)
{
ath12k_core_stopped(ab);
diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
index 22ade838b99f..fa2eadd60c44 100644
--- a/drivers/net/wireless/ath/ath12k/core.h
+++ b/drivers/net/wireless/ath/ath12k/core.h
@@ -239,6 +239,7 @@ enum ath12k_dev_flags {
ATH12K_FLAG_EXT_IRQ_ENABLED,
ATH12K_FLAG_QMI_FW_READY_COMPLETE,
ATH12K_FLAG_FTM_SEGMENTED,
+ ATH12K_FLAG_FIXED_MEM_REGION,
};

struct ath12k_tx_conf {
@@ -1203,6 +1204,8 @@ u32 ath12k_core_get_max_peers_per_radio(struct ath12k_base *ab);
u32 ath12k_core_get_max_num_tids(struct ath12k_base *ab);

void ath12k_core_hw_group_set_mlo_capable(struct ath12k_hw_group *ag);
+struct device_node *ath12k_core_get_reserved_mem_by_name(struct ath12k_base *ab,
+ const char *name);

static inline const char *ath12k_scan_state_str(enum ath12k_scan_state state)
{
diff --git a/drivers/net/wireless/ath/ath12k/hw.c b/drivers/net/wireless/ath/ath12k/hw.c
index 91aecca566a4..b4d5651973b7 100644
--- a/drivers/net/wireless/ath/ath12k/hw.c
+++ b/drivers/net/wireless/ath/ath12k/hw.c
@@ -1322,6 +1322,7 @@ static const struct ath12k_hw_params ath12k_hw_params[] = {

.ce_ie_addr = NULL,
.ce_remap = NULL,
+ .bdf_addr_offset = 0,
},
{
.name = "wcn7850 hw2.0",
@@ -1406,6 +1407,7 @@ static const struct ath12k_hw_params ath12k_hw_params[] = {

.ce_ie_addr = NULL,
.ce_remap = NULL,
+ .bdf_addr_offset = 0,
},
{
.name = "qcn9274 hw2.0",
@@ -1486,6 +1488,7 @@ static const struct ath12k_hw_params ath12k_hw_params[] = {

.ce_ie_addr = NULL,
.ce_remap = NULL,
+ .bdf_addr_offset = 0,
},
{
.name = "ipq5332 hw1.0",
@@ -1561,6 +1564,7 @@ static const struct ath12k_hw_params ath12k_hw_params[] = {

.ce_ie_addr = &ath12k_ce_ie_addr_ipq5332,
.ce_remap = &ath12k_ce_remap_ipq5332,
+ .bdf_addr_offset = 0xC00000,
},
};

diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h
index 6a75af093f31..5123d2f51865 100644
--- a/drivers/net/wireless/ath/ath12k/hw.h
+++ b/drivers/net/wireless/ath/ath12k/hw.h
@@ -229,6 +229,7 @@ struct ath12k_hw_params {

const struct ce_ie_addr *ce_ie_addr;
const struct ce_remap *ce_remap;
+ u32 bdf_addr_offset;
};

struct ath12k_hw_ops {
diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c
index 3d1f5309c962..d75769ae03bb 100644
--- a/drivers/net/wireless/ath/ath12k/qmi.c
+++ b/drivers/net/wireless/ath/ath12k/qmi.c
@@ -11,6 +11,8 @@
#include "debug.h"
#include <linux/of.h>
#include <linux/firmware.h>
+#include <linux/of_address.h>
+#include <linux/ioport.h>

#define SLEEP_CLOCK_SELECT_INTERNAL_BIT 0x02
#define HOST_CSTATE_BIT 0x04
@@ -2384,7 +2386,8 @@ int ath12k_qmi_respond_fw_mem_request(struct ath12k_base *ab)
* failure to firmware and firmware then request multiple blocks of
* small chunk size memory.
*/
- if (ab->qmi.target_mem_delayed) {
+ if (!test_bit(ATH12K_FLAG_FIXED_MEM_REGION, &ab->dev_flags) &&
+ ab->qmi.target_mem_delayed) {
delayed = true;
ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi delays mem_request %d\n",
ab->qmi.mem_seg_count);
@@ -2448,6 +2451,7 @@ static void ath12k_qmi_free_mlo_mem_chunk(struct ath12k_base *ab,
{
struct ath12k_hw_group *ag = ab->ag;
struct target_mem_chunk *mlo_chunk;
+ bool fixed_mem;

lockdep_assert_held(&ag->mutex);

@@ -2459,8 +2463,13 @@ static void ath12k_qmi_free_mlo_mem_chunk(struct ath12k_base *ab,
return;
}

+ fixed_mem = test_bit(ATH12K_FLAG_FIXED_MEM_REGION, &ab->dev_flags);
mlo_chunk = &ag->mlo_mem.chunk[idx];
- if (mlo_chunk->v.addr) {
+
+ if (fixed_mem && mlo_chunk->v.ioaddr) {
+ iounmap(mlo_chunk->v.ioaddr);
+ mlo_chunk->v.ioaddr = NULL;
+ } else if (mlo_chunk->v.addr) {
dma_free_coherent(ab->dev,
mlo_chunk->size,
mlo_chunk->v.addr,
@@ -2470,7 +2479,10 @@ static void ath12k_qmi_free_mlo_mem_chunk(struct ath12k_base *ab,

mlo_chunk->paddr = 0;
mlo_chunk->size = 0;
- chunk->v.addr = NULL;
+ if (fixed_mem)
+ chunk->v.ioaddr = NULL;
+ else
+ chunk->v.addr = NULL;
chunk->paddr = 0;
chunk->size = 0;
}
@@ -2481,19 +2493,24 @@ static void ath12k_qmi_free_target_mem_chunk(struct ath12k_base *ab)
int i, mlo_idx;

for (i = 0, mlo_idx = 0; i < ab->qmi.mem_seg_count; i++) {
- if (!ab->qmi.target_mem[i].v.addr)
- continue;
-
if (ab->qmi.target_mem[i].type == MLO_GLOBAL_MEM_REGION_TYPE) {
ath12k_qmi_free_mlo_mem_chunk(ab,
&ab->qmi.target_mem[i],
mlo_idx++);
} else {
- dma_free_coherent(ab->dev,
- ab->qmi.target_mem[i].prev_size,
- ab->qmi.target_mem[i].v.addr,
- ab->qmi.target_mem[i].paddr);
- ab->qmi.target_mem[i].v.addr = NULL;
+ if (test_bit(ATH12K_FLAG_FIXED_MEM_REGION, &ab->dev_flags) &&
+ ab->qmi.target_mem[i].v.ioaddr) {
+ iounmap(ab->qmi.target_mem[i].v.ioaddr);
+ ab->qmi.target_mem[i].v.ioaddr = NULL;
+ } else {
+ if (!ab->qmi.target_mem[i].v.addr)
+ continue;
+ dma_free_coherent(ab->dev,
+ ab->qmi.target_mem[i].prev_size,
+ ab->qmi.target_mem[i].v.addr,
+ ab->qmi.target_mem[i].paddr);
+ ab->qmi.target_mem[i].v.addr = NULL;
+ }
}
}

@@ -2646,6 +2663,136 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab)
return ret;
}

+static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab)
+{
+ struct device_node *mem_node;
+ struct resource res, m3_res;
+ u32 bdf_start_addr;
+ int i, idx, ret;
+
+ for (i = 0, idx = 0; i < ab->qmi.mem_seg_count; i++) {
+ switch (ab->qmi.target_mem[i].type) {
+ case HOST_DDR_REGION_TYPE:
+ mem_node = ath12k_core_get_reserved_mem_by_name(ab, "q6-region");
+ if (!mem_node || of_address_to_resource(mem_node, 0, &res)) {
+ ath12k_dbg(ab, ATH12K_DBG_QMI,
+ "q6-region is not defined in device-tree\n");
+ ret = -ENODEV;
+ goto out;
+ }
+ of_node_put(mem_node);
+
+ if (res.end - res.start + 1 < ab->qmi.target_mem[i].size) {
+ ath12k_dbg(ab, ATH12K_DBG_QMI,
+ "failed to assign mem type %d req size %d avail size %lld\n",
+ ab->qmi.target_mem[i].type,
+ ab->qmi.target_mem[i].size,
+ (res.end - res.start + 1));
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ab->qmi.target_mem[idx].paddr = res.start;
+ ab->qmi.target_mem[idx].v.ioaddr =
+ ioremap(ab->qmi.target_mem[idx].paddr,
+ ab->qmi.target_mem[i].size);
+ if (!ab->qmi.target_mem[idx].v.ioaddr) {
+ ret = -EIO;
+ goto out;
+ }
+ ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size;
+ ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type;
+ idx++;
+ break;
+ case BDF_MEM_REGION_TYPE:
+ mem_node = ath12k_core_get_reserved_mem_by_name(ab, "q6-region");
+ if (!mem_node || of_address_to_resource(mem_node, 0, &res)) {
+ ath12k_dbg(ab, ATH12K_DBG_QMI,
+ "q6-region is not defined to calculate BDF mem in DT\n");
+ ret = -ENODEV;
+ goto out;
+ }
+ of_node_put(mem_node);
+
+ bdf_start_addr = res.start + ab->hw_params->bdf_addr_offset;
+ if (res.end - bdf_start_addr + 1 < ab->qmi.target_mem[i].size) {
+ ath12k_dbg(ab, ATH12K_DBG_QMI,
+ "failed to assign mem type %d req size %d avail size %lld\n",
+ ab->qmi.target_mem[i].type,
+ ab->qmi.target_mem[i].size,
+ (res.end - bdf_start_addr + 1));
+ ret = -EINVAL;
+ goto out;
+ }
+ ab->qmi.target_mem[idx].paddr = bdf_start_addr;
+ ab->qmi.target_mem[idx].v.ioaddr =
+ ioremap(ab->qmi.target_mem[idx].paddr,
+ ab->qmi.target_mem[i].size);
+ if (!ab->qmi.target_mem[idx].v.ioaddr) {
+ ret = -EIO;
+ goto out;
+ }
+ ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size;
+ ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type;
+ idx++;
+ break;
+ case CALDB_MEM_REGION_TYPE:
+ /* Cold boot calibration is not enabled in Ath12k. Hence,
+ * assign paddr = 0.
+ * Once cold boot calibration is enabled add support to
+ * assign reserved memory from DT.
+ */
+ ab->qmi.target_mem[idx].paddr = 0;
+ ab->qmi.target_mem[idx].v.ioaddr = NULL;
+ ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size;
+ ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type;
+ idx++;
+ break;
+ case M3_DUMP_REGION_TYPE:
+ mem_node = ath12k_core_get_reserved_mem_by_name(ab, "m3-dump");
+ if (!mem_node || of_address_to_resource(mem_node, 0, &m3_res)) {
+ ath12k_err(ab, "m3_dump not defined in device-tree\n");
+ ret = -EINVAL;
+ goto out;
+ }
+ of_node_put(mem_node);
+
+ if (m3_res.end - m3_res.start + 1 < ab->qmi.target_mem[i].size) {
+ ath12k_dbg(ab, ATH12K_DBG_QMI,
+ "failed to assign mem type %d req size %d avail size %lld\n",
+ ab->qmi.target_mem[i].type,
+ ab->qmi.target_mem[i].size,
+ (m3_res.end - m3_res.start + 1));
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ab->qmi.target_mem[idx].paddr = m3_res.start;
+ ab->qmi.target_mem[idx].v.ioaddr =
+ ioremap(ab->qmi.target_mem[idx].paddr,
+ ab->qmi.target_mem[i].size);
+ if (!ab->qmi.target_mem[idx].v.ioaddr) {
+ ret = -EIO;
+ goto out;
+ }
+ ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size;
+ ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type;
+ idx++;
+ break;
+ default:
+ ath12k_warn(ab, "qmi ignore invalid mem req type %d\n",
+ ab->qmi.target_mem[i].type);
+ break;
+ }
+ }
+ ab->qmi.mem_seg_count = idx;
+
+ return 0;
+out:
+ ath12k_qmi_free_target_mem_chunk(ab);
+ return ret;
+}
+
/* clang stack usage explodes if this is inlined */
static noinline_for_stack
int ath12k_qmi_request_target_cap(struct ath12k_base *ab)
@@ -3480,11 +3627,20 @@ static void ath12k_qmi_msg_mem_request_cb(struct qmi_handle *qmi_hdl,
msg->mem_seg[i].type, msg->mem_seg[i].size);
}

- ret = ath12k_qmi_alloc_target_mem_chunk(ab);
- if (ret) {
- ath12k_warn(ab, "qmi failed to alloc target memory: %d\n",
- ret);
- return;
+ if (test_bit(ATH12K_FLAG_FIXED_MEM_REGION, &ab->dev_flags)) {
+ ret = ath12k_qmi_assign_target_mem_chunk(ab);
+ if (ret) {
+ ath12k_warn(ab, "failed to assign qmi target memory: %d\n",
+ ret);
+ return;
+ }
+ } else {
+ ret = ath12k_qmi_alloc_target_mem_chunk(ab);
+ if (ret) {
+ ath12k_warn(ab, "qmi failed to alloc target memory: %d\n",
+ ret);
+ return;
+ }
}

ath12k_qmi_driver_event_post(qmi, ATH12K_QMI_EVENT_REQUEST_MEM, NULL);
--
2.34.1