mirror of
https://github.com/hzyitc/openwrt-redmi-ax3000.git
synced 2025-12-17 00:42:53 +00:00
925 lines
25 KiB
Diff
925 lines
25 KiB
Diff
From dd894fe4e3973e20f63e881641e854932ca8b4af Mon Sep 17 00:00:00 2001
|
|
From: Manikanta Mylavarapu <quic_mmanikan@quicinc.com>
|
|
Date: Tue, 7 Mar 2023 10:11:34 +0530
|
|
Subject: [PATCH 4/4] remoteproc: qcom: Add Hexagon based multipd rproc driver
|
|
|
|
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@quicinc.com>
|
|
|
|
Spilt from <1678164097-13247-9-git-send-email-quic_mmanikan@quicinc.com>
|
|
|
|
Remove uncessary member - remote_id
|
|
|
|
Signed-off-by: hzy <hzyitc@outlook.com>
|
|
---
|
|
drivers/remoteproc/Kconfig | 20 +
|
|
drivers/remoteproc/Makefile | 1 +
|
|
drivers/remoteproc/qcom_q6v5.c | 37 +-
|
|
drivers/remoteproc/qcom_q6v5.h | 12 +
|
|
drivers/remoteproc/qcom_q6v5_mpd.c | 655 +++++++++++++++++++++++++++++
|
|
5 files changed, 722 insertions(+), 3 deletions(-)
|
|
create mode 100644 drivers/remoteproc/qcom_q6v5_mpd.c
|
|
|
|
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
|
|
index 94afdde4bc9f..6c1e4a0f7419 100644
|
|
--- a/drivers/remoteproc/Kconfig
|
|
+++ b/drivers/remoteproc/Kconfig
|
|
@@ -141,6 +141,26 @@ config QCOM_Q6V5_PAS
|
|
for the Qualcomm Hexagon v5 based remote processors. This is commonly
|
|
used to control subsystems such as ADSP, Compute and Sensor.
|
|
|
|
+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 y || RPMSG_QCOM_GLINK || RPMSG_QCOM_GLINK=n
|
|
+ depends on y || 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 00f09e658cb3..a195539b0972 100644
|
|
--- a/drivers/remoteproc/Makefile
|
|
+++ b/drivers/remoteproc/Makefile
|
|
@@ -17,6 +17,7 @@ obj-$(CONFIG_KEYSTONE_REMOTEPROC) += keystone_remoteproc.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_q6v5.c b/drivers/remoteproc/qcom_q6v5.c
|
|
index fd6fd36268d9..8a2fa7aaf74d 100644
|
|
--- a/drivers/remoteproc/qcom_q6v5.c
|
|
+++ b/drivers/remoteproc/qcom_q6v5.c
|
|
@@ -74,7 +74,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;
|
|
@@ -92,7 +92,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;
|
|
|
|
@@ -134,7 +134,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;
|
|
|
|
@@ -166,6 +175,28 @@ int qcom_q6v5_request_stop(struct qcom_q6v5 *q6v5)
|
|
}
|
|
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
|
|
diff --git a/drivers/remoteproc/qcom_q6v5.h b/drivers/remoteproc/qcom_q6v5.h
|
|
index c4ed887c1499..9f4c36093985 100644
|
|
--- a/drivers/remoteproc/qcom_q6v5.h
|
|
+++ b/drivers/remoteproc/qcom_q6v5.h
|
|
@@ -14,18 +14,25 @@ struct qcom_q6v5 {
|
|
struct rproc *rproc;
|
|
|
|
struct qcom_smem_state *state;
|
|
+ struct qcom_smem_state *shutdown_state;
|
|
+ struct qcom_smem_state *spawn_state;
|
|
+
|
|
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 crash_reason;
|
|
|
|
@@ -41,7 +48,12 @@ int qcom_q6v5_init(struct qcom_q6v5 *q6v5, struct platform_device *pdev,
|
|
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_mpd.c b/drivers/remoteproc/qcom_q6v5_mpd.c
|
|
new file mode 100644
|
|
index 000000000000..0e0e39715580
|
|
--- /dev/null
|
|
+++ b/drivers/remoteproc/qcom_q6v5_mpd.c
|
|
@@ -0,0 +1,655 @@
|
|
+// 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/qcom_scm.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include "qcom_common.h"
|
|
+#include "qcom_q6v5.h"
|
|
+
|
|
+#include "remoteproc_internal.h"
|
|
+
|
|
+#define WCSS_CRASH_REASON 421
|
|
+#define WCSS_SMEM_HOST 1
|
|
+
|
|
+#define WCNSS_PAS_ID 6
|
|
+#define MPD_WCNSS_PAS_ID 0xD
|
|
+
|
|
+#define BUF_SIZE 35
|
|
+
|
|
+/**
|
|
+ * enum state - state of a wcss (private)
|
|
+ * @WCSS_NORMAL: subsystem is operating normally
|
|
+ * @WCSS_CRASHED: subsystem has crashed and hasn't been shutdown
|
|
+ * @WCSS_RESTARTING: subsystem has been shutdown and is now restarting
|
|
+ * @WCSS_SHUTDOWN: subsystem has been shutdown
|
|
+ *
|
|
+ */
|
|
+enum q6_wcss_state {
|
|
+ WCSS_NORMAL,
|
|
+ WCSS_CRASHED,
|
|
+ WCSS_RESTARTING,
|
|
+ WCSS_SHUTDOWN,
|
|
+};
|
|
+
|
|
+enum {
|
|
+ Q6_IPQ,
|
|
+ WCSS_AHB_IPQ,
|
|
+ WCSS_PCIE_IPQ,
|
|
+};
|
|
+
|
|
+struct q6_wcss {
|
|
+ struct device *dev;
|
|
+ struct qcom_rproc_glink glink_subdev;
|
|
+ struct qcom_rproc_ssr ssr_subdev;
|
|
+ struct qcom_q6v5 q6;
|
|
+ phys_addr_t mem_phys;
|
|
+ phys_addr_t mem_reloc;
|
|
+ void *mem_region;
|
|
+ size_t mem_size;
|
|
+ int crash_reason_smem;
|
|
+ u32 version;
|
|
+ s8 pd_asid;
|
|
+ enum q6_wcss_state state;
|
|
+};
|
|
+
|
|
+struct wcss_data {
|
|
+ int (*init_irq)(struct qcom_q6v5 *q6, struct platform_device *pdev,
|
|
+ struct rproc *rproc, int crash_reason,
|
|
+ void (*handover)(struct qcom_q6v5 *q6));
|
|
+ const char *q6_firmware_name;
|
|
+ int crash_reason_smem;
|
|
+ u32 version;
|
|
+ const char *ssr_name;
|
|
+ const struct rproc_ops *ops;
|
|
+ bool need_auto_boot;
|
|
+ bool glink_subdev_required;
|
|
+ s8 pd_asid;
|
|
+ bool reset_seq;
|
|
+ u32 pasid;
|
|
+ int (*mdt_load_sec)(struct device *dev, const struct firmware *fw,
|
|
+ const char *fw_name, int pas_id, void *mem_region,
|
|
+ phys_addr_t mem_phys, size_t mem_size,
|
|
+ phys_addr_t *reloc_base);
|
|
+};
|
|
+
|
|
+static int q6_wcss_start(struct rproc *rproc)
|
|
+{
|
|
+ struct q6_wcss *wcss = rproc->priv;
|
|
+ int ret;
|
|
+ struct device_node *upd_np;
|
|
+ struct platform_device *upd_pdev;
|
|
+ struct rproc *upd_rproc;
|
|
+ struct q6_wcss *upd_wcss;
|
|
+ const struct wcss_data *desc;
|
|
+
|
|
+ desc = of_device_get_match_data(wcss->dev);
|
|
+ if (!desc)
|
|
+ return -EINVAL;
|
|
+
|
|
+ qcom_q6v5_prepare(&wcss->q6);
|
|
+
|
|
+ ret = qcom_scm_pas_auth_and_reset(desc->pasid);
|
|
+ if (ret) {
|
|
+ dev_err(wcss->dev, "wcss_reset failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = qcom_q6v5_wait_for_start(&wcss->q6, 5 * HZ);
|
|
+ if (ret == -ETIMEDOUT)
|
|
+ dev_err(wcss->dev, "start timed out\n");
|
|
+
|
|
+ /* Bring userpd wcss state to default value */
|
|
+ for_each_available_child_of_node(wcss->dev->of_node, upd_np) {
|
|
+ if (!strstr(upd_np->name, "pd"))
|
|
+ continue;
|
|
+ upd_pdev = of_find_device_by_node(upd_np);
|
|
+ upd_rproc = platform_get_drvdata(upd_pdev);
|
|
+ upd_wcss = upd_rproc->priv;
|
|
+ upd_wcss->state = WCSS_NORMAL;
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int q6_wcss_spawn_pd(struct rproc *rproc)
|
|
+{
|
|
+ int ret;
|
|
+ struct q6_wcss *wcss = rproc->priv;
|
|
+
|
|
+ ret = qcom_q6v5_request_spawn(&wcss->q6);
|
|
+ if (ret == -ETIMEDOUT) {
|
|
+ pr_err("%s spawn timedout\n", rproc->name);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = qcom_q6v5_wait_for_start(&wcss->q6, msecs_to_jiffies(10000));
|
|
+ if (ret == -ETIMEDOUT) {
|
|
+ pr_err("%s start timedout\n", rproc->name);
|
|
+ wcss->q6.running = false;
|
|
+ return ret;
|
|
+ }
|
|
+ wcss->q6.running = true;
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int wcss_ahb_pcie_pd_start(struct rproc *rproc)
|
|
+{
|
|
+ struct q6_wcss *wcss = rproc->priv;
|
|
+ const struct wcss_data *desc = of_device_get_match_data(wcss->dev);
|
|
+ int ret;
|
|
+
|
|
+ if (desc->reset_seq) {
|
|
+ ret = qcom_scm_pas_power_up(desc->pasid);
|
|
+ if (ret) {
|
|
+ dev_err(wcss->dev, "failed to power up ahb pd\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (wcss->q6.spawn_bit) {
|
|
+ ret = q6_wcss_spawn_pd(rproc);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ wcss->state = WCSS_NORMAL;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int q6_wcss_stop(struct rproc *rproc)
|
|
+{
|
|
+ struct q6_wcss *wcss = rproc->priv;
|
|
+ int ret;
|
|
+ const struct wcss_data *desc =
|
|
+ of_device_get_match_data(wcss->dev);
|
|
+
|
|
+ if (!desc)
|
|
+ return -EINVAL;
|
|
+
|
|
+ ret = qcom_scm_pas_power_down(desc->pasid);
|
|
+ if (ret) {
|
|
+ dev_err(wcss->dev, "not able to shutdown\n");
|
|
+ return ret;
|
|
+ }
|
|
+ qcom_q6v5_unprepare(&wcss->q6);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int wcss_ahb_pcie_pd_stop(struct rproc *rproc)
|
|
+{
|
|
+ struct q6_wcss *wcss = rproc->priv;
|
|
+ struct rproc *rpd_rproc = dev_get_drvdata(wcss->dev->parent);
|
|
+ const struct wcss_data *desc = of_device_get_match_data(wcss->dev);
|
|
+ int ret;
|
|
+
|
|
+ if (rproc->state != RPROC_CRASHED && wcss->q6.stop_bit) {
|
|
+ ret = qcom_q6v5_request_stop(&wcss->q6, NULL);
|
|
+ if (ret) {
|
|
+ dev_err(&rproc->dev, "pd not stopped\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (desc->reset_seq) {
|
|
+ ret = qcom_scm_pas_power_down(desc->pasid);
|
|
+ if (ret) {
|
|
+ dev_err(wcss->dev, "failed to power down pd\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (rproc->state != RPROC_CRASHED)
|
|
+ rproc_shutdown(rpd_rproc);
|
|
+
|
|
+ wcss->state = WCSS_SHUTDOWN;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void *q6_wcss_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem)
|
|
+{
|
|
+ struct q6_wcss *wcss = rproc->priv;
|
|
+ int offset;
|
|
+
|
|
+ offset = da - wcss->mem_reloc;
|
|
+ if (offset < 0 || offset + len > wcss->mem_size)
|
|
+ return NULL;
|
|
+
|
|
+ return wcss->mem_region + offset;
|
|
+}
|
|
+
|
|
+static int q6_wcss_load(struct rproc *rproc, const struct firmware *fw)
|
|
+{
|
|
+ struct q6_wcss *wcss = rproc->priv;
|
|
+ const struct firmware *m3_fw;
|
|
+ int ret;
|
|
+ const char *m3_fw_name;
|
|
+ struct device_node *upd_np;
|
|
+ struct platform_device *upd_pdev;
|
|
+ const struct wcss_data *desc =
|
|
+ of_device_get_match_data(wcss->dev);
|
|
+
|
|
+ if (!desc)
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* load m3 firmware */
|
|
+ for_each_available_child_of_node(wcss->dev->of_node, upd_np) {
|
|
+ if (!strstr(upd_np->name, "pd"))
|
|
+ continue;
|
|
+ upd_pdev = of_find_device_by_node(upd_np);
|
|
+
|
|
+ ret = of_property_read_string(upd_np, "m3_firmware",
|
|
+ &m3_fw_name);
|
|
+ if (!ret && m3_fw_name) {
|
|
+ ret = request_firmware(&m3_fw, m3_fw_name,
|
|
+ &upd_pdev->dev);
|
|
+ if (ret)
|
|
+ continue;
|
|
+
|
|
+ ret = qcom_mdt_load_no_init(wcss->dev, m3_fw,
|
|
+ m3_fw_name, 0,
|
|
+ wcss->mem_region,
|
|
+ wcss->mem_phys,
|
|
+ wcss->mem_size,
|
|
+ &wcss->mem_reloc);
|
|
+
|
|
+ release_firmware(m3_fw);
|
|
+
|
|
+ if (ret) {
|
|
+ dev_err(wcss->dev,
|
|
+ "can't load m3_fw.bXX ret:%d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return qcom_mdt_load(wcss->dev, fw, rproc->firmware,
|
|
+ desc->pasid, wcss->mem_region,
|
|
+ wcss->mem_phys, wcss->mem_size,
|
|
+ &wcss->mem_reloc);
|
|
+}
|
|
+
|
|
+static int wcss_ahb_pcie_pd_load(struct rproc *rproc, const struct firmware *fw)
|
|
+{
|
|
+ struct q6_wcss *wcss = rproc->priv, *wcss_rpd;
|
|
+ struct rproc *rpd_rproc = dev_get_drvdata(wcss->dev->parent);
|
|
+ int ret;
|
|
+ const struct wcss_data *desc =
|
|
+ of_device_get_match_data(wcss->dev);
|
|
+
|
|
+ if (!desc)
|
|
+ return -EINVAL;
|
|
+
|
|
+ wcss_rpd = rpd_rproc->priv;
|
|
+
|
|
+ /* Boot rootpd rproc */
|
|
+ ret = rproc_boot(rpd_rproc);
|
|
+ if (ret || wcss->state == WCSS_NORMAL)
|
|
+ return ret;
|
|
+
|
|
+ return desc->mdt_load_sec(wcss->dev, fw, rproc->firmware,
|
|
+ desc->pasid, wcss->mem_region,
|
|
+ wcss->mem_phys, wcss->mem_size,
|
|
+ &wcss->mem_reloc);
|
|
+}
|
|
+
|
|
+static unsigned long q6_wcss_panic(struct rproc *rproc)
|
|
+{
|
|
+ struct q6_wcss *wcss = rproc->priv;
|
|
+
|
|
+ return qcom_q6v5_panic(&wcss->q6);
|
|
+}
|
|
+
|
|
+static const struct rproc_ops wcss_ahb_pcie_ipq5018_ops = {
|
|
+ .start = wcss_ahb_pcie_pd_start,
|
|
+ .stop = wcss_ahb_pcie_pd_stop,
|
|
+ .load = wcss_ahb_pcie_pd_load,
|
|
+};
|
|
+
|
|
+static const struct rproc_ops q6_wcss_ipq5018_ops = {
|
|
+ .start = q6_wcss_start,
|
|
+ .stop = q6_wcss_stop,
|
|
+ .da_to_va = q6_wcss_da_to_va,
|
|
+ .load = q6_wcss_load,
|
|
+ .get_boot_addr = rproc_elf_get_boot_addr,
|
|
+ .panic = q6_wcss_panic,
|
|
+};
|
|
+
|
|
+static int q6_alloc_memory_region(struct q6_wcss *wcss)
|
|
+{
|
|
+ struct reserved_mem *rmem = NULL;
|
|
+ struct device_node *node;
|
|
+ struct device *dev = wcss->dev;
|
|
+
|
|
+ if (wcss->version == Q6_IPQ) {
|
|
+ node = of_parse_phandle(dev->of_node, "memory-region", 0);
|
|
+ if (node)
|
|
+ rmem = of_reserved_mem_lookup(node);
|
|
+
|
|
+ of_node_put(node);
|
|
+
|
|
+ if (!rmem) {
|
|
+ dev_err(dev, "unable to acquire memory-region\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ } else {
|
|
+ struct rproc *rpd_rproc = dev_get_drvdata(dev->parent);
|
|
+ struct q6_wcss *rpd_wcss = rpd_rproc->priv;
|
|
+
|
|
+ wcss->mem_phys = rpd_wcss->mem_phys;
|
|
+ wcss->mem_reloc = rpd_wcss->mem_reloc;
|
|
+ wcss->mem_size = rpd_wcss->mem_size;
|
|
+ wcss->mem_region = rpd_wcss->mem_region;
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ wcss->mem_phys = rmem->base;
|
|
+ wcss->mem_reloc = rmem->base;
|
|
+ wcss->mem_size = rmem->size;
|
|
+ wcss->mem_region = devm_ioremap_wc(dev, wcss->mem_phys, wcss->mem_size);
|
|
+ if (!wcss->mem_region) {
|
|
+ dev_err(dev, "unable to map memory region: %pa+%pa\n",
|
|
+ &rmem->base, &rmem->size);
|
|
+ return -EBUSY;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int q6_get_inbound_irq(struct qcom_q6v5 *q6,
|
|
+ struct platform_device *pdev,
|
|
+ const char *int_name,
|
|
+ irqreturn_t (*handler)(int irq, void *data))
|
|
+{
|
|
+ int ret, irq;
|
|
+ char *interrupt, *tmp = (char *)int_name;
|
|
+ struct q6_wcss *wcss = q6->rproc->priv;
|
|
+
|
|
+ irq = platform_get_irq_byname(pdev, int_name);
|
|
+ if (irq < 0) {
|
|
+ if (irq != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev,
|
|
+ "failed to retrieve %s IRQ: %d\n",
|
|
+ int_name, irq);
|
|
+ return irq;
|
|
+ }
|
|
+
|
|
+ if (!strcmp(int_name, "fatal")) {
|
|
+ q6->fatal_irq = irq;
|
|
+ } else if (!strcmp(int_name, "stop-ack")) {
|
|
+ q6->stop_irq = irq;
|
|
+ tmp = "stop_ack";
|
|
+ } else if (!strcmp(int_name, "ready")) {
|
|
+ q6->ready_irq = irq;
|
|
+ } else if (!strcmp(int_name, "handover")) {
|
|
+ q6->handover_irq = irq;
|
|
+ } else if (!strcmp(int_name, "spawn-ack")) {
|
|
+ q6->spawn_irq = irq;
|
|
+ tmp = "spawn_ack";
|
|
+ } else {
|
|
+ dev_err(&pdev->dev, "unknown interrupt\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ interrupt = devm_kzalloc(&pdev->dev, BUF_SIZE, GFP_KERNEL);
|
|
+ if (!interrupt)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ snprintf(interrupt, BUF_SIZE, "q6v5_wcss_userpd%d", wcss->pd_asid);
|
|
+ strlcat(interrupt, "_", BUF_SIZE);
|
|
+ strlcat(interrupt, tmp, BUF_SIZE);
|
|
+
|
|
+ ret = devm_request_threaded_irq(&pdev->dev, irq,
|
|
+ NULL, handler,
|
|
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
|
+ interrupt, q6);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "failed to acquire %s irq\n", interrupt);
|
|
+ return ret;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int q6_get_outbound_irq(struct qcom_q6v5 *q6,
|
|
+ struct platform_device *pdev,
|
|
+ const char *int_name)
|
|
+{
|
|
+ struct qcom_smem_state *tmp_state;
|
|
+ unsigned bit;
|
|
+
|
|
+ tmp_state = qcom_smem_state_get(&pdev->dev, int_name, &bit);
|
|
+ if (IS_ERR(tmp_state)) {
|
|
+ dev_err(&pdev->dev, "failed to acquire %s state\n", int_name);
|
|
+ return PTR_ERR(tmp_state);
|
|
+ }
|
|
+
|
|
+ if (!strcmp(int_name, "stop")) {
|
|
+ q6->state = tmp_state;
|
|
+ q6->stop_bit = bit;
|
|
+ } else if (!strcmp(int_name, "spawn")) {
|
|
+ q6->spawn_state = tmp_state;
|
|
+ q6->spawn_bit = bit;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int init_irq(struct qcom_q6v5 *q6,
|
|
+ struct platform_device *pdev, struct rproc *rproc,
|
|
+ int crash_reason, void (*handover)(struct qcom_q6v5 *q6))
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ q6->rproc = rproc;
|
|
+ q6->dev = &pdev->dev;
|
|
+ q6->crash_reason = crash_reason;
|
|
+ q6->handover = handover;
|
|
+
|
|
+ init_completion(&q6->start_done);
|
|
+ init_completion(&q6->stop_done);
|
|
+ init_completion(&q6->spawn_done);
|
|
+
|
|
+ ret = q6_get_inbound_irq(q6, pdev, "fatal",
|
|
+ q6v5_fatal_interrupt);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = q6_get_inbound_irq(q6, pdev, "ready",
|
|
+ q6v5_ready_interrupt);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = q6_get_inbound_irq(q6, pdev, "stop-ack",
|
|
+ q6v5_stop_interrupt);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = q6_get_inbound_irq(q6, pdev, "spawn-ack",
|
|
+ q6v5_spawn_interrupt);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = q6_get_outbound_irq(q6, pdev, "stop");
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = q6_get_outbound_irq(q6, pdev, "spawn");
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int q6_wcss_probe(struct platform_device *pdev)
|
|
+{
|
|
+ const struct wcss_data *desc;
|
|
+ struct q6_wcss *wcss;
|
|
+ struct rproc *rproc;
|
|
+ int ret;
|
|
+ char *subdev_name;
|
|
+
|
|
+ desc = of_device_get_match_data(&pdev->dev);
|
|
+ if (!desc)
|
|
+ return -EINVAL;
|
|
+
|
|
+ rproc = rproc_alloc(&pdev->dev, pdev->name, desc->ops,
|
|
+ desc->q6_firmware_name, sizeof(*wcss));
|
|
+ if (!rproc) {
|
|
+ dev_err(&pdev->dev, "failed to allocate rproc\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ wcss = rproc->priv;
|
|
+ wcss->dev = &pdev->dev;
|
|
+ wcss->version = desc->version;
|
|
+
|
|
+ ret = q6_alloc_memory_region(wcss);
|
|
+ if (ret)
|
|
+ goto free_rproc;
|
|
+
|
|
+ wcss->pd_asid = qcom_get_pd_asid(wcss->dev->of_node);
|
|
+ if (wcss->pd_asid < 0)
|
|
+ goto free_rproc;
|
|
+
|
|
+ if (desc->init_irq) {
|
|
+ ret = desc->init_irq(&wcss->q6, pdev, rproc,
|
|
+ desc->crash_reason_smem, NULL);
|
|
+ if (ret) {
|
|
+ if (wcss->version == Q6_IPQ)
|
|
+ goto free_rproc;
|
|
+ else
|
|
+ dev_info(wcss->dev,
|
|
+ "userpd irq registration failed\n");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (desc->glink_subdev_required)
|
|
+ qcom_add_glink_subdev(rproc, &wcss->glink_subdev, desc->ssr_name);
|
|
+
|
|
+ subdev_name = (char *)(desc->ssr_name ? desc->ssr_name : pdev->name);
|
|
+ qcom_add_ssr_subdev(rproc, &wcss->ssr_subdev, subdev_name);
|
|
+
|
|
+ rproc->auto_boot = desc->need_auto_boot;
|
|
+ ret = rproc_add(rproc);
|
|
+ if (ret)
|
|
+ goto free_rproc;
|
|
+
|
|
+ platform_set_drvdata(pdev, rproc);
|
|
+
|
|
+ ret = of_platform_populate(wcss->dev->of_node, NULL,
|
|
+ NULL, wcss->dev);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "failed to populate wcss pd nodes\n");
|
|
+ goto free_rproc;
|
|
+ }
|
|
+ return 0;
|
|
+
|
|
+free_rproc:
|
|
+ rproc_free(rproc);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int q6_wcss_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct rproc *rproc = platform_get_drvdata(pdev);
|
|
+
|
|
+ rproc_del(rproc);
|
|
+ rproc_free(rproc);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct wcss_data q6_ipq5018_res_init = {
|
|
+ .init_irq = qcom_q6v5_init,
|
|
+ .q6_firmware_name = "IPQ5018/q6_fw.mdt",
|
|
+ .crash_reason_smem = WCSS_CRASH_REASON,
|
|
+ .ssr_name = "q6wcss",
|
|
+ .ops = &q6_wcss_ipq5018_ops,
|
|
+ .version = Q6_IPQ,
|
|
+ .glink_subdev_required = true,
|
|
+ .pasid = MPD_WCNSS_PAS_ID,
|
|
+};
|
|
+
|
|
+static const struct wcss_data q6_ipq9574_res_init = {
|
|
+ .init_irq = qcom_q6v5_init,
|
|
+ .q6_firmware_name = "IPQ9574/q6_fw.mdt",
|
|
+ .crash_reason_smem = WCSS_CRASH_REASON,
|
|
+ .ssr_name = "q6wcss",
|
|
+ .ops = &q6_wcss_ipq5018_ops,
|
|
+ .version = Q6_IPQ,
|
|
+ .glink_subdev_required = true,
|
|
+ .pasid = WCNSS_PAS_ID,
|
|
+};
|
|
+
|
|
+static const struct wcss_data wcss_ahb_ipq5018_res_init = {
|
|
+ .init_irq = init_irq,
|
|
+ .q6_firmware_name = "IPQ5018/q6_fw.mdt",
|
|
+ .crash_reason_smem = WCSS_CRASH_REASON,
|
|
+ .ops = &wcss_ahb_pcie_ipq5018_ops,
|
|
+ .version = WCSS_AHB_IPQ,
|
|
+ .pasid = MPD_WCNSS_PAS_ID,
|
|
+ .reset_seq = true,
|
|
+ .mdt_load_sec = qcom_mdt_load_pd_seg,
|
|
+};
|
|
+
|
|
+static const struct wcss_data wcss_ahb_ipq9574_res_init = {
|
|
+ .init_irq = init_irq,
|
|
+ .q6_firmware_name = "IPQ9574/q6_fw.mdt",
|
|
+ .crash_reason_smem = WCSS_CRASH_REASON,
|
|
+ .ops = &wcss_ahb_pcie_ipq5018_ops,
|
|
+ .version = WCSS_AHB_IPQ,
|
|
+ .pasid = WCNSS_PAS_ID,
|
|
+ .mdt_load_sec = qcom_mdt_load,
|
|
+};
|
|
+
|
|
+static const struct wcss_data wcss_pcie_ipq5018_res_init = {
|
|
+ .init_irq = init_irq,
|
|
+ .q6_firmware_name = "IPQ5018/q6_fw.mdt",
|
|
+ .crash_reason_smem = WCSS_CRASH_REASON,
|
|
+ .ops = &wcss_ahb_pcie_ipq5018_ops,
|
|
+ .version = WCSS_PCIE_IPQ,
|
|
+ .mdt_load_sec = qcom_mdt_load_pd_seg,
|
|
+ .pasid = MPD_WCNSS_PAS_ID,
|
|
+};
|
|
+
|
|
+static const struct of_device_id q6_wcss_of_match[] = {
|
|
+ { .compatible = "qcom,ipq5018-q6-mpd", .data = &q6_ipq5018_res_init },
|
|
+ { .compatible = "qcom,ipq9574-q6-mpd", .data = &q6_ipq9574_res_init },
|
|
+ { .compatible = "qcom,ipq5018-wcss-ahb-mpd",
|
|
+ .data = &wcss_ahb_ipq5018_res_init },
|
|
+ { .compatible = "qcom,ipq9574-wcss-ahb-mpd",
|
|
+ .data = &wcss_ahb_ipq9574_res_init },
|
|
+ { .compatible = "qcom,ipq5018-wcss-pcie-mpd",
|
|
+ .data = &wcss_pcie_ipq5018_res_init },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, q6_wcss_of_match);
|
|
+
|
|
+static struct platform_driver q6_wcss_driver = {
|
|
+ .probe = q6_wcss_probe,
|
|
+ .remove = q6_wcss_remove,
|
|
+ .driver = {
|
|
+ .name = "qcom-q6-mpd",
|
|
+ .of_match_table = q6_wcss_of_match,
|
|
+ },
|
|
+};
|
|
+module_platform_driver(q6_wcss_driver);
|
|
+
|
|
+MODULE_DESCRIPTION("Hexagon WCSS Multipd Peripheral Image Loader");
|
|
+MODULE_LICENSE("GPL v2");
|
|
--
|
|
2.25.1
|
|
|