2649 lines
69 KiB
Diff
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
|
|
|