meta-st-stm32mp/recipes-kernel/linux/linux-stm32mp/4.19/4.19.9/0013-ARM-stm32mp1-r0-rc1-RE...

2649 lines
69 KiB
Diff

From 80b6c916e8d8900d2449726c59ed7ad8609c9976 Mon Sep 17 00:00:00 2001
From: Romuald JEANNE <romuald.jeanne@st.com>
Date: Tue, 13 Nov 2018 12:28:00 +0100
Subject: [PATCH 13/52] ARM: stm32mp1-r0-rc1: REMOTEPROC RPMSG
---
Documentation/remoteproc.txt | 23 +
drivers/remoteproc/Kconfig | 36 ++
drivers/remoteproc/Makefile | 3 +
drivers/remoteproc/remoteproc_core.c | 45 +-
drivers/remoteproc/rproc_srm_core.c | 303 ++++++++++++
drivers/remoteproc/rproc_srm_core.h | 110 +++++
drivers/remoteproc/rproc_srm_dev.c | 928 +++++++++++++++++++++++++++++++++++
drivers/remoteproc/stm32_rproc.c | 585 ++++++++++++++++++++++
drivers/rpmsg/Kconfig | 9 +
drivers/rpmsg/Makefile | 1 +
drivers/rpmsg/rpmsg_core.c | 19 +
drivers/rpmsg/rpmsg_internal.h | 2 +
drivers/rpmsg/rpmsg_tty.c | 305 ++++++++++++
drivers/rpmsg/virtio_rpmsg_bus.c | 11 +
include/linux/remoteproc.h | 2 +
include/linux/rpmsg.h | 9 +
16 files changed, 2382 insertions(+), 9 deletions(-)
create mode 100644 drivers/remoteproc/rproc_srm_core.c
create mode 100644 drivers/remoteproc/rproc_srm_core.h
create mode 100644 drivers/remoteproc/rproc_srm_dev.c
create mode 100644 drivers/remoteproc/stm32_rproc.c
create mode 100644 drivers/rpmsg/rpmsg_tty.c
diff --git a/Documentation/remoteproc.txt b/Documentation/remoteproc.txt
index 77fb03a..bec2177 100644
--- a/Documentation/remoteproc.txt
+++ b/Documentation/remoteproc.txt
@@ -353,3 +353,26 @@ Of course, RSC_VDEV resource entries are only good enough for static
allocation of virtio devices. Dynamic allocations will also be made possible
using the rpmsg bus (similar to how we already do dynamic allocations of
rpmsg channels; read more about it in rpmsg.txt).
+
+8. System Resource Manager (SRM)
+
+Since some resources are shared (directly or not) between the processors, a
+processor cannot manage such resources without potentially impacting the other
+processors : as an example, if a processor changes the frequency of a clock, the
+frequency of another clock managed by another processor may be updated too.
+
+The System Resource Manager prevents such resource conflicts between the
+processors : it reserves and initializes the system resources of the peripherals
+assigned to a remote processor.
+
+As of today the following resources are controlled by the SRM:
+- clocks
+- gpios (pinctrl)
+- regulators (power supplies)
+
+The SRM is implemented as an 'rproc_subdev' and registered to remoteproc_core.
+Unlike the virtio device (vdev), the SRM subdev is probed *before* the rproc
+boots, ensuring the availability of the resources before the remoteproc starts.
+
+The resources handled by the SRM are defined in the DeviceTree: please read
+Documentation/devicetree/bindings/remoteproc/rproc-srm.txt for details.
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index 052d4dd..c1f3f00 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -13,6 +13,25 @@ config REMOTEPROC
if REMOTEPROC
+config REMOTEPROC_SRM_CORE
+ tristate "Remoteproc System Resource Manager core"
+ depends on RPMSG
+ help
+ Say y here to enable the core driver of the remoteproc System Resource
+ Manager (SRM).
+ The SRM handles resources allocated to remote processors.
+ The core part is in charge of controlling the device children.
+
+config REMOTEPROC_SRM_DEV
+ tristate "Remoteproc System Resource Manager device"
+ depends on REMOTEPROC_SRM_CORE
+ help
+ Say y here to enable the device driver of the remoteproc System
+ Resource Manager (SRM).
+ The SRM handles resources allocated to remote processors.
+ The device part is in charge of reserving and initializing resources
+ for a peripheral assigned to a coprocessor.
+
config IMX_REMOTEPROC
tristate "IMX6/7 remoteproc support"
depends on SOC_IMX6SX || SOC_IMX7D
@@ -181,6 +200,23 @@ config ST_REMOTEPROC
config ST_SLIM_REMOTEPROC
tristate
+config STM32_RPROC
+ tristate "STM32 remoteproc support"
+ depends on ARCH_STM32
+ depends on REMOTEPROC
+ select MAILBOX
+ select REMOTEPROC_SRM_CORE
+ select REMOTEPROC_SRM_DEV
+ help
+ Say y here to support STM32 MCU processors via the
+ remote processor framework.
+
+ You want to say y here in order to enable AMP
+ use-cases to run on your platform (dedicated firmware could be
+ offloaded to remote MCU processors using this framework).
+
+ This can be either built-in or a loadable module.
+
endif # REMOTEPROC
endmenu
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 03332fa..1e43aa6 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -9,6 +9,8 @@ remoteproc-y += remoteproc_debugfs.o
remoteproc-y += remoteproc_sysfs.o
remoteproc-y += remoteproc_virtio.o
remoteproc-y += remoteproc_elf_loader.o
+obj-$(CONFIG_REMOTEPROC_SRM_CORE) += rproc_srm_core.o
+obj-$(CONFIG_REMOTEPROC_SRM_DEV) += rproc_srm_dev.o
obj-$(CONFIG_IMX_REMOTEPROC) += imx_rproc.o
obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o
obj-$(CONFIG_WKUP_M3_RPROC) += wkup_m3_rproc.o
@@ -25,3 +27,4 @@ qcom_wcnss_pil-y += qcom_wcnss.o
qcom_wcnss_pil-y += qcom_wcnss_iris.o
obj-$(CONFIG_ST_REMOTEPROC) += st_remoteproc.o
obj-$(CONFIG_ST_SLIM_REMOTEPROC) += st_slim_rproc.o
+obj-$(CONFIG_STM32_RPROC) += stm32_rproc.o
diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c
index aa62067..21726f0 100644
--- a/drivers/remoteproc/remoteproc_core.c
+++ b/drivers/remoteproc/remoteproc_core.c
@@ -41,6 +41,7 @@
#include <linux/crc32.h>
#include <linux/virtio_ids.h>
#include <linux/virtio_ring.h>
+#include <linux/of_platform.h>
#include <asm/byteorder.h>
#include "remoteproc_internal.h"
@@ -987,7 +988,11 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw)
if (ret)
return ret;
- dev_info(dev, "Booting fw image %s, size %zd\n", name, fw->size);
+ if (fw)
+ dev_info(dev, "Booting fw image %s, size %zd\n", name,
+ fw->size);
+ else
+ dev_info(dev, "Synchronizing with early booted co-processor\n");
/*
* if enabling an IOMMU isn't relevant for this rproc, this is
@@ -1290,7 +1295,7 @@ static void rproc_crash_handler_work(struct work_struct *work)
*/
int rproc_boot(struct rproc *rproc)
{
- const struct firmware *firmware_p;
+ const struct firmware *firmware_p = NULL;
struct device *dev;
int ret;
@@ -1321,11 +1326,17 @@ int rproc_boot(struct rproc *rproc)
dev_info(dev, "powering up %s\n", rproc->name);
- /* load firmware */
- ret = request_firmware(&firmware_p, rproc->firmware, dev);
- if (ret < 0) {
- dev_err(dev, "request_firmware failed: %d\n", ret);
- goto downref_rproc;
+ if (!rproc->early_boot) {
+ /* load firmware */
+ ret = request_firmware(&firmware_p, rproc->firmware, dev);
+ if (ret < 0) {
+ dev_err(dev, "request_firmware failed: %d\n", ret);
+ goto downref_rproc;
+ }
+ } else {
+ /* set firmware name to null as unknown */
+ kfree(rproc->firmware);
+ rproc->firmware = NULL;
}
ret = rproc_fw_boot(rproc, firmware_p);
@@ -1479,8 +1490,22 @@ int rproc_add(struct rproc *rproc)
/* create debugfs entries */
rproc_create_debug_dir(rproc);
- /* if rproc is marked always-on, request it to boot */
- if (rproc->auto_boot) {
+ /* add resource manager device */
+ ret = devm_of_platform_populate(dev->parent);
+ if (ret < 0)
+ return ret;
+
+ if (rproc->early_boot) {
+ /*
+ * If rproc is marked already booted, no need to wait
+ * for firmware.
+ * Just handle associated resources and start sub devices
+ */
+ ret = rproc_boot(rproc);
+ if (ret < 0)
+ return ret;
+ } else if (rproc->auto_boot) {
+ /* if rproc is marked always-on, request it to boot */
ret = rproc_trigger_auto_boot(rproc);
if (ret < 0)
return ret;
@@ -1706,6 +1731,8 @@ int rproc_del(struct rproc *rproc)
list_del(&rproc->node);
mutex_unlock(&rproc_list_mutex);
+ of_platform_depopulate(rproc->dev.parent);
+
device_del(&rproc->dev);
return 0;
diff --git a/drivers/remoteproc/rproc_srm_core.c b/drivers/remoteproc/rproc_srm_core.c
new file mode 100644
index 0000000..66be92e
--- /dev/null
+++ b/drivers/remoteproc/rproc_srm_core.c
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
+ */
+
+#include <linux/component.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/remoteproc.h>
+#include <linux/rpmsg.h>
+
+#include "rproc_srm_core.h"
+
+#define BIND_TIMEOUT 10000
+
+struct rproc_srm_core {
+ struct device *dev;
+ struct completion all_bound;
+ int bind_status;
+ atomic_t prepared;
+ struct rproc_subdev subdev;
+ struct rpmsg_driver rpdrv;
+ struct blocking_notifier_head notifier;
+};
+
+#define to_rproc_srm_core(s) container_of(s, struct rproc_srm_core, subdev)
+
+static struct rproc_srm_core *rpmsg_srm_to_core(struct rpmsg_device *rpdev)
+{
+ struct rpmsg_driver *rpdrv;
+ struct rproc_srm_core *core;
+
+ rpdrv = container_of(rpdev->dev.driver, struct rpmsg_driver, drv);
+ core = container_of(rpdrv, struct rproc_srm_core, rpdrv);
+
+ return core;
+}
+
+int rpmsg_srm_send(struct rpmsg_endpoint *ept, struct rpmsg_srm_msg *msg)
+{
+ int ret;
+
+ ret = rpmsg_send(ept, (void *)msg, sizeof(*msg));
+ if (ret)
+ dev_err(&ept->rpdev->dev, "rpmsg_send failed: %d\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL(rpmsg_srm_send);
+
+static int rpmsg_srm_cb(struct rpmsg_device *rpdev, void *data, int len,
+ void *priv, u32 src)
+{
+ struct rproc_srm_core *core = rpmsg_srm_to_core(rpdev);
+ struct rpmsg_srm_msg_desc desc;
+ int ret;
+
+ desc.ept = rpdev->ept;
+ desc.msg = data;
+
+ ret = blocking_notifier_call_chain(&core->notifier, 0, &desc);
+
+ if (!(ret & NOTIFY_STOP_MASK)) {
+ dev_warn(&rpdev->dev, "unknown device\n");
+ desc.msg->message_type = RPROC_SRM_MSG_ERROR;
+ rpmsg_srm_send(desc.ept, desc.msg);
+ }
+
+ return 0;
+}
+
+static int rpmsg_srm_probe(struct rpmsg_device *rpdev)
+{
+ int ret;
+
+ dev_dbg(&rpdev->dev, "%s\n", __func__);
+
+ /* Send an empty message to complete the initialization */
+ ret = rpmsg_send(rpdev->ept, NULL, 0);
+ if (ret)
+ dev_err(&rpdev->dev, "failed to send init message\n");
+
+ return ret;
+}
+
+static void rpmsg_srm_remove(struct rpmsg_device *rpdev)
+{
+ /* Note : the remove ops is mandatory */
+ dev_dbg(&rpdev->dev, "%s\n", __func__);
+}
+
+static struct rpmsg_device_id rpmsg_srm_id_table[] = {
+ { .name = "rproc-srm" },
+ { },
+};
+MODULE_DEVICE_TABLE(rpmsg, rpmsg_srm_id_table);
+
+static struct rpmsg_driver rpmsg_srm_drv = {
+ .drv.name = "rpmsg_srm",
+ .id_table = rpmsg_srm_id_table,
+ .probe = rpmsg_srm_probe,
+ .callback = rpmsg_srm_cb,
+ .remove = rpmsg_srm_remove,
+};
+
+int rproc_srm_core_register_notifier(struct rproc_srm_core *core,
+ struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&core->notifier, nb);
+}
+EXPORT_SYMBOL(rproc_srm_core_register_notifier);
+
+int rproc_srm_core_unregister_notifier(struct rproc_srm_core *core,
+ struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&core->notifier, nb);
+}
+EXPORT_SYMBOL(rproc_srm_core_unregister_notifier);
+
+static int compare_of(struct device *dev, void *data)
+{
+ return dev->of_node == data;
+}
+
+static void release_of(struct device *dev, void *data)
+{
+ of_node_put(data);
+}
+
+static void rproc_srm_core_unbind(struct device *dev)
+{
+ component_unbind_all(dev, NULL);
+}
+
+static int rproc_srm_core_bind(struct device *dev)
+{
+ struct rproc_srm_core *rproc_srm_core = dev_get_drvdata(dev);
+
+ rproc_srm_core->bind_status = component_bind_all(dev, NULL);
+ complete(&rproc_srm_core->all_bound);
+
+ return rproc_srm_core->bind_status;
+}
+
+static const struct component_master_ops srm_comp_ops = {
+ .bind = rproc_srm_core_bind,
+ .unbind = rproc_srm_core_unbind,
+};
+
+static int rproc_srm_core_prepare(struct rproc_subdev *subdev)
+{
+ struct rproc_srm_core *rproc_srm_core = to_rproc_srm_core(subdev);
+ struct device *dev = rproc_srm_core->dev;
+ struct device_node *node = dev->of_node;
+ struct device_node *child_np;
+ struct component_match *match = NULL;
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ init_completion(&rproc_srm_core->all_bound);
+
+ ret = devm_of_platform_populate(dev);
+ if (ret) {
+ dev_err(dev, "cannot populate node (%d)\n", ret);
+ return ret;
+ }
+
+ child_np = of_get_next_available_child(node, NULL);
+
+ while (child_np) {
+ of_node_get(child_np);
+ component_match_add_release(dev, &match, release_of, compare_of,
+ child_np);
+ child_np = of_get_next_available_child(node, child_np);
+ }
+
+ if (!match) {
+ dev_dbg(dev, "No available child\n");
+ goto done;
+ }
+
+ ret = component_master_add_with_match(dev, &srm_comp_ops, match);
+ if (ret)
+ goto depopulate;
+
+ /* Wait for every child to be bound */
+ if (!wait_for_completion_timeout(&rproc_srm_core->all_bound,
+ msecs_to_jiffies(BIND_TIMEOUT))) {
+ dev_err(dev, "bind timeout\n");
+ ret = -ETIMEDOUT;
+ goto master;
+ }
+
+ ret = rproc_srm_core->bind_status;
+ if (ret) {
+ dev_err(dev, "failed to bind\n");
+ goto master;
+ }
+
+ /* Register rpmsg driver for dynamic management */
+ rproc_srm_core->rpdrv = rpmsg_srm_drv;
+ ret = register_rpmsg_driver(&rproc_srm_core->rpdrv);
+ if (ret) {
+ dev_err(dev, "failed to register rpmsg drv\n");
+ goto master;
+ }
+
+done:
+ atomic_inc(&rproc_srm_core->prepared);
+
+ return 0;
+
+master:
+ component_master_del(dev, &srm_comp_ops);
+depopulate:
+ devm_of_platform_depopulate(dev);
+ return ret;
+}
+
+static void rproc_srm_core_unprepare(struct rproc_subdev *subdev)
+{
+ struct rproc_srm_core *rproc_srm_core = to_rproc_srm_core(subdev);
+ struct device *dev = rproc_srm_core->dev;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (!atomic_read(&rproc_srm_core->prepared))
+ return;
+
+ atomic_dec(&rproc_srm_core->prepared);
+
+ unregister_rpmsg_driver(&rproc_srm_core->rpdrv);
+
+ component_master_del(dev, &srm_comp_ops);
+ devm_of_platform_depopulate(dev);
+}
+
+static int rproc_srm_core_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rproc *rproc = dev_get_drvdata(dev->parent);
+ struct rproc_srm_core *rproc_srm_core;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ rproc_srm_core = devm_kzalloc(dev, sizeof(struct rproc_srm_core),
+ GFP_KERNEL);
+ if (!rproc_srm_core)
+ return -ENOMEM;
+
+ rproc_srm_core->dev = dev;
+ BLOCKING_INIT_NOTIFIER_HEAD(&rproc_srm_core->notifier);
+
+ /* Register rproc subdevice with (un)prepare ops */
+ rproc_srm_core->subdev.prepare = rproc_srm_core_prepare;
+ rproc_srm_core->subdev.unprepare = rproc_srm_core_unprepare;
+ rproc_add_subdev(rproc, &rproc_srm_core->subdev);
+
+ dev_set_drvdata(dev, rproc_srm_core);
+
+ return 0;
+}
+
+static int rproc_srm_core_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rproc_srm_core *rproc_srm_core = dev_get_drvdata(dev);
+ struct rproc *rproc = dev_get_drvdata(dev->parent);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (atomic_read(&rproc->power) > 0)
+ dev_warn(dev, "Releasing resources while firmware running!\n");
+
+ rproc_srm_core_unprepare(&rproc_srm_core->subdev);
+
+ return 0;
+}
+
+static const struct of_device_id rproc_srm_core_match[] = {
+ { .compatible = "rproc-srm-core", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, rproc_srm_core_match);
+
+static struct platform_driver rproc_srm_core_driver = {
+ .probe = rproc_srm_core_probe,
+ .remove = rproc_srm_core_remove,
+ .driver = {
+ .name = "rproc-srm-core",
+ .of_match_table = of_match_ptr(rproc_srm_core_match),
+ },
+};
+
+module_platform_driver(rproc_srm_core_driver);
+
+MODULE_AUTHOR("Fabien Dessenne <fabien.dessenne@st.com>");
+MODULE_DESCRIPTION("Remoteproc System Resource Manager driver - core");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/remoteproc/rproc_srm_core.h b/drivers/remoteproc/rproc_srm_core.h
new file mode 100644
index 0000000..7915f35
--- /dev/null
+++ b/drivers/remoteproc/rproc_srm_core.h
@@ -0,0 +1,110 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
+ */
+
+#ifndef _RPROC_SRM_CORE_H_
+#define _RPROC_SRM_CORE_H_
+
+/**
+ * Message type used in resource manager rpmsg:
+ * RPROC_SRM_MSG_GETCONFIG: Request to get the configuration of a resource
+ * RPROC_SRM_MSG_SETCONFIG: Request to set the configuration of a resource
+ * RPROC_SRM_MSG_ERROR: Error when processing a request
+ */
+#define RPROC_SRM_MSG_GETCONFIG 0x00
+#define RPROC_SRM_MSG_SETCONFIG 0x01
+#define RPROC_SRM_MSG_ERROR 0xFF
+
+/**
+ * Resource type used in resource manager rpmsg:
+ * RPROC_SRM_RSC_CLOCK: clock resource
+ * RPROC_SRM_RSC_PIN: pin resource
+ * RPROC_SRM_RSC_REGU: regulator resource
+ */
+#define RPROC_SRM_RSC_CLOCK 0x00
+#define RPROC_SRM_RSC_PIN 0x01
+#define RPROC_SRM_RSC_REGU 0x02
+
+/**
+ * struct clock_cfg - clock configuration used in resource manager rpmsg
+ * @index: clock index
+ * @name: clock name
+ * @rate: clock rate request (in SetConfig message) or current status (in
+ * GetConfig message)
+ */
+struct clock_cfg {
+ u32 index;
+ u8 name[16];
+ u32 rate;
+};
+
+/**
+ * struct regu_cfg - regu configuration used in resource manager rpmsg
+ * @index: regulator index
+ * @name: regulator name
+ * @enable: regulator enable/disable request (in SetConfig message) or
+ * current status (in GetConfig message)
+ * @curr_voltage_mv: current regulator voltage in mV (meaningful in
+ * SetConfig message)
+ * @min_voltage_mv: regulator min voltage request in mV (meaningful in
+ * SetConfig message)
+ * @max_voltage_mv: regulator max voltage request in mV (meaningful in
+ * SetConfig message)
+ */
+struct regu_cfg {
+ u32 index;
+ u8 name[16];
+ u32 enable;
+ u32 curr_voltage_mv;
+ u32 min_voltage_mv;
+ u32 max_voltage_mv;
+};
+
+/**
+ * struct pin_cfg - pin configuration used in resource manager rpmsg
+ * @name: current pin configuration name (meaningful in GetConfig message)
+ */
+struct pin_cfg {
+ u8 name[16];
+};
+
+/**
+ * struct rpmsg_srm_msg - message structure used between processors to
+ * dynamically update resources configuration
+ * @message_type: type of the message: see RPROC_SRM_MSG*
+ * @device_id: an identifier specifying the device owning the resources.
+ * This is implementation dependent. As example it may be the
+ * device name or the device address.
+ * @rsc_type: the type of the resource for which the configuration applies:
+ * see RPROC_SRM_RSC*
+ * @clock_cfg: clock config - relevant if &rsc_type is RPROC_SRM_RSC_CLOCK
+ * @regu_cfg: regulator config - relevant if &rsc_type is RPROC_SRM_RSC_REGU
+ * @pin_cfg: pin config - relevant if &rsc_type is RPROC_SRM_RSC_PIN
+ */
+struct rpmsg_srm_msg {
+ u32 message_type;
+ u8 device_id[32];
+ u32 rsc_type;
+ union {
+ struct clock_cfg clock_cfg;
+ struct regu_cfg regu_cfg;
+ struct pin_cfg pin_cfg;
+ };
+};
+
+struct rpmsg_srm_msg_desc {
+ struct rpmsg_endpoint *ept;
+ struct rpmsg_srm_msg *msg;
+};
+
+struct rproc_srm_core;
+
+int rproc_srm_core_register_notifier(struct rproc_srm_core *core,
+ struct notifier_block *nb);
+int rproc_srm_core_unregister_notifier(struct rproc_srm_core *core,
+ struct notifier_block *nb);
+int rpmsg_srm_send(struct rpmsg_endpoint *ept, struct rpmsg_srm_msg *msg);
+
+#endif
diff --git a/drivers/remoteproc/rproc_srm_dev.c b/drivers/remoteproc/rproc_srm_dev.c
new file mode 100644
index 0000000..b026f961
--- /dev/null
+++ b/drivers/remoteproc/rproc_srm_dev.c
@@ -0,0 +1,928 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/component.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/remoteproc.h>
+
+#include "rproc_srm_core.h"
+
+struct rproc_srm_clk_info {
+ struct list_head list;
+ unsigned int index;
+ struct clk *clk;
+ const char *name;
+ bool parent_enabled;
+};
+
+struct rproc_srm_pin_info {
+ struct list_head list;
+ unsigned int index;
+ char *name;
+ bool selected;
+};
+
+struct rproc_srm_regu_info {
+ struct list_head list;
+ unsigned int index;
+ struct regulator *regu;
+ const char *name;
+ bool enabled;
+};
+
+struct rproc_srm_irq_info {
+ struct list_head list;
+ unsigned int index;
+ char *name;
+ int irq;
+ bool enabled;
+};
+
+struct rproc_srm_dev {
+ struct device *dev;
+ struct rproc_srm_core *core;
+ struct notifier_block nb;
+ struct pinctrl *pctrl;
+ bool early_boot;
+
+ struct list_head clk_list_head;
+ struct list_head regu_list_head;
+ struct list_head pin_list_head;
+ struct list_head irq_list_head;
+};
+
+/* Irqs */
+static void rproc_srm_dev_irqs_put(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct rproc_srm_irq_info *i, *tmp;
+
+ list_for_each_entry_safe(i, tmp, &rproc_srm_dev->irq_list_head, list) {
+ devm_free_irq(dev, i->irq, NULL);
+ dev_dbg(dev, "Put irq %d (%s)\n", i->irq, i->name);
+ list_del(&i->list);
+ }
+}
+
+static irqreturn_t rproc_srm_dev_irq_handler(int irq, void *dev)
+{
+ dev_warn(dev, "Spurious interrupt\n");
+ return IRQ_HANDLED;
+}
+
+static int rproc_srm_dev_irqs_get(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct device_node *np = dev->of_node;
+ struct rproc_srm_irq_info *info;
+ const char *name;
+ int nr, ret, irq;
+ unsigned int i;
+
+ if (!np)
+ return 0;
+
+ nr = platform_irq_count(pdev);
+ if (!nr)
+ return 0;
+
+ if (rproc_srm_dev->early_boot)
+ /*
+ * Do not overwrite the irq configuration.
+ * No need to parse irq from DT since the resource manager does
+ * not offer any service to update the irq config.
+ */
+ return 0;
+
+ for (i = 0; i < nr; i++) {
+ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ irq = platform_get_irq(pdev, i);
+ if (irq <= 0) {
+ ret = irq;
+ dev_err(dev, "Failed to get irq (%d)\n", ret);
+ goto err;
+ }
+
+ info->irq = irq;
+
+ /* Register a dummy irq handleras not used by Linux */
+ ret = devm_request_irq(dev, info->irq,
+ rproc_srm_dev_irq_handler, 0,
+ dev_name(dev), NULL);
+ if (ret) {
+ dev_err(dev, "Failed to request irq (%d)\n", ret);
+ goto err;
+ }
+
+ /*
+ * Disable IRQ. Since it is used by the remote processor we
+ * must not use the 'irq lazy disable' optimization
+ */
+ irq_set_status_flags(info->irq, IRQ_DISABLE_UNLAZY);
+ disable_irq(info->irq);
+
+ /* Note: "interrupt-names" is optional */
+ if (!of_property_read_string_index(np, "interrupt-names", i,
+ &name))
+ info->name = devm_kstrdup(dev, name, GFP_KERNEL);
+ else
+ info->name = devm_kstrdup(dev, "", GFP_KERNEL);
+
+ info->index = i;
+
+ list_add_tail(&info->list, &rproc_srm_dev->irq_list_head);
+ dev_dbg(dev, "Got irq %d (%s)\n", info->irq, info->name);
+ }
+
+ return 0;
+
+err:
+ rproc_srm_dev_irqs_put(rproc_srm_dev);
+
+ return ret;
+}
+
+/* Clocks */
+static void rproc_srm_dev_clocks_unsetup(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct rproc_srm_clk_info *c;
+
+ list_for_each_entry(c, &rproc_srm_dev->clk_list_head, list) {
+ if (!c->parent_enabled)
+ continue;
+
+ clk_disable_unprepare(clk_get_parent(c->clk));
+ c->parent_enabled = false;
+ dev_dbg(rproc_srm_dev->dev, "clk %d (%s) unsetup\n",
+ c->index, c->name);
+ }
+}
+
+static int rproc_srm_dev_clocks_setup(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct rproc_srm_clk_info *c;
+ int ret;
+
+ /*
+ * Prepare and enable the parent clocks.
+ * Since the clock tree is under the exclusive control of the master
+ * processor, we need to configure the clock tree of the targeted clock.
+ * We do not want to enable the clock itself, which is under the
+ * responsibility of the remote processor.
+ * Hence we prepare and enable the parent clock.
+ */
+
+ list_for_each_entry(c, &rproc_srm_dev->clk_list_head, list) {
+ if (c->parent_enabled)
+ continue;
+
+ ret = clk_prepare_enable(clk_get_parent(c->clk));
+ if (ret) {
+ dev_err(rproc_srm_dev->dev,
+ "clk %d (%s) parent enable failed\n",
+ c->index, c->name);
+ rproc_srm_dev_clocks_unsetup(rproc_srm_dev);
+ return ret;
+ }
+ c->parent_enabled = true;
+ dev_dbg(rproc_srm_dev->dev, "clk %d (%s) parent enabled\n",
+ c->index, c->name);
+ }
+
+ return 0;
+}
+
+static struct rproc_srm_clk_info
+ *rproc_srm_dev_clock_find(struct rproc_srm_dev *rproc_srm_dev,
+ struct clock_cfg *cfg)
+{
+ struct rproc_srm_clk_info *ci;
+
+ /* Search by index (if valid value) otherwise search by name */
+ list_for_each_entry(ci, &rproc_srm_dev->clk_list_head, list) {
+ if (cfg->index != U32_MAX) {
+ if (ci->index == cfg->index)
+ return ci;
+ } else {
+ if (!strcmp(ci->name, cfg->name))
+ return ci;
+ }
+ }
+
+ return NULL;
+}
+
+static int rproc_srm_dev_clock_set_cfg(struct rproc_srm_dev *rproc_srm_dev,
+ struct clock_cfg *cfg)
+{
+ struct rproc_srm_clk_info *c;
+ struct device *dev = rproc_srm_dev->dev;
+ int ret;
+
+ c = rproc_srm_dev_clock_find(rproc_srm_dev, cfg);
+
+ if (!c) {
+ dev_err(dev, "unknown clock (id %d)\n", cfg->index);
+ return -EINVAL;
+ }
+
+ if (cfg->rate && clk_get_rate(c->clk) != cfg->rate) {
+ ret = clk_set_rate(c->clk, cfg->rate);
+ if (ret) {
+ dev_err(dev, "clk set rate failed\n");
+ return ret;
+ }
+
+ dev_dbg(dev, "clk %d (%s) rate = %d\n", c->index, c->name,
+ cfg->rate);
+ }
+
+ return 0;
+}
+
+static int rproc_srm_dev_clock_get_cfg(struct rproc_srm_dev *rproc_srm_dev,
+ struct clock_cfg *cfg)
+{
+ struct rproc_srm_clk_info *c;
+
+ c = rproc_srm_dev_clock_find(rproc_srm_dev, cfg);
+ if (!c) {
+ dev_err(rproc_srm_dev->dev, "unknown clock (%d)\n", cfg->index);
+ return -EINVAL;
+ }
+
+ strlcpy(cfg->name, c->name, sizeof(cfg->name));
+ cfg->index = c->index;
+ cfg->rate = (u32)clk_get_rate(c->clk);
+
+ return 0;
+}
+
+static void rproc_srm_dev_clocks_put(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct rproc_srm_clk_info *c, *tmp;
+
+ list_for_each_entry_safe(c, tmp, &rproc_srm_dev->clk_list_head, list) {
+ clk_put(c->clk);
+ dev_dbg(dev, "put clock %d (%s)\n", c->index, c->name);
+ list_del(&c->list);
+ }
+}
+
+static int rproc_srm_dev_clocks_get(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct device_node *np = dev->of_node;
+ struct rproc_srm_clk_info *c;
+ const char *name;
+ int nb_c, ret;
+ unsigned int i;
+
+ if (!np)
+ return 0;
+
+ nb_c = of_clk_get_parent_count(np);
+ if (!nb_c)
+ return 0;
+
+ for (i = 0; i < nb_c; i++) {
+ c = devm_kzalloc(dev, sizeof(*c), GFP_KERNEL);
+ if (!c) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ c->clk = of_clk_get(np, i);
+ if (IS_ERR(c->clk)) {
+ dev_err(dev, "clock %d KO (%ld)\n", i,
+ PTR_ERR(c->clk));
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ /* Note: "clock-names" is optional */
+ if (!of_property_read_string_index(np, "clock-names", i,
+ &name))
+ c->name = devm_kstrdup(dev, name, GFP_KERNEL);
+ else
+ c->name = devm_kstrdup(dev, "", GFP_KERNEL);
+
+ c->index = i;
+
+ list_add_tail(&c->list, &rproc_srm_dev->clk_list_head);
+ dev_dbg(dev, "got clock %d (%s)\n", c->index, c->name);
+ }
+
+ return 0;
+
+err:
+ rproc_srm_dev_clocks_put(rproc_srm_dev);
+ return ret;
+}
+
+/* Regulators */
+static void rproc_srm_dev_regus_unsetup(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct rproc_srm_regu_info *r;
+ struct device *dev = rproc_srm_dev->dev;
+
+ list_for_each_entry(r, &rproc_srm_dev->regu_list_head, list) {
+ if (!r->enabled)
+ continue;
+
+ if (regulator_disable(r->regu)) {
+ dev_warn(dev, "regu %d disabled failed\n", r->index);
+ continue;
+ }
+
+ r->enabled = false;
+ dev_dbg(dev, "regu %d (%s) disabled\n", r->index, r->name);
+ }
+}
+
+static int rproc_srm_dev_regus_setup(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct rproc_srm_regu_info *r;
+ int ret;
+
+ /* Enable all the regulators */
+ list_for_each_entry(r, &rproc_srm_dev->regu_list_head, list) {
+ if (r->enabled)
+ continue;
+
+ /* in early_boot mode sync on hw */
+ if (rproc_srm_dev->early_boot && !regulator_is_enabled(r->regu))
+ continue;
+
+ ret = regulator_enable(r->regu);
+ if (ret) {
+ dev_err(rproc_srm_dev->dev, "regu %d (%s) failed\n",
+ r->index, r->name);
+ rproc_srm_dev_regus_unsetup(rproc_srm_dev);
+ return ret;
+ }
+ r->enabled = true;
+ dev_dbg(rproc_srm_dev->dev, "regu %d (%s) enabled\n",
+ r->index, r->name);
+ }
+
+ return 0;
+}
+
+static struct rproc_srm_regu_info
+ *rproc_srm_dev_regu_find(struct rproc_srm_dev *rproc_srm_dev,
+ struct regu_cfg *cfg)
+{
+ struct rproc_srm_regu_info *ri;
+
+ list_for_each_entry(ri, &rproc_srm_dev->regu_list_head, list) {
+ if (cfg->index != U32_MAX) {
+ if (ri->index == cfg->index)
+ return ri;
+ } else {
+ if (!strcmp(ri->name, cfg->name))
+ return ri;
+ }
+ }
+
+ return NULL;
+}
+
+static int rproc_srm_dev_regu_set_cfg(struct rproc_srm_dev *rproc_srm_dev,
+ struct regu_cfg *cfg)
+{
+ struct rproc_srm_regu_info *r;
+ struct device *dev = rproc_srm_dev->dev;
+ int ret;
+
+ r = rproc_srm_dev_regu_find(rproc_srm_dev, cfg);
+ if (!r) {
+ dev_err(dev, "unknown regu (%d)\n", cfg->index);
+ return -EINVAL;
+ }
+
+ if (!r->enabled && cfg->enable) {
+ ret = regulator_enable(r->regu);
+ if (ret) {
+ dev_err(dev, "regu %d enable failed\n", r->index);
+ return ret;
+ }
+ r->enabled = true;
+ dev_dbg(dev, "regu %d (%s) enabled\n", r->index, r->name);
+ } else if (r->enabled && !cfg->enable) {
+ ret = regulator_disable(r->regu);
+ if (ret) {
+ dev_err(dev, "regu %d disable failed\n", r->index);
+ return ret;
+ }
+ r->enabled = false;
+ dev_dbg(dev, "regu %d (%s) disabled\n", r->index, r->name);
+ }
+
+ if (cfg->min_voltage_mv || cfg->max_voltage_mv) {
+ ret = regulator_set_voltage(r->regu, cfg->min_voltage_mv * 1000,
+ cfg->max_voltage_mv * 1000);
+ if (ret) {
+ dev_err(dev, "regu %d set voltage failed\n", r->index);
+ return ret;
+ }
+
+ dev_dbg(dev, "regu %d (%s) voltage = [%d - %d] mv\n", r->index,
+ r->name, cfg->min_voltage_mv, cfg->max_voltage_mv);
+ }
+
+ return 0;
+}
+
+static int rproc_srm_dev_regu_get_cfg(struct rproc_srm_dev *rproc_srm_dev,
+ struct regu_cfg *cfg)
+{
+ struct rproc_srm_regu_info *r;
+ struct device *dev = rproc_srm_dev->dev;
+ int v;
+
+ r = rproc_srm_dev_regu_find(rproc_srm_dev, cfg);
+ if (!r) {
+ dev_err(dev, "unknown regu (%d)\n", cfg->index);
+ return -EINVAL;
+ }
+
+ strlcpy(cfg->name, r->name, sizeof(cfg->name));
+ cfg->index = r->index;
+ cfg->enable = r->enabled;
+ cfg->min_voltage_mv = 0;
+ cfg->max_voltage_mv = 0;
+
+ v = regulator_get_voltage(r->regu);
+ if (v < 0) {
+ dev_warn(dev, "cannot get %s voltage\n", r->name);
+ cfg->curr_voltage_mv = 0;
+ } else {
+ cfg->curr_voltage_mv = v / 1000;
+ }
+
+ return 0;
+}
+
+static void rproc_srm_dev_regus_put(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct rproc_srm_regu_info *r, *tmp;
+
+ list_for_each_entry_safe(r, tmp, &rproc_srm_dev->regu_list_head, list) {
+ devm_regulator_put(r->regu);
+ dev_dbg(dev, "put regu %d (%s)\n", r->index, r->name);
+ list_del(&r->list);
+ }
+}
+
+static int rproc_srm_dev_regus_get(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct device_node *np = dev->of_node;
+ struct property *p;
+ const char *n;
+ char *name;
+ struct rproc_srm_regu_info *r;
+ int ret, nb_s = 0;
+
+ if (!np)
+ return 0;
+
+ for_each_property_of_node(np, p) {
+ n = strstr(p->name, "-supply");
+ if (!n || n == p->name)
+ continue;
+
+ r = devm_kzalloc(dev, sizeof(*r), GFP_KERNEL);
+ if (!r) {
+ ret = -ENOMEM;
+ goto err_list;
+ }
+
+ name = devm_kstrdup(dev, p->name, GFP_KERNEL);
+ name[strlen(p->name) - strlen("-supply")] = '\0';
+ r->name = name;
+
+ r->regu = devm_regulator_get(dev, r->name);
+ if (IS_ERR(r->regu)) {
+ dev_err(dev, "cannot get regu %s\n", r->name);
+ ret = -EINVAL;
+ goto err_list;
+ }
+
+ r->index = nb_s++;
+
+ list_add_tail(&r->list, &rproc_srm_dev->regu_list_head);
+ dev_dbg(dev, "got regu %d (%s)\n", r->index, r->name);
+ }
+
+ return 0;
+
+err_list:
+ rproc_srm_dev_regus_put(rproc_srm_dev);
+ return ret;
+}
+
+/* Pins */
+static int rproc_srm_dev_pin_set_cfg(struct rproc_srm_dev *rproc_srm_dev,
+ struct pin_cfg *cfg)
+{
+ struct rproc_srm_pin_info *pi, *p = NULL;
+ struct device *dev = rproc_srm_dev->dev;
+ struct pinctrl_state *state;
+ int ret;
+
+ list_for_each_entry(pi, &rproc_srm_dev->pin_list_head, list) {
+ if (!strcmp(pi->name, cfg->name)) {
+ p = pi;
+ break;
+ }
+ }
+
+ if (!p) {
+ dev_err(dev, "unknown pin config (%s)\n", cfg->name);
+ return -EINVAL;
+ }
+
+ state = pinctrl_lookup_state(rproc_srm_dev->pctrl, cfg->name);
+ if (IS_ERR(state)) {
+ dev_err(dev, "cannot get pin config (%s)\n", cfg->name);
+ return -EINVAL;
+ }
+
+ ret = pinctrl_select_state(rproc_srm_dev->pctrl, state);
+ if (ret < 0) {
+ dev_err(dev, "cannot set pin config (%s)\n", cfg->name);
+ return ret;
+ }
+
+ list_for_each_entry(pi, &rproc_srm_dev->pin_list_head, list) {
+ pi->selected = (pi == p);
+ }
+
+ dev_dbg(dev, "pin config (%s) selected\n", p->name);
+
+ return 0;
+}
+
+static int rproc_srm_dev_pin_get_cfg(struct rproc_srm_dev *rproc_srm_dev,
+ struct pin_cfg *cfg)
+{
+ struct rproc_srm_pin_info *p;
+
+ list_for_each_entry(p, &rproc_srm_dev->pin_list_head, list) {
+ if (p->selected) {
+ strlcpy(cfg->name, p->name, sizeof(cfg->name));
+ return 0;
+ }
+ }
+
+ dev_warn(rproc_srm_dev->dev, "cannot find selected pin state\n");
+ strcpy(cfg->name, "");
+
+ return 0;
+}
+
+static int rproc_srm_dev_pins_setup(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct rproc_srm_pin_info *p;
+ struct pin_cfg cfg = { .name = "rproc_default" };
+
+ if (rproc_srm_dev->early_boot)
+ /* in early_boot mode do not update pin config */
+ return 0;
+
+ /* set the "rproc_default" pin config if defined */
+ list_for_each_entry(p, &rproc_srm_dev->pin_list_head, list) {
+ if (!strcmp(p->name, cfg.name))
+ return rproc_srm_dev_pin_set_cfg(rproc_srm_dev, &cfg);
+ }
+
+ return 0;
+}
+
+static void rproc_srm_dev_pins_put(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct rproc_srm_pin_info *p, *tmp;
+
+ list_for_each_entry_safe(p, tmp, &rproc_srm_dev->pin_list_head, list) {
+ devm_kfree(dev, p->name);
+ devm_kfree(dev, p);
+ dev_dbg(dev, "remove pin cfg %d (%s)\n", p->index, p->name);
+ list_del(&p->list);
+ }
+
+ if (!IS_ERR_OR_NULL(rproc_srm_dev->pctrl)) {
+ devm_pinctrl_put(rproc_srm_dev->pctrl);
+ rproc_srm_dev->pctrl = NULL;
+ }
+}
+
+static int rproc_srm_dev_pins_get(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct device_node *np = dev->of_node;
+ struct rproc_srm_pin_info *p;
+ int ret, nb_p;
+ unsigned int i;
+ const char *name;
+
+ if (!np)
+ return 0;
+
+ rproc_srm_dev->pctrl = devm_pinctrl_get(dev);
+ if (IS_ERR(rproc_srm_dev->pctrl))
+ return 0;
+
+ nb_p = of_property_count_strings(np, "pinctrl-names");
+ if (nb_p <= 0) {
+ dev_err(dev, "pinctrl-names not defined\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ for (i = 0; i < nb_p; i++) {
+ p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL);
+ if (!p) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ if (of_property_read_string_index(np, "pinctrl-names", i,
+ &name)) {
+ dev_err(dev, "no pinctrl-names (pin %d)\n", i);
+ ret = -EINVAL;
+ goto err;
+ }
+ p->name = devm_kstrdup(dev, name, GFP_KERNEL);
+
+ if (!strcmp(p->name, PINCTRL_STATE_DEFAULT)) {
+ if (rproc_srm_dev->early_boot)
+ dev_warn(dev, "pin config potentially overwritten!\n");
+ p->selected = true;
+ }
+
+ p->index = i;
+
+ list_add_tail(&p->list, &rproc_srm_dev->pin_list_head);
+ dev_dbg(dev, "found pin cfg %d (%s)\n", p->index, p->name);
+ }
+ return 0;
+
+err:
+ rproc_srm_dev_pins_put(rproc_srm_dev);
+ return ret;
+}
+
+/* Core */
+static int rproc_srm_dev_notify_cb(struct notifier_block *nb, unsigned long evt,
+ void *data)
+{
+ struct rproc_srm_dev *rproc_srm_dev =
+ container_of(nb, struct rproc_srm_dev, nb);
+ struct device *dev = rproc_srm_dev->dev;
+ struct rpmsg_srm_msg_desc *desc;
+ struct rpmsg_srm_msg *i, o;
+ int ret = 0;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ desc = (struct rpmsg_srm_msg_desc *)data;
+ i = desc->msg;
+ o = *i;
+
+ /* Check if 'device_id' (name / addr ) matches this device */
+ if (!strstr(dev_name(dev), i->device_id))
+ return NOTIFY_DONE;
+
+ switch (i->message_type) {
+ case RPROC_SRM_MSG_SETCONFIG:
+ switch (i->rsc_type) {
+ case RPROC_SRM_RSC_CLOCK:
+ ret = rproc_srm_dev_clock_set_cfg(rproc_srm_dev,
+ &i->clock_cfg);
+ if (!ret)
+ ret = rproc_srm_dev_clock_get_cfg(rproc_srm_dev,
+ &o.clock_cfg);
+ break;
+ case RPROC_SRM_RSC_PIN:
+ ret = rproc_srm_dev_pin_set_cfg(rproc_srm_dev,
+ &i->pin_cfg);
+ if (!ret)
+ ret = rproc_srm_dev_pin_get_cfg(rproc_srm_dev,
+ &o.pin_cfg);
+ break;
+ case RPROC_SRM_RSC_REGU:
+ ret = rproc_srm_dev_regu_set_cfg(rproc_srm_dev,
+ &i->regu_cfg);
+ if (!ret)
+ ret = rproc_srm_dev_regu_get_cfg(rproc_srm_dev,
+ &o.regu_cfg);
+ break;
+ default:
+ dev_warn(dev, "bad rsc type (%d)\n", i->rsc_type);
+ ret = -EINVAL;
+ break;
+ }
+ break;
+ case RPROC_SRM_MSG_GETCONFIG:
+ switch (i->rsc_type) {
+ case RPROC_SRM_RSC_CLOCK:
+ ret = rproc_srm_dev_clock_get_cfg(rproc_srm_dev,
+ &o.clock_cfg);
+ break;
+ case RPROC_SRM_RSC_PIN:
+ ret = rproc_srm_dev_pin_get_cfg(rproc_srm_dev,
+ &o.pin_cfg);
+ break;
+ case RPROC_SRM_RSC_REGU:
+ ret = rproc_srm_dev_regu_get_cfg(rproc_srm_dev,
+ &o.regu_cfg);
+ break;
+ default:
+ dev_warn(dev, "bad rsc type (%d)\n", i->rsc_type);
+ ret = -EINVAL;
+ break;
+ }
+ break;
+ default:
+ dev_warn(dev, "bad msg type (%d)\n", i->message_type);
+ ret = -EINVAL;
+ break;
+ }
+
+ /* Send return msg */
+ if (ret)
+ o.message_type = RPROC_SRM_MSG_ERROR;
+
+ ret = rpmsg_srm_send(desc->ept, &o);
+
+ return ret ? NOTIFY_BAD : NOTIFY_STOP;
+}
+
+static void
+rproc_srm_dev_unbind(struct device *dev, struct device *master, void *data)
+{
+ struct rproc_srm_dev *rproc_srm_dev = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ rproc_srm_dev_regus_unsetup(rproc_srm_dev);
+ rproc_srm_dev_clocks_unsetup(rproc_srm_dev);
+
+ /* For pins and IRQs: nothing to unsetup */
+}
+
+static int
+rproc_srm_dev_bind(struct device *dev, struct device *master, void *data)
+{
+ struct rproc_srm_dev *rproc_srm_dev = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ ret = rproc_srm_dev_clocks_setup(rproc_srm_dev);
+ if (ret)
+ return ret;
+
+ ret = rproc_srm_dev_regus_setup(rproc_srm_dev);
+ if (ret)
+ return ret;
+
+ ret = rproc_srm_dev_pins_setup(rproc_srm_dev);
+ if (ret)
+ return ret;
+
+ /* For IRQs: nothing to setup */
+ return 0;
+}
+
+static const struct component_ops rproc_srm_dev_ops = {
+ .bind = rproc_srm_dev_bind,
+ .unbind = rproc_srm_dev_unbind,
+};
+
+static int rproc_srm_dev_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rproc_srm_dev *rproc_srm_dev;
+ struct rproc *rproc;
+ int ret;
+
+ dev_dbg(dev, "%s for node %s\n", __func__, dev->of_node->name);
+
+ rproc_srm_dev = devm_kzalloc(dev, sizeof(struct rproc_srm_dev),
+ GFP_KERNEL);
+ if (!rproc_srm_dev)
+ return -ENOMEM;
+
+ rproc_srm_dev->dev = dev;
+ rproc = (struct rproc *)dev_get_drvdata(dev->parent->parent);
+ rproc_srm_dev->early_boot = rproc->early_boot;
+ rproc_srm_dev->core = dev_get_drvdata(dev->parent);
+
+ INIT_LIST_HEAD(&rproc_srm_dev->clk_list_head);
+ INIT_LIST_HEAD(&rproc_srm_dev->pin_list_head);
+ INIT_LIST_HEAD(&rproc_srm_dev->regu_list_head);
+ INIT_LIST_HEAD(&rproc_srm_dev->irq_list_head);
+
+ /* Get clocks, regu, irqs and pinctrl */
+ ret = rproc_srm_dev_clocks_get(rproc_srm_dev);
+ if (ret)
+ return ret;
+
+ ret = rproc_srm_dev_regus_get(rproc_srm_dev);
+ if (ret)
+ goto err_get;
+
+ ret = rproc_srm_dev_pins_get(rproc_srm_dev);
+ if (ret)
+ goto err_get;
+
+ ret = rproc_srm_dev_irqs_get(rproc_srm_dev);
+ if (ret)
+ goto err_get;
+
+ rproc_srm_dev->nb.notifier_call = rproc_srm_dev_notify_cb;
+ ret = rproc_srm_core_register_notifier(rproc_srm_dev->core,
+ &rproc_srm_dev->nb);
+ if (ret)
+ goto err_register;
+
+ dev_set_drvdata(dev, rproc_srm_dev);
+
+ return component_add(dev, &rproc_srm_dev_ops);
+
+err_register:
+ rproc_srm_core_unregister_notifier(rproc_srm_dev->core,
+ &rproc_srm_dev->nb);
+err_get:
+ rproc_srm_dev_irqs_put(rproc_srm_dev);
+ rproc_srm_dev_pins_put(rproc_srm_dev);
+ rproc_srm_dev_regus_put(rproc_srm_dev);
+ rproc_srm_dev_clocks_put(rproc_srm_dev);
+ return ret;
+}
+
+static int rproc_srm_dev_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rproc_srm_dev *rproc_srm_dev = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ component_del(dev, &rproc_srm_dev_ops);
+
+ rproc_srm_core_unregister_notifier(rproc_srm_dev->core,
+ &rproc_srm_dev->nb);
+
+ rproc_srm_dev_irqs_put(rproc_srm_dev);
+ rproc_srm_dev_regus_put(rproc_srm_dev);
+ rproc_srm_dev_pins_put(rproc_srm_dev);
+ rproc_srm_dev_clocks_put(rproc_srm_dev);
+
+ return 0;
+}
+
+static const struct of_device_id rproc_srm_dev_match[] = {
+ { .compatible = "rproc-srm-dev", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, rproc_srm_dev_match);
+
+static struct platform_driver rproc_srm_dev_driver = {
+ .probe = rproc_srm_dev_probe,
+ .remove = rproc_srm_dev_remove,
+ .driver = {
+ .name = "rproc-srm-dev",
+ .of_match_table = of_match_ptr(rproc_srm_dev_match),
+ },
+};
+
+module_platform_driver(rproc_srm_dev_driver);
+
+MODULE_AUTHOR("Fabien Dessenne <fabien.dessenne@st.com>");
+MODULE_DESCRIPTION("Remoteproc System Resource Manager driver - dev");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/remoteproc/stm32_rproc.c b/drivers/remoteproc/stm32_rproc.c
new file mode 100644
index 0000000..998de67
--- /dev/null
+++ b/drivers/remoteproc/stm32_rproc.c
@@ -0,0 +1,585 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Authors: Ludovic Barre <ludovic.barre@st.com> for STMicroelectronics.
+ * Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/interrupt.h>
+#include <linux/mailbox_client.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/regmap.h>
+#include <linux/remoteproc.h>
+#include <linux/reset.h>
+
+#include "remoteproc_internal.h"
+
+#define HOLD_BOOT 0
+#define RELEASE_BOOT 1
+
+#define MBOX_NB_VQ 2
+#define MBOX_NB_MBX 3
+
+#define STM32_SMC_RCC 0x82001000
+#define STM32_SMC_REG_WRITE 0x1
+
+#define STM32_MBX_VQ0 "vq0"
+#define STM32_MBX_VQ1 "vq1"
+#define STM32_MBX_SHUTDOWN "init_shdn"
+
+struct stm32_syscon {
+ struct regmap *map;
+ u32 reg;
+ u32 mask;
+};
+
+struct stm32_rproc_mem {
+ void __iomem *cpu_addr;
+ phys_addr_t bus_addr;
+ u32 dev_addr;
+ size_t size;
+};
+
+struct stm32_mbox {
+ const unsigned char name[10];
+ struct mbox_chan *chan;
+ struct mbox_client client;
+ int vq_id;
+};
+
+struct stm32_rproc {
+ struct reset_control *rst;
+ struct stm32_syscon hold_boot;
+ struct stm32_syscon pdds;
+ struct stm32_rproc_mem ram[2];
+ struct stm32_mbox mb[MBOX_NB_MBX];
+ bool secured_soc;
+ u32 rsc_addr;
+ u32 rsc_len;
+};
+
+static int stm32_rproc_elf_load_segments(struct rproc *rproc,
+ const struct firmware *fw)
+{
+ if (!rproc->early_boot)
+ return rproc_elf_load_segments(rproc, fw);
+
+ return 0;
+}
+
+static int stm32_rproc_mbox_idx(struct rproc *rproc, const unsigned char *name)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ddata->mb); i++) {
+ if (!strncmp(ddata->mb[i].name, name, strlen(name)))
+ return i;
+ }
+ dev_err(&rproc->dev, "mailbox %s not found\n", name);
+
+ return -EINVAL;
+}
+
+static int stm32_rproc_elf_load_rsc_table(struct rproc *rproc,
+ const struct firmware *fw)
+{
+ int status;
+ struct resource_table *table = NULL;
+ struct stm32_rproc *ddata = rproc->priv;
+ size_t tablesz = 0;
+
+ if (!rproc->early_boot) {
+ status = rproc_elf_load_rsc_table(rproc, fw);
+ if (status)
+ goto no_rsc_table;
+
+ return 0;
+ }
+
+ if (ddata->rsc_addr) {
+ tablesz = ddata->rsc_len;
+ table = (struct resource_table *)
+ rproc_da_to_va(rproc, (u64)ddata->rsc_addr,
+ ddata->rsc_len);
+ rproc->cached_table = kmemdup(table, tablesz, GFP_KERNEL);
+ if (!rproc->cached_table)
+ return -ENOMEM;
+
+ rproc->table_ptr = rproc->cached_table;
+ rproc->table_sz = tablesz;
+ return 0;
+ }
+
+ rproc->cached_table = NULL;
+ rproc->table_ptr = NULL;
+ rproc->table_sz = 0;
+
+no_rsc_table:
+ dev_warn(&rproc->dev, "not resource table found for this firmware\n");
+ return 0;
+}
+
+static struct resource_table *
+stm32_rproc_elf_find_loaded_rsc_table(struct rproc *rproc,
+ const struct firmware *fw)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+
+ if (!rproc->early_boot)
+ return rproc_elf_find_loaded_rsc_table(rproc, fw);
+
+ if (ddata->rsc_addr)
+ return (struct resource_table *)
+ rproc_da_to_va(rproc, (u64)ddata->rsc_addr,
+ ddata->rsc_len);
+
+ return NULL;
+}
+
+static int stm32_rproc_elf_sanity_check(struct rproc *rproc,
+ const struct firmware *fw)
+{
+ if (!rproc->early_boot)
+ return rproc_elf_sanity_check(rproc, fw);
+
+ return 0;
+}
+
+static u32 stm32_rproc_elf_get_boot_addr(struct rproc *rproc,
+ const struct firmware *fw)
+{
+ if (!rproc->early_boot)
+ return rproc_elf_get_boot_addr(rproc, fw);
+
+ return 0;
+}
+
+static irqreturn_t stm32_rproc_wdg(int irq, void *data)
+{
+ struct rproc *rproc = data;
+
+ rproc_report_crash(rproc, RPROC_WATCHDOG);
+
+ return IRQ_HANDLED;
+}
+
+static void stm32_rproc_mb_callback(struct mbox_client *cl, void *data)
+{
+ struct rproc *rproc = dev_get_drvdata(cl->dev);
+ struct stm32_mbox *mb = container_of(cl, struct stm32_mbox, client);
+
+ if (rproc_vq_interrupt(rproc, mb->vq_id) == IRQ_NONE)
+ dev_dbg(&rproc->dev, "no message found in vq%d\n", mb->vq_id);
+}
+
+static void stm32_rproc_free_mbox(struct rproc *rproc)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(ddata->mb); i++) {
+ if (ddata->mb[i].chan)
+ mbox_free_channel(ddata->mb[i].chan);
+ ddata->mb[i].chan = NULL;
+ }
+}
+
+static const struct stm32_mbox stm32_rproc_mbox[MBOX_NB_MBX] = {
+ {
+ .name = STM32_MBX_VQ0,
+ .vq_id = 0,
+ .client = {
+ .rx_callback = stm32_rproc_mb_callback,
+ .tx_block = false,
+ },
+ },
+ {
+ .name = STM32_MBX_VQ1,
+ .vq_id = 1,
+ .client = {
+ .rx_callback = stm32_rproc_mb_callback,
+ .tx_block = false,
+ },
+ },
+ {
+ .name = STM32_MBX_SHUTDOWN,
+ .vq_id = -1,
+ .client = {
+ .tx_block = true,
+ .tx_done = NULL,
+ .tx_tout = 500, /* 500 ms time out */
+ },
+ }
+};
+
+static void stm32_rproc_request_mbox(struct rproc *rproc)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ struct device *dev = &rproc->dev;
+ unsigned int i;
+ const unsigned char *name;
+ struct mbox_client *cl;
+
+ /* Initialise mailbox structure table */
+ memcpy(ddata->mb, stm32_rproc_mbox, sizeof(stm32_rproc_mbox));
+
+ for (i = 0; i < MBOX_NB_MBX; i++) {
+ name = ddata->mb[i].name;
+
+ cl = &ddata->mb[i].client;
+ cl->dev = dev->parent;
+
+ ddata->mb[i].chan = mbox_request_channel_byname(cl, name);
+ if (IS_ERR(ddata->mb[i].chan)) {
+ dev_warn(dev, "cannot get %s mbox\n", name);
+ ddata->mb[i].chan = NULL;
+ }
+ }
+}
+
+static int stm32_rproc_set_hold_boot(struct rproc *rproc, bool hold)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ struct stm32_syscon hold_boot = ddata->hold_boot;
+ struct arm_smccc_res smc_res;
+ int val, err;
+
+ val = hold ? HOLD_BOOT : RELEASE_BOOT;
+
+ if (ddata->secured_soc) {
+ arm_smccc_smc(STM32_SMC_RCC, STM32_SMC_REG_WRITE,
+ hold_boot.reg, val, 0, 0, 0, 0, &smc_res);
+ err = smc_res.a0;
+ } else {
+ err = regmap_update_bits(hold_boot.map, hold_boot.reg,
+ hold_boot.mask, val);
+ }
+
+ if (err)
+ dev_err(&rproc->dev, "failed to set hold boot\n");
+
+ return err;
+}
+
+static int stm32_rproc_start(struct rproc *rproc)
+{
+ int err;
+
+ /*
+ * If M4 previously started by bootloader, just guarantee holdboot
+ * is set to catch any crash.
+ */
+ if (!rproc->early_boot) {
+ err = stm32_rproc_set_hold_boot(rproc, false);
+ if (err)
+ return err;
+ }
+
+ return stm32_rproc_set_hold_boot(rproc, true);
+}
+
+static int stm32_rproc_stop(struct rproc *rproc)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ int err, dummy_data, idx;
+
+ /* request shutdown of the remote processor */
+ if (rproc->state != RPROC_OFFLINE) {
+ idx = stm32_rproc_mbox_idx(rproc, STM32_MBX_SHUTDOWN);
+ if (idx >= 0 && ddata->mb[idx].chan) {
+ /* a dummy data is sent to allow to block on transmit */
+ err = mbox_send_message(ddata->mb[idx].chan,
+ &dummy_data);
+ if (err < 0)
+ dev_warn(&rproc->dev, "warning: remote FW shutdown without ack\n");
+ }
+ }
+
+ err = stm32_rproc_set_hold_boot(rproc, true);
+ if (err)
+ return err;
+
+ err = reset_control_assert(ddata->rst);
+ if (err) {
+ dev_err(&rproc->dev, "failed to assert the reset\n");
+ return err;
+ }
+
+ /* to allow platform Standby power mode, set remote proc Deep Sleep */
+ if (ddata->pdds.map) {
+ err = regmap_update_bits(ddata->pdds.map, ddata->pdds.reg,
+ ddata->pdds.mask, 1);
+ if (err) {
+ dev_err(&rproc->dev, "failed to set pdds\n");
+ return err;
+ }
+ }
+
+ /* Reset early_boot state as we stop the co-processor */
+ rproc->early_boot = false;
+
+ return 0;
+}
+
+static void stm32_rproc_kick(struct rproc *rproc, int vqid)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ unsigned int i;
+ int err;
+
+ if (WARN_ON(vqid >= MBOX_NB_VQ))
+ return;
+
+ for (i = 0; i < MBOX_NB_MBX; i++) {
+ if (vqid != ddata->mb[i].vq_id)
+ continue;
+ if (!ddata->mb[i].chan)
+ return;
+ err = mbox_send_message(ddata->mb[i].chan, (void *)vqid);
+ if (err < 0)
+ dev_err(&rproc->dev, "%s: failed (%s, err:%d)\n",
+ __func__, ddata->mb[i].name, err);
+ return;
+ }
+}
+
+static void *stm32_rproc_da_to_va(struct rproc *rproc, u64 da, int len)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ void *va = NULL;
+ u32 offset;
+ unsigned int i;
+
+ for (i = 0; i < 2; i++) {
+ if (da >= ddata->ram[i].dev_addr && da + len <=
+ ddata->ram[i].dev_addr + ddata->ram[i].size) {
+ offset = da - ddata->ram[i].dev_addr;
+ /* __force to make sparse happy with type conversion */
+ va = (__force void *)(ddata->ram[i].cpu_addr + offset);
+ break;
+ }
+ }
+
+ return va;
+}
+
+static struct rproc_ops st_rproc_ops = {
+ .start = stm32_rproc_start,
+ .stop = stm32_rproc_stop,
+ .kick = stm32_rproc_kick,
+ .da_to_va = stm32_rproc_da_to_va,
+ .load = stm32_rproc_elf_load_segments,
+ .parse_fw = stm32_rproc_elf_load_rsc_table,
+ .find_loaded_rsc_table = stm32_rproc_elf_find_loaded_rsc_table,
+ .sanity_check = stm32_rproc_elf_sanity_check,
+ .get_boot_addr = stm32_rproc_elf_get_boot_addr,
+};
+
+static const struct of_device_id stm32_rproc_match[] = {
+ { .compatible = "st,stm32mp1-rproc" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, stm32_rproc_match);
+
+static int stm32_rproc_get_syscon(struct device_node *np, const char *prop,
+ struct stm32_syscon *syscon)
+{
+ int err = 0;
+
+ syscon->map = syscon_regmap_lookup_by_phandle(np, prop);
+ if (IS_ERR(syscon->map)) {
+ err = PTR_ERR(syscon->map);
+ syscon->map = NULL;
+ goto out;
+ }
+
+ err = of_property_read_u32_index(np, prop, 1, &syscon->reg);
+ if (err)
+ goto out;
+
+ err = of_property_read_u32_index(np, prop, 2, &syscon->mask);
+
+out:
+ return err;
+}
+
+static int stm32_rproc_parse_dt(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct rproc *rproc = platform_get_drvdata(pdev);
+ struct stm32_rproc *ddata = rproc->priv;
+ struct resource *res;
+ struct stm32_syscon tz;
+ unsigned int tzen, i = 0;
+ int err, irq;
+
+ irq = platform_get_irq_byname(pdev, "wdg");
+ if (irq > 0) {
+ err = devm_request_irq(dev, irq, stm32_rproc_wdg, 0,
+ dev_name(dev), rproc);
+ if (err) {
+ dev_err(dev, "failed to request wdg irq\n");
+ return err;
+ }
+
+ dev_info(dev, "wdg irq registered\n");
+ }
+
+ ddata->rst = devm_reset_control_get(dev, "mcu_rst");
+ if (IS_ERR(ddata->rst)) {
+ dev_err(dev, "failed to get mcu reset\n");
+ return PTR_ERR(ddata->rst);
+ }
+
+ /*
+ * if platform is secured the hold boot bit must be written by
+ * smc call and read normally.
+ * if not secure the hold boot bit could be read/write normally
+ */
+ err = stm32_rproc_get_syscon(np, "st,syscfg-tz", &tz);
+ if (err) {
+ dev_err(dev, "failed to get tz syscfg\n");
+ return err;
+ }
+
+ err = regmap_read(tz.map, tz.reg, &tzen);
+ if (err) {
+ dev_err(&rproc->dev, "failed to read tzen\n");
+ return err;
+ }
+ ddata->secured_soc = tzen & tz.mask;
+
+ err = stm32_rproc_get_syscon(np, "st,syscfg-holdboot",
+ &ddata->hold_boot);
+ if (err) {
+ dev_err(dev, "failed to get hold boot\n");
+ return err;
+ }
+
+ err = stm32_rproc_get_syscon(np, "st,syscfg-pdds", &ddata->pdds);
+ if (err)
+ dev_warn(dev, "failed to get pdds\n");
+
+ while ((res = platform_get_resource(pdev, IORESOURCE_MEM, i))) {
+ ddata->ram[i].cpu_addr = devm_ioremap_resource(dev, res);
+ if (IS_ERR(ddata->ram[i].cpu_addr))
+ return err;
+
+ ddata->ram[i].bus_addr = res->start;
+ ddata->ram[i].size = resource_size(res);
+
+ /*
+ * the m4 has retram at address 0 in its view (DA)
+ * so for retram DA=0x0 PA=bus_addr else DA=PA=bus_addr
+ */
+ if (i == 0)
+ ddata->ram[i].dev_addr = 0x0;
+ else
+ ddata->ram[i].dev_addr = ddata->ram[i].bus_addr;
+
+ i++;
+ }
+
+ rproc->auto_boot = of_property_read_bool(np, "auto_boot");
+ rproc->recovery_disabled = !of_property_read_bool(np, "recovery");
+
+ if (of_property_read_bool(np, "early-booted")) {
+ rproc->early_boot = true;
+
+ err = of_property_read_u32(np, "rsc-address", &ddata->rsc_addr);
+ if (!err) {
+ err = of_property_read_u32(np, "rsc-size",
+ &ddata->rsc_len);
+
+ if (err) {
+ dev_err(dev, "resource table size required as address defined\n");
+ return err;
+ }
+ }
+ }
+
+ of_reserved_mem_device_init(dev);
+
+ return 0;
+}
+
+static int stm32_rproc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct stm32_rproc *ddata;
+ struct device_node *np = dev->of_node;
+ struct rproc *rproc;
+ int ret;
+
+ rproc = rproc_alloc(dev, np->name, &st_rproc_ops, NULL, sizeof(*ddata));
+ if (!rproc)
+ return -ENOMEM;
+
+ rproc->has_iommu = false;
+ ddata = rproc->priv;
+
+ platform_set_drvdata(pdev, rproc);
+
+ ret = stm32_rproc_parse_dt(pdev);
+ if (ret)
+ goto free_rproc;
+
+ if (!rproc->early_boot) {
+ ret = stm32_rproc_stop(rproc);
+ if (ret)
+ goto free_mem;
+ }
+
+ stm32_rproc_request_mbox(rproc);
+
+ ret = rproc_add(rproc);
+ if (ret)
+ goto free_mb;
+
+ return 0;
+
+free_mb:
+ stm32_rproc_free_mbox(rproc);
+free_mem:
+ of_reserved_mem_device_release(dev);
+free_rproc:
+ rproc_free(rproc);
+ return ret;
+}
+
+static int stm32_rproc_remove(struct platform_device *pdev)
+{
+ struct rproc *rproc = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+
+ if (atomic_read(&rproc->power) > 0)
+ dev_warn(dev, "Releasing rproc while firmware running!\n");
+
+ rproc_del(rproc);
+ stm32_rproc_free_mbox(rproc);
+ of_reserved_mem_device_release(dev);
+ rproc_free(rproc);
+
+ return 0;
+}
+
+static struct platform_driver stm32_rproc_driver = {
+ .probe = stm32_rproc_probe,
+ .remove = stm32_rproc_remove,
+ .driver = {
+ .name = "stm32-rproc",
+ .of_match_table = of_match_ptr(stm32_rproc_match),
+ },
+};
+module_platform_driver(stm32_rproc_driver);
+
+MODULE_DESCRIPTION("STM32 Remote Processor Control Driver");
+MODULE_AUTHOR("Ludovic Barre <ludovic.barre@st.com>");
+MODULE_LICENSE("GPL v2");
+
diff --git a/drivers/rpmsg/Kconfig b/drivers/rpmsg/Kconfig
index d0322b4..88759a4 100644
--- a/drivers/rpmsg/Kconfig
+++ b/drivers/rpmsg/Kconfig
@@ -55,4 +55,13 @@ config RPMSG_VIRTIO
select RPMSG
select VIRTIO
+config RPMSG_TTY
+ tristate "RPMSG tty driver"
+ depends on RPMSG
+ help
+ Say y here to export rpmsg endpoints as tty console, usually found
+ in /dev/ttyRPMSG.
+ This makes it possible for user-space programs to send and receive
+ rpmsg messages as a standard tty protocol.
+
endmenu
diff --git a/drivers/rpmsg/Makefile b/drivers/rpmsg/Makefile
index 9aa8595..107145c 100644
--- a/drivers/rpmsg/Makefile
+++ b/drivers/rpmsg/Makefile
@@ -5,4 +5,5 @@ obj-$(CONFIG_RPMSG_QCOM_GLINK_RPM) += qcom_glink_rpm.o
obj-$(CONFIG_RPMSG_QCOM_GLINK_NATIVE) += qcom_glink_native.o
obj-$(CONFIG_RPMSG_QCOM_GLINK_SMEM) += qcom_glink_smem.o
obj-$(CONFIG_RPMSG_QCOM_SMD) += qcom_smd.o
+obj-$(CONFIG_RPMSG_TTY) += rpmsg_tty.o
obj-$(CONFIG_RPMSG_VIRTIO) += virtio_rpmsg_bus.o
diff --git a/drivers/rpmsg/rpmsg_core.c b/drivers/rpmsg/rpmsg_core.c
index 8122807..edb8549 100644
--- a/drivers/rpmsg/rpmsg_core.c
+++ b/drivers/rpmsg/rpmsg_core.c
@@ -283,6 +283,25 @@ int rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst,
}
EXPORT_SYMBOL(rpmsg_trysend_offchannel);
+/**
+ * rpmsg_get_buffer_size()
+ * This function returns buffer size available for sending messages.
+ *
+ * @ept: the rpmsg endpoint
+ *
+ * Returns buffer size on success and an appropriate error value on failure.
+ */
+int rpmsg_get_buffer_size(struct rpmsg_endpoint *ept)
+{
+ if (WARN_ON(!ept))
+ return -EINVAL;
+ if (!ept->ops->get_buffer_size)
+ return -ENXIO;
+
+ return ept->ops->get_buffer_size(ept);
+}
+EXPORT_SYMBOL(rpmsg_get_buffer_size);
+
/*
* match an rpmsg channel with a channel info struct.
* this is used to make sure we're not creating rpmsg devices for channels
diff --git a/drivers/rpmsg/rpmsg_internal.h b/drivers/rpmsg/rpmsg_internal.h
index 0d791c3..65bcb52 100644
--- a/drivers/rpmsg/rpmsg_internal.h
+++ b/drivers/rpmsg/rpmsg_internal.h
@@ -46,6 +46,7 @@ struct rpmsg_device_ops {
* @trysend: see @rpmsg_trysend(), required
* @trysendto: see @rpmsg_trysendto(), optional
* @trysend_offchannel: see @rpmsg_trysend_offchannel(), optional
+ * @get_buffer_size: see @rpmsg_get_buffer_size(), optional
*
* Indirection table for the operations that a rpmsg backend should implement.
* In addition to @destroy_ept, the backend must at least implement @send and
@@ -65,6 +66,7 @@ struct rpmsg_endpoint_ops {
void *data, int len);
__poll_t (*poll)(struct rpmsg_endpoint *ept, struct file *filp,
poll_table *wait);
+ int (*get_buffer_size)(struct rpmsg_endpoint *ept);
};
int rpmsg_register_device(struct rpmsg_device *rpdev);
diff --git a/drivers/rpmsg/rpmsg_tty.c b/drivers/rpmsg/rpmsg_tty.c
new file mode 100644
index 0000000..2826118
--- /dev/null
+++ b/drivers/rpmsg/rpmsg_tty.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Arnaud Pouliquen <arnaud.pouliquen@st.com> for STMicroelectronics.
+ * Derived from the imx-rmpsg and omap-rpmsg implementations.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/rpmsg.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/virtio.h>
+
+#define MAX_TTY_RPMSG_INDEX 32 /* Should be enough for a while */
+
+static LIST_HEAD(rpmsg_tty_list); /* tty instances list */
+static DEFINE_MUTEX(rpmsg_tty_lock); /* protect tty list */
+
+static struct tty_driver *rpmsg_tty_driver;
+static struct tty_port_operations rpmsg_tty_port_ops = { };
+
+struct rpmsg_tty_port {
+ struct tty_port port; /* TTY port data */
+ struct list_head list; /* TTY device list */
+ u32 id; /* tty rpmsg index */
+ spinlock_t rx_lock; /* message reception lock */
+ struct rpmsg_device *rpdev; /* handle rpmsg device */
+};
+
+static int rpmsg_tty_cb(struct rpmsg_device *rpdev, void *data, int len,
+ void *priv, u32 src)
+{
+ int space;
+ unsigned char *cbuf;
+ struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
+
+ /* Flush the recv-ed none-zero data to tty node */
+ if (len == 0)
+ return -EINVAL;
+
+ dev_dbg(&rpdev->dev, "msg(<- src 0x%x) len %d\n", src, len);
+
+ print_hex_dump_debug(__func__, DUMP_PREFIX_NONE, 16, 1, data, len,
+ true);
+
+ spin_lock(&cport->rx_lock);
+ space = tty_prepare_flip_string(&cport->port, &cbuf, len);
+ if (space <= 0) {
+ dev_err(&rpdev->dev, "No memory for tty_prepare_flip_string\n");
+ spin_unlock(&cport->rx_lock);
+ return -ENOMEM;
+ }
+
+ if (space != len)
+ dev_warn(&rpdev->dev, "Trunc buffer from %d to %d\n",
+ len, space);
+
+ memcpy(cbuf, data, space);
+ tty_flip_buffer_push(&cport->port);
+ spin_unlock(&cport->rx_lock);
+
+ return 0;
+}
+
+static struct rpmsg_tty_port *rpmsg_tty_get(unsigned int index)
+{
+ struct rpmsg_tty_port *cport;
+
+ mutex_lock(&rpmsg_tty_lock);
+ list_for_each_entry(cport, &rpmsg_tty_list, list) {
+ if (index == cport->id) {
+ mutex_unlock(&rpmsg_tty_lock);
+ return cport;
+ }
+ }
+ mutex_unlock(&rpmsg_tty_lock);
+
+ return NULL;
+}
+
+static int rpmsg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+ struct rpmsg_tty_port *cport = rpmsg_tty_get(tty->index);
+
+ if (!cport)
+ return -ENODEV;
+
+ return tty_port_install(&cport->port, driver, tty);
+}
+
+static int rpmsg_tty_open(struct tty_struct *tty, struct file *filp)
+{
+ struct rpmsg_tty_port *cport = rpmsg_tty_get(tty->index);
+
+ if (!cport)
+ return -ENODEV;
+
+ return tty_port_open(tty->port, tty, filp);
+}
+
+static void rpmsg_tty_close(struct tty_struct *tty, struct file *filp)
+{
+ struct rpmsg_tty_port *cport = rpmsg_tty_get(tty->index);
+
+ if (!cport)
+ return;
+ return tty_port_close(tty->port, tty, filp);
+}
+
+static int rpmsg_tty_write(struct tty_struct *tty, const unsigned char *buf,
+ int total)
+{
+ int count, ret = 0;
+ const unsigned char *tbuf;
+ struct rpmsg_tty_port *cport = container_of(tty->port,
+ struct rpmsg_tty_port, port);
+ struct rpmsg_device *rpdev = cport->rpdev;
+ int msg_size;
+
+ dev_dbg(&rpdev->dev, "%s: send message from tty->index = %d\n",
+ __func__, tty->index);
+
+ if (!buf) {
+ dev_err(&rpdev->dev, "buf shouldn't be null.\n");
+ return -ENOMEM;
+ }
+
+ msg_size = rpmsg_get_buffer_size(rpdev->ept);
+ if (msg_size < 0)
+ return msg_size;
+
+ count = total;
+ tbuf = buf;
+ do {
+ /* send a message to our remote processor */
+ ret = rpmsg_send(rpdev->ept, (void *)tbuf,
+ count > msg_size ? msg_size : count);
+ if (ret) {
+ dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
+ return ret;
+ }
+
+ if (count > msg_size) {
+ count -= msg_size;
+ tbuf += msg_size;
+ } else {
+ count = 0;
+ }
+ } while (count > 0);
+
+ return total;
+}
+
+static int rpmsg_tty_write_room(struct tty_struct *tty)
+{
+ struct rpmsg_tty_port *cport = container_of(tty->port,
+ struct rpmsg_tty_port, port);
+ struct rpmsg_device *rpdev = cport->rpdev;
+
+ /* report the space in the rpmsg buffer */
+ return rpmsg_get_buffer_size(rpdev->ept);
+}
+
+static const struct tty_operations rpmsg_tty_ops = {
+ .install = rpmsg_tty_install,
+ .open = rpmsg_tty_open,
+ .close = rpmsg_tty_close,
+ .write = rpmsg_tty_write,
+ .write_room = rpmsg_tty_write_room,
+};
+
+static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
+{
+ struct rpmsg_tty_port *cport, *tmp;
+ unsigned int index;
+ struct device *tty_dev;
+
+ cport = devm_kzalloc(&rpdev->dev, sizeof(*cport), GFP_KERNEL);
+ if (!cport)
+ return -ENOMEM;
+
+ tty_port_init(&cport->port);
+ cport->port.ops = &rpmsg_tty_port_ops;
+ spin_lock_init(&cport->rx_lock);
+
+ cport->port.low_latency = cport->port.flags | ASYNC_LOW_LATENCY;
+
+ cport->rpdev = rpdev;
+
+ /* get free index */
+ mutex_lock(&rpmsg_tty_lock);
+ for (index = 0; index < MAX_TTY_RPMSG_INDEX; index++) {
+ bool id_found = false;
+
+ list_for_each_entry(tmp, &rpmsg_tty_list, list) {
+ if (index == tmp->id) {
+ id_found = true;
+ break;
+ }
+ }
+ if (!id_found)
+ break;
+ }
+
+ tty_dev = tty_port_register_device(&cport->port, rpmsg_tty_driver,
+ index, &rpdev->dev);
+ if (IS_ERR(tty_dev)) {
+ dev_err(&rpdev->dev, "failed to register tty port\n");
+ tty_port_destroy(&cport->port);
+ mutex_unlock(&rpmsg_tty_lock);
+ return PTR_ERR(tty_dev);
+ }
+
+ cport->id = index;
+ list_add_tail(&cport->list, &rpmsg_tty_list);
+ mutex_unlock(&rpmsg_tty_lock);
+ dev_set_drvdata(&rpdev->dev, cport);
+
+ dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x : ttyRPMSG%d\n",
+ rpdev->src, rpdev->dst, index);
+
+ return 0;
+}
+
+static void rpmsg_tty_remove(struct rpmsg_device *rpdev)
+{
+ struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
+
+ /* User hang up to release the tty */
+ if (tty_port_initialized(&cport->port))
+ tty_port_tty_hangup(&cport->port, false);
+ tty_port_destroy(&cport->port);
+ tty_unregister_device(rpmsg_tty_driver, cport->id);
+ list_del(&cport->list);
+
+ dev_info(&rpdev->dev, "rpmsg tty device %d is removed\n", cport->id);
+}
+
+static struct rpmsg_device_id rpmsg_driver_tty_id_table[] = {
+ { .name = "rpmsg-tty-channel" },
+ { },
+};
+MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_tty_id_table);
+
+static struct rpmsg_driver rpmsg_tty_rmpsg_drv = {
+ .drv.name = KBUILD_MODNAME,
+ .drv.owner = THIS_MODULE,
+ .id_table = rpmsg_driver_tty_id_table,
+ .probe = rpmsg_tty_probe,
+ .callback = rpmsg_tty_cb,
+ .remove = rpmsg_tty_remove,
+};
+
+static int __init rpmsg_tty_init(void)
+{
+ int err;
+
+ rpmsg_tty_driver = tty_alloc_driver(MAX_TTY_RPMSG_INDEX, 0);
+ if (IS_ERR(rpmsg_tty_driver))
+ return PTR_ERR(rpmsg_tty_driver);
+
+ rpmsg_tty_driver->driver_name = "rpmsg_tty";
+ rpmsg_tty_driver->name = "ttyRPMSG";
+ rpmsg_tty_driver->major = TTYAUX_MAJOR;
+ rpmsg_tty_driver->minor_start = 3;
+ rpmsg_tty_driver->type = TTY_DRIVER_TYPE_CONSOLE;
+ rpmsg_tty_driver->init_termios = tty_std_termios;
+ rpmsg_tty_driver->flags = TTY_DRIVER_REAL_RAW |
+ TTY_DRIVER_DYNAMIC_DEV;
+
+ tty_set_operations(rpmsg_tty_driver, &rpmsg_tty_ops);
+
+ err = tty_register_driver(rpmsg_tty_driver);
+ if (err < 0) {
+ pr_err("Couldn't install rpmsg tty driver: err %d\n", err);
+ goto tty_error;
+ }
+ err = register_rpmsg_driver(&rpmsg_tty_rmpsg_drv);
+
+ if (!err)
+ return 0;
+
+ tty_unregister_driver(rpmsg_tty_driver);
+
+tty_error:
+ put_tty_driver(rpmsg_tty_driver);
+
+ return err;
+}
+
+static void __exit rpmsg_tty_exit(void)
+{
+ unregister_rpmsg_driver(&rpmsg_tty_rmpsg_drv);
+ tty_unregister_driver(rpmsg_tty_driver);
+ put_tty_driver(rpmsg_tty_driver);
+}
+
+module_init(rpmsg_tty_init);
+module_exit(rpmsg_tty_exit);
+
+MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>");
+MODULE_DESCRIPTION("virtio remote processor messaging tty driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c
index 664f957..481eaea 100644
--- a/drivers/rpmsg/virtio_rpmsg_bus.c
+++ b/drivers/rpmsg/virtio_rpmsg_bus.c
@@ -175,6 +175,7 @@ static int virtio_rpmsg_trysendto(struct rpmsg_endpoint *ept, void *data,
int len, u32 dst);
static int virtio_rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src,
u32 dst, void *data, int len);
+static int virtio_get_buffer_size(struct rpmsg_endpoint *ept);
static const struct rpmsg_endpoint_ops virtio_endpoint_ops = {
.destroy_ept = virtio_rpmsg_destroy_ept,
@@ -184,6 +185,7 @@ static const struct rpmsg_endpoint_ops virtio_endpoint_ops = {
.trysend = virtio_rpmsg_trysend,
.trysendto = virtio_rpmsg_trysendto,
.trysend_offchannel = virtio_rpmsg_trysend_offchannel,
+ .get_buffer_size = virtio_get_buffer_size,
};
/**
@@ -699,6 +701,15 @@ static int virtio_rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src,
return rpmsg_send_offchannel_raw(rpdev, src, dst, data, len, false);
}
+static int virtio_get_buffer_size(struct rpmsg_endpoint *ept)
+{
+ struct rpmsg_device *rpdev = ept->rpdev;
+ struct virtio_rpmsg_channel *vch = to_virtio_rpmsg_channel(rpdev);
+ struct virtproc_info *vrp = vch->vrp;
+
+ return vrp->buf_size;
+}
+
static int rpmsg_recv_single(struct virtproc_info *vrp, struct device *dev,
struct rpmsg_hdr *msg, unsigned int len)
{
diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h
index e3c5d85..787fd18 100644
--- a/include/linux/remoteproc.h
+++ b/include/linux/remoteproc.h
@@ -440,6 +440,7 @@ struct rproc_dump_segment {
* @table_sz: size of @cached_table
* @has_iommu: flag to indicate if remote processor is behind an MMU
* @dump_segments: list of segments in the firmware
+ * @early_boot: remote processor has been booted before kernel boot
*/
struct rproc {
struct list_head node;
@@ -472,6 +473,7 @@ struct rproc {
bool has_iommu;
bool auto_boot;
struct list_head dump_segments;
+ bool early_boot;
};
/**
diff --git a/include/linux/rpmsg.h b/include/linux/rpmsg.h
index 9fe156d..2af7674 100644
--- a/include/linux/rpmsg.h
+++ b/include/linux/rpmsg.h
@@ -135,6 +135,7 @@ int rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst,
__poll_t rpmsg_poll(struct rpmsg_endpoint *ept, struct file *filp,
poll_table *wait);
+int rpmsg_get_buffer_size(struct rpmsg_endpoint *ept);
#else
static inline int register_rpmsg_device(struct rpmsg_device *dev)
@@ -242,6 +243,14 @@ static inline __poll_t rpmsg_poll(struct rpmsg_endpoint *ept,
return 0;
}
+static int rpmsg_get_buffer_size(struct rpmsg_endpoint *ept)
+{
+ /* This shouldn't be possible */
+ WARN_ON(1);
+
+ return -ENXIO;
+}
+
#endif /* IS_ENABLED(CONFIG_RPMSG) */
/* use a macro to avoid include chaining to get THIS_MODULE */
--
2.7.4