APSS brings Q6 out of reset and then Q6 brings
WCSS block (wifi radio's) out of reset.
---------------
--> |WiFi 2G radio|
| --------------
|
-------- ------- |
| APSS | ---> |QDSP6| -----|
--------- ------- |
|
|
| --------------
--> |WiFi 5G radio|
--------------
Problem here is if any radio crashes, subsequently other
radio also should crash because Q6 crashed. Let's say
2G radio crashed, Q6 should pass this info to APSS. Only
Q6 processor interrupts registered with APSS. Obviously
Q6 should crash and raise fatal interrupt to APSS. Due
to this 5G radio also crashed. But no issue in 5G radio,
because of 2G radio crash 5G radio also impacted.
In multi pd model, this problem is resolved. Here WCSS
functionality (WiFi radio's) moved out from Q6 root pd
to a separate user pd. Due to this, radio's independently
pass their status info to APPS with out crashing Q6. So
other radio's won't be impacted.
---------
|WiFi |
--> |2G radio|
| ---------
------ Start Q6 ------- |
| | ------------------> | | |
| | Start WCSS PD1 (2G) | | |
|APSS| ----------------------->|QDSP6|-----|
| | Start WCSS PD1 (5G) | |
| | ----------------------->| |-----|
------ ------- |
|
| -----------
|-->|WiFi |
|5G radio |
-----------
According to linux terminology, here consider Q6 as root
i.e it provide all services, WCSS (wifi radio's) as user
i.e it uses services provided by root.
Since Q6 root & WCSS user pd's able to communicate with
APSS individually, multipd remoteproc driver registers
each PD with rproc framework. Here clients (Wifi host drivers)
intrested on WCSS PD rproc, so multipd driver start's root
pd in the context of WCSS user pd rproc start. Similarly
on down path, root pd will be stopped after wcss user pd
stopped.
Here WCSS(user) PD is dependent on Q6(root) PD, so first
q6 pd should be up before wcss pd. After wcss pd goes down,
q6 pd should be turned off.
rproc->ops->start(userpd_rproc) {
/* Boot root pd rproc */
rproc_boot(upd_dev->parent);
---
/* user pd rproc start sequence */
---
---
}
With this way we ensure that root pd brought up before userpd.
rproc->ops->stop(userpd_rproc) {
---
---
/* user pd rproc stop sequence */
---
---
/* Shutdown root pd rproc */
rproc_shutdown(upd_dev->parent);
}
After userpd rproc stops, root pd rproc will be stopped.
IPQ5018, IPQ9574 supports multipd remoteproc driver.
Signed-off-by: Manikanta Mylavarapu <quic_mmanikan@xxxxxxxxxxx>
---
drivers/firmware/qcom_scm.c | 114 +++++
drivers/firmware/qcom_scm.h | 6 +
drivers/remoteproc/Kconfig | 20 +
drivers/remoteproc/Makefile | 1 +
drivers/remoteproc/qcom_common.c | 23 +
drivers/remoteproc/qcom_common.h | 1 +
drivers/remoteproc/qcom_q6v5.c | 41 +-
drivers/remoteproc/qcom_q6v5.h | 15 +-
drivers/remoteproc/qcom_q6v5_adsp.c | 5 +-
drivers/remoteproc/qcom_q6v5_mpd.c | 668 +++++++++++++++++++++++++
drivers/remoteproc/qcom_q6v5_mss.c | 4 +-
drivers/remoteproc/qcom_q6v5_pas.c | 3 +-
drivers/soc/qcom/mdt_loader.c | 314 ++++++++++++
include/linux/firmware/qcom/qcom_scm.h | 3 +
include/linux/soc/qcom/mdt_loader.h | 19 +
15 files changed, 1228 insertions(+), 9 deletions(-)
create mode 100644 drivers/remoteproc/qcom_q6v5_mpd.c
diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c
index d88c5f14bd54..d69560963353 100644
--- a/drivers/firmware/qcom_scm.c
+++ b/drivers/firmware/qcom_scm.c
@@ -654,6 +654,120 @@ int qcom_scm_pas_shutdown(u32 peripheral)
}
EXPORT_SYMBOL(qcom_scm_pas_shutdown);
+/**
+ * qti_scm_int_radio_powerup - Bring up internal radio userpd
+ *
+ * @peripheral: peripheral id
+ *
+ * Return 0 on success.
+ */
+int qti_scm_int_radio_powerup(u32 peripheral)
+{
+ int ret;
+ struct qcom_scm_desc desc = {
+ .svc = QCOM_SCM_PD_LOAD_SVC_ID,
+ .cmd = QCOM_SCM_INT_RAD_PWR_UP_CMD_ID,
+ .arginfo = QCOM_SCM_ARGS(1),
+ .args[0] = peripheral,
+ .owner = ARM_SMCCC_OWNER_SIP,
+ };
+ struct qcom_scm_res res;
+
+ ret = qcom_scm_clk_enable();
+ if (ret)
+ return ret;
+
+ ret = qcom_scm_bw_enable();
+ if (ret)
+ return ret;
+
+ ret = qcom_scm_call(__scm->dev, &desc, &res);
+ qcom_scm_bw_disable();
+ qcom_scm_clk_disable();
+
+ return ret ? : res.result[0];
+}
+EXPORT_SYMBOL(qti_scm_int_radio_powerup);
+
+/**
+ * qti_scm_int_radio_powerdown() - Shut down internal radio userpd
+ *
+ * @peripheral: peripheral id
+ *
+ * Returns 0 on success.
+ */
+int qti_scm_int_radio_powerdown(u32 peripheral)
+{
+ int ret;
+ struct qcom_scm_desc desc = {
+ .svc = QCOM_SCM_PD_LOAD_SVC_ID,
+ .cmd = QCOM_SCM_INT_RAD_PWR_DN_CMD_ID,
+ .arginfo = QCOM_SCM_ARGS(1),
+ .args[0] = peripheral,
+ .owner = ARM_SMCCC_OWNER_SIP,
+ };
+ struct qcom_scm_res res;
+
+ ret = qcom_scm_clk_enable();
+ if (ret)
+ return ret;
+
+ ret = qcom_scm_bw_enable();
+ if (ret)
+ return ret;
+
+ ret = qcom_scm_call(__scm->dev, &desc, &res);
+ qcom_scm_bw_disable();
+ qcom_scm_clk_disable();
+
+ return ret ? : res.result[0];
+}
+EXPORT_SYMBOL(qti_scm_int_radio_powerdown);
+
+/**
+ * qti_scm_pdseg_memcpy_v2() - copy userpd PIL segments data to dma blocks
+ *
+ * @peripheral: peripheral id
+ * @phno: program header no
+ * @dma: handle of dma region
+ * @seg_cnt: no of dma blocks
+ *
+ * Returns 0 if trustzone successfully loads userpd PIL segments from dma
+ * blocks to DDR
+ */
+int qti_scm_pdseg_memcpy_v2(u32 peripheral, int phno, dma_addr_t dma,
+ int seg_cnt)
+{
+ int ret;
+ struct qcom_scm_desc desc = {
+ .svc = QCOM_SCM_PD_LOAD_SVC_ID,
+ .cmd = QCOM_SCM_PD_LOAD_V2_CMD_ID,
+ .arginfo = QCOM_SCM_ARGS(4, QCOM_SCM_VAL, QCOM_SCM_VAL,
+ QCOM_SCM_RW, QCOM_SCM_VAL),
+ .args[0] = peripheral,
+ .args[1] = phno,
+ .args[2] = dma,
+ .args[3] = seg_cnt,
+ .owner = ARM_SMCCC_OWNER_SIP,
+ };
+ struct qcom_scm_res res;
+
+ ret = qcom_scm_clk_enable();
+ if (ret)
+ return ret;
+
+ ret = qcom_scm_bw_enable();
+ if (ret)
+ return ret;
+
+ ret = qcom_scm_call(__scm->dev, &desc, &res);
+ qcom_scm_bw_disable();
+ qcom_scm_clk_disable();
+
+ return ret ? : res.result[0];
+}
+EXPORT_SYMBOL(qti_scm_pdseg_memcpy_v2);
+
/**
* qcom_scm_pas_supported() - Check if the peripheral authentication service is
* available for the given peripherial
diff --git a/drivers/firmware/qcom_scm.h b/drivers/firmware/qcom_scm.h
index e6e512bd57d1..99e3ab2f1986 100644
--- a/drivers/firmware/qcom_scm.h
+++ b/drivers/firmware/qcom_scm.h
@@ -132,6 +132,12 @@ extern int scm_legacy_call(struct device *dev, const struct qcom_scm_desc *desc,
#define QCOM_SCM_SMMU_CONFIG_ERRATA1 0x03
#define QCOM_SCM_SMMU_CONFIG_ERRATA1_CLIENT_ALL 0x02
+#define QCOM_SCM_PD_LOAD_SVC_ID 0x2
+#define QCOM_SCM_PD_LOAD_CMD_ID 0x16
+#define QCOM_SCM_PD_LOAD_V2_CMD_ID 0x19
+#define QCOM_SCM_INT_RAD_PWR_UP_CMD_ID 0x17
+#define QCOM_SCM_INT_RAD_PWR_DN_CMD_ID 0x18
+
#define QCOM_SCM_SVC_WAITQ 0x24
#define QCOM_SCM_WAITQ_RESUME 0x02
#define QCOM_SCM_WAITQ_GET_WQ_CTX 0x03
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index a850e9f486dd..44af5c36f67e 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -234,6 +234,26 @@ config QCOM_Q6V5_PAS
CDSP (Compute DSP), MPSS (Modem Peripheral SubSystem), and
SLPI (Sensor Low Power Island).
+config QCOM_Q6V5_MPD
+ tristate "Qualcomm Hexagon based MPD model Peripheral Image Loader"
+ depends on OF && ARCH_QCOM
+ depends on QCOM_SMEM
+ depends on RPMSG_QCOM_SMD || RPMSG_QCOM_SMD=n
+ depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n
+ depends on QCOM_SYSMON || QCOM_SYSMON=n
+ depends on RPMSG_QCOM_GLINK || RPMSG_QCOM_GLINK=n
+ depends on QCOM_AOSS_QMP || QCOM_AOSS_QMP=n
+ select MFD_SYSCON
+ select QCOM_MDT_LOADER
+ select QCOM_PIL_INFO
+ select QCOM_Q6V5_COMMON
+ select QCOM_RPROC_COMMON
+ select QCOM_SCM
+ help
+ Say y here to support the Qualcomm Secure Peripheral Image Loader
+ for the Hexagon based MultiPD model remote processors on e.g. IPQ5018.
+ This is trustZone wireless subsystem.
+
config QCOM_Q6V5_WCSS
tristate "Qualcomm Hexagon based WCSS Peripheral Image Loader"
depends on OF && ARCH_QCOM
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 91314a9b43ce..b64051080ec1 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_QCOM_PIL_INFO) += qcom_pil_info.o
obj-$(CONFIG_QCOM_RPROC_COMMON) += qcom_common.o
obj-$(CONFIG_QCOM_Q6V5_COMMON) += qcom_q6v5.o
obj-$(CONFIG_QCOM_Q6V5_ADSP) += qcom_q6v5_adsp.o
+obj-$(CONFIG_QCOM_Q6V5_MPD) += qcom_q6v5_mpd.o
obj-$(CONFIG_QCOM_Q6V5_MSS) += qcom_q6v5_mss.o
obj-$(CONFIG_QCOM_Q6V5_PAS) += qcom_q6v5_pas.o
obj-$(CONFIG_QCOM_Q6V5_WCSS) += qcom_q6v5_wcss.o
diff --git a/drivers/remoteproc/qcom_common.c b/drivers/remoteproc/qcom_common.c
index a0d4238492e9..b72fbda02242 100644
--- a/drivers/remoteproc/qcom_common.c
+++ b/drivers/remoteproc/qcom_common.c
@@ -510,5 +510,28 @@ void qcom_remove_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr)
}
EXPORT_SYMBOL_GPL(qcom_remove_ssr_subdev);
+/**
+ * qcom_get_pd_asid() - get the pd asid number from DT node
+ * @node: device tree node
+ *
+ * Returns asid if node name has 'pd' string
+ */
+s8 qcom_get_pd_asid(struct device_node *node)
+{
+ char *str;
+ u8 pd_asid;
+
+ if (!node)
+ return -EINVAL;
+
+ str = strstr(node->name, "pd");
+ if (!str)
+ return 0;
+
+ str += strlen("pd");
+ return kstrtos8(str, 10, &pd_asid) ? -EINVAL : pd_asid;
+}
+EXPORT_SYMBOL(qcom_get_pd_asid);
+
MODULE_DESCRIPTION("Qualcomm Remoteproc helper driver");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/remoteproc/qcom_common.h b/drivers/remoteproc/qcom_common.h
index 9ef4449052a9..9f3fb11224aa 100644
--- a/drivers/remoteproc/qcom_common.h
+++ b/drivers/remoteproc/qcom_common.h
@@ -75,5 +75,6 @@ static inline bool qcom_sysmon_shutdown_acked(struct qcom_sysmon *sysmon)
return false;
}
#endif
+s8 qcom_get_pd_asid(struct device_node *node);
#endif
diff --git a/drivers/remoteproc/qcom_q6v5.c b/drivers/remoteproc/qcom_q6v5.c
index 192c7aa0e39e..b88bf3a8e53b 100644
--- a/drivers/remoteproc/qcom_q6v5.c
+++ b/drivers/remoteproc/qcom_q6v5.c
@@ -118,7 +118,7 @@ static irqreturn_t q6v5_wdog_interrupt(int irq, void *data)
return IRQ_HANDLED;
}
-static irqreturn_t q6v5_fatal_interrupt(int irq, void *data)
+irqreturn_t q6v5_fatal_interrupt(int irq, void *data)
{
struct qcom_q6v5 *q6v5 = data;
size_t len;
@@ -139,7 +139,7 @@ static irqreturn_t q6v5_fatal_interrupt(int irq, void *data)
return IRQ_HANDLED;
}
-static irqreturn_t q6v5_ready_interrupt(int irq, void *data)
+irqreturn_t q6v5_ready_interrupt(int irq, void *data)
{
struct qcom_q6v5 *q6v5 = data;
@@ -183,7 +183,16 @@ static irqreturn_t q6v5_handover_interrupt(int irq, void *data)
return IRQ_HANDLED;
}
-static irqreturn_t q6v5_stop_interrupt(int irq, void *data)
+irqreturn_t q6v5_spawn_interrupt(int irq, void *data)
+{
+ struct qcom_q6v5 *q6v5 = data;
+
+ complete(&q6v5->spawn_done);
+
+ return IRQ_HANDLED;
+}
+
+irqreturn_t q6v5_stop_interrupt(int irq, void *data)
{
struct qcom_q6v5 *q6v5 = data;
@@ -220,6 +229,28 @@ int qcom_q6v5_request_stop(struct qcom_q6v5 *q6v5, struct qcom_sysmon *sysmon)
}
EXPORT_SYMBOL_GPL(qcom_q6v5_request_stop);
+/**
+ * qcom_q6v5_request_spawn() - request the remote processor to spawn
+ * @q6v5: reference to qcom_q6v5 context
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+int qcom_q6v5_request_spawn(struct qcom_q6v5 *q6v5)
+{
+ int ret;
+
+ ret = qcom_smem_state_update_bits(q6v5->spawn_state,
+ BIT(q6v5->spawn_bit), BIT(q6v5->spawn_bit));
+
+ ret = wait_for_completion_timeout(&q6v5->spawn_done, 5 * HZ);
+
+ qcom_smem_state_update_bits(q6v5->spawn_state,
+ BIT(q6v5->spawn_bit), 0);
+
+ return ret == 0 ? -ETIMEDOUT : 0;
+}
+EXPORT_SYMBOL_GPL(qcom_q6v5_request_spawn);
+
/**
* qcom_q6v5_panic() - panic handler to invoke a stop on the remote
* @q6v5: reference to qcom_q6v5 context
@@ -250,7 +281,8 @@ EXPORT_SYMBOL_GPL(qcom_q6v5_panic);
* Return: 0 on success, negative errno on failure
*/
int qcom_q6v5_init(struct qcom_q6v5 *q6v5, struct platform_device *pdev,
- struct rproc *rproc, int crash_reason, const char *load_state,
+ struct rproc *rproc, int remote_id, int crash_reason,
+ const char *load_state,
void (*handover)(struct qcom_q6v5 *q6v5))
{
int ret;
@@ -258,6 +290,7 @@ int qcom_q6v5_init(struct qcom_q6v5 *q6v5, struct platform_device *pdev,
q6v5->rproc = rproc;
q6v5->dev = &pdev->dev;
q6v5->crash_reason = crash_reason;
+ q6v5->remote_id = remote_id;
q6v5->handover = handover;
init_completion(&q6v5->start_done);
diff --git a/drivers/remoteproc/qcom_q6v5.h b/drivers/remoteproc/qcom_q6v5.h
index 5a859c41896e..d00568339d46 100644
--- a/drivers/remoteproc/qcom_q6v5.h
+++ b/drivers/remoteproc/qcom_q6v5.h
@@ -18,22 +18,29 @@ struct qcom_q6v5 {
struct qcom_smem_state *state;
struct qmp *qmp;
+ struct qcom_smem_state *shutdown_state;
+ struct qcom_smem_state *spawn_state;
struct icc_path *path;
unsigned stop_bit;
+ unsigned shutdown_bit;
+ unsigned spawn_bit;
int wdog_irq;
int fatal_irq;
int ready_irq;
int handover_irq;
int stop_irq;
+ int spawn_irq;
bool handover_issued;
struct completion start_done;
struct completion stop_done;
+ struct completion spawn_done;
+ int remote_id;
int crash_reason;
bool running;
@@ -43,14 +50,20 @@ struct qcom_q6v5 {
};
int qcom_q6v5_init(struct qcom_q6v5 *q6v5, struct platform_device *pdev,
- struct rproc *rproc, int crash_reason, const char *load_state,
+ struct rproc *rproc, int remote_id, int crash_reason,
+ const char *load_state,
void (*handover)(struct qcom_q6v5 *q6v5));
void qcom_q6v5_deinit(struct qcom_q6v5 *q6v5);
int qcom_q6v5_prepare(struct qcom_q6v5 *q6v5);
int qcom_q6v5_unprepare(struct qcom_q6v5 *q6v5);
int qcom_q6v5_request_stop(struct qcom_q6v5 *q6v5, struct qcom_sysmon *sysmon);
+int qcom_q6v5_request_spawn(struct qcom_q6v5 *q6v5);
int qcom_q6v5_wait_for_start(struct qcom_q6v5 *q6v5, int timeout);
unsigned long qcom_q6v5_panic(struct qcom_q6v5 *q6v5);
+irqreturn_t q6v5_fatal_interrupt(int irq, void *data);
+irqreturn_t q6v5_ready_interrupt(int irq, void *data);
+irqreturn_t q6v5_spawn_interrupt(int irq, void *data);
+irqreturn_t q6v5_stop_interrupt(int irq, void *data);
#endif
diff --git a/drivers/remoteproc/qcom_q6v5_adsp.c b/drivers/remoteproc/qcom_q6v5_adsp.c
index 08d8dad22ca7..bf8909ad5ff5 100644
--- a/drivers/remoteproc/qcom_q6v5_adsp.c
+++ b/drivers/remoteproc/qcom_q6v5_adsp.c
@@ -733,8 +733,9 @@ static int adsp_probe(struct platform_device *pdev)
if (ret)
goto disable_pm;
- ret = qcom_q6v5_init(&adsp->q6v5, pdev, rproc, desc->crash_reason_smem,
- desc->load_state, qcom_adsp_pil_handover);
+ ret = qcom_q6v5_init(&adsp->q6v5, pdev, rproc, QCOM_SMEM_HOST_ANY,
+ desc->crash_reason_smem, desc->load_state,
+ qcom_adsp_pil_handover);
if (ret)
goto disable_pm;
diff --git a/drivers/remoteproc/qcom_q6v5_mpd.c b/drivers/remoteproc/qcom_q6v5_mpd.c
new file mode 100644
index 000000000000..853aa3bc5859
--- /dev/null
+++ b/drivers/remoteproc/qcom_q6v5_mpd.c
@@ -0,0 +1,668 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2016-2018 Linaro Ltd.
+ * Copyright (C) 2014 Sony Mobile Communications AB
+ * Copyright (c) 2012-2018, 2021 The Linux Foundation. All rights reserved.
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/soc/qcom/mdt_loader.h>
+#include <linux/soc/qcom/smem.h>
+#include <linux/soc/qcom/smem_state.h>
+#include <linux/firmware/qcom/qcom_scm.h>
+#include <linux/interrupt.h>