[PATCH RFC 2/2] remoteproc: qcom: wcss-sec: add IPQ8074 support
From: John Macias
Date: Sun Oct 25 2026 - 00:22:46 EST
Add IPQ8074, which uses PAS ID 6 and needs the PRNG AHB clock enabled.
IPQ8074 firmware ships M3 as a separate image whose load address differs
from Q6's. When the node names its regions "q6" and "m3" and gives a
second firmware name, map the M3 region and load the M3 image into it
before the authenticated Q6 image, keeping the order the firmware has
been used with. Load M3 without updating the Q6 relocation base so
da_to_va() keeps translating Q6 addresses. A missing or failed M3 load
stops the Q6 load. Probe fails for a malformed named configuration
(wrong names or counts, a compatible without separate M3 support, or
overlapping regions) instead of picking a different loading policy.
Without memory-region-names nothing changes: extra regions remain
coredump-only.
Tested on a Netgear RAX120 v1 (IPQ8074 hw1.0, WLAN.HK.2.0-01792) only
with the equivalent change on OpenWrt's earlier copy of this driver.
Assisted-by: LLM
Signed-off-by: John Macias <dencity7@xxxxxxxxx>
---
drivers/remoteproc/qcom_q6v5_wcss_sec.c | 97 ++++++++++++++++++++++++-
1 file changed, 96 insertions(+), 1 deletion(-)
diff --git a/drivers/remoteproc/qcom_q6v5_wcss_sec.c b/drivers/remoteproc/qcom_q6v5_wcss_sec.c
index 4837825d1..a4ebb79d0 100644
--- a/drivers/remoteproc/qcom_q6v5_wcss_sec.c
+++ b/drivers/remoteproc/qcom_q6v5_wcss_sec.c
@@ -7,6 +7,7 @@
#include <linux/io.h>
#include <linux/mailbox_client.h>
#include <linux/of_reserved_mem.h>
+#include <linux/overflow.h>
#include <linux/platform_device.h>
#include <linux/soc/qcom/mdt_loader.h>
@@ -30,6 +31,10 @@ struct wcss_sec {
phys_addr_t mem_reloc;
void *mem_region;
size_t mem_size;
+ phys_addr_t m3_phys;
+ void *m3_region;
+ size_t m3_size;
+ const char *m3_fw_name;
const struct wcss_data *desc;
};
@@ -37,6 +42,7 @@ struct wcss_data {
u32 pasid;
const char *ss_name;
bool auto_boot;
+ bool separate_m3;
};
static int wcss_sec_start(struct rproc *rproc)
@@ -106,12 +112,43 @@ static void *wcss_sec_da_to_va(struct rproc *rproc, u64 da, size_t len,
return wcss->mem_region + offset;
}
+/*
+ * IPQ8074 firmware ships M3 as a separate image with its own load address.
+ * Load it into its own reserved region before the authenticated Q6 image.
+ */
+static int wcss_sec_load_m3(struct wcss_sec *wcss)
+{
+ const struct firmware *fw;
+ int ret;
+
+ ret = request_firmware(&fw, wcss->m3_fw_name, wcss->dev);
+ if (ret)
+ return ret;
+
+ /* M3 relocation must not overwrite Q6's da_to_va translation base. */
+ ret = qcom_mdt_load_no_init(wcss->dev, fw, wcss->m3_fw_name,
+ wcss->m3_region, wcss->m3_phys,
+ wcss->m3_size, NULL);
+ release_firmware(fw);
+
+ return ret;
+}
+
static int wcss_sec_load(struct rproc *rproc, const struct firmware *fw)
{
struct wcss_sec *wcss = rproc->priv;
struct device *dev = wcss->dev;
int ret;
+ if (wcss->m3_region) {
+ ret = wcss_sec_load_m3(wcss);
+ if (ret) {
+ dev_err(dev, "unable to load M3 firmware: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+ }
+
ret = qcom_mdt_load(dev, fw, rproc->firmware, wcss->desc->pasid, wcss->mem_region,
wcss->mem_phys, wcss->mem_size, &wcss->mem_reloc);
if (ret)
@@ -206,6 +243,51 @@ static const struct rproc_ops wcss_sec_ops = {
.parse_fw = wcss_sec_dump_segments,
};
+static int wcss_sec_alloc_m3_region(struct wcss_sec *wcss)
+{
+ struct device *dev = wcss->dev;
+ struct device_node *np = dev->of_node;
+ phys_addr_t q6_end, m3_end;
+ struct resource res;
+ int ret;
+
+ /* Unnamed extra regions keep their existing coredump-only meaning. */
+ if (!of_property_present(np, "memory-region-names"))
+ return 0;
+
+ if (!wcss->desc->separate_m3 ||
+ of_property_count_strings(np, "memory-region-names") != 2 ||
+ of_property_match_string(np, "memory-region-names", "q6") != 0 ||
+ of_property_match_string(np, "memory-region-names", "m3") != 1 ||
+ of_reserved_mem_region_count(np) != 2 ||
+ of_property_read_string_index(np, "firmware-name", 1,
+ &wcss->m3_fw_name))
+ return dev_err_probe(dev, -EINVAL,
+ "invalid separate M3 configuration\n");
+
+ ret = of_reserved_mem_region_to_resource(np, 1, &res);
+ if (ret)
+ return dev_err_probe(dev, ret, "unable to acquire M3 region\n");
+
+ if (!wcss->mem_size || !resource_size(&res) ||
+ check_add_overflow(wcss->mem_phys, (phys_addr_t)wcss->mem_size,
+ &q6_end) ||
+ check_add_overflow((phys_addr_t)res.start,
+ (phys_addr_t)resource_size(&res), &m3_end) ||
+ (wcss->mem_phys < m3_end && res.start < q6_end))
+ return dev_err_probe(dev, -EINVAL,
+ "invalid or overlapping Q6/M3 regions\n");
+
+ wcss->m3_phys = res.start;
+ wcss->m3_size = resource_size(&res);
+ wcss->m3_region = devm_ioremap_resource_wc(dev, &res);
+ if (IS_ERR(wcss->m3_region))
+ return dev_err_probe(dev, PTR_ERR(wcss->m3_region),
+ "unable to map M3 region %pR\n", &res);
+
+ return 0;
+}
+
static int wcss_sec_alloc_memory_region(struct wcss_sec *wcss)
{
struct device *dev = wcss->dev;
@@ -227,7 +309,7 @@ static int wcss_sec_alloc_memory_region(struct wcss_sec *wcss)
return PTR_ERR(wcss->mem_region);
}
- return 0;
+ return wcss_sec_alloc_m3_region(wcss);
}
static int wcss_sec_probe(struct platform_device *pdev)
@@ -237,6 +319,7 @@ static int wcss_sec_probe(struct platform_device *pdev)
struct wcss_sec *wcss;
struct clk *sleep_clk;
struct clk *int_clk;
+ struct clk *prng_clk;
struct rproc *rproc;
int ret;
@@ -270,6 +353,11 @@ static int wcss_sec_probe(struct platform_device *pdev)
return dev_err_probe(&pdev->dev, PTR_ERR(int_clk),
"Failed to get interconnect clock\n");
+ prng_clk = devm_clk_get_optional_enabled(&pdev->dev, "prng");
+ if (IS_ERR(prng_clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(prng_clk),
+ "Failed to get prng clock\n");
+
ret = qcom_q6v5_init(&wcss->q6, pdev, rproc,
WCSS_CRASH_REASON, NULL, NULL);
if (ret)
@@ -310,6 +398,12 @@ static const struct wcss_data wcss_sec_ipq5332_res_init = {
.ss_name = "q6wcss",
};
+static const struct wcss_data wcss_sec_ipq8074_res_init = {
+ .pasid = WCSS_PAS_ID,
+ .ss_name = "q6wcss",
+ .separate_m3 = true,
+};
+
static const struct wcss_data wcss_sec_ipq9574_res_init = {
.pasid = WCSS_PAS_ID,
.ss_name = "q6wcss",
@@ -318,6 +412,7 @@ static const struct wcss_data wcss_sec_ipq9574_res_init = {
static const struct of_device_id wcss_sec_of_match[] = {
{ .compatible = "qcom,ipq5018-wcss-sec-pil", .data = &wcss_sec_ipq5332_res_init },
{ .compatible = "qcom,ipq5332-wcss-sec-pil", .data = &wcss_sec_ipq5332_res_init },
+ { .compatible = "qcom,ipq8074-wcss-sec-pil", .data = &wcss_sec_ipq8074_res_init },
{ .compatible = "qcom,ipq9574-wcss-sec-pil", .data = &wcss_sec_ipq9574_res_init },
{ },
};
--
2.47.3