@@ -21,6 +21,16 @@ config REMOTEPROC_SRM_CORE
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
@@ -10,6 +10,7 @@ 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
new file mode 100644
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2017, STMicroelectronics - All Rights Reserved
+ * Author: Fabien Dessenne <fabien.dessenne@st.com>.
+ *
+ * License type: GPLv2
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#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>
+
+#define SUPPLY_SUFFIX "-supply"
+
+struct rproc_srm_clk_info {
+ struct list_head list;
+ unsigned int index;
+ struct clk *clk;
+ const char *name;
+ bool enabled;
+};
+
+struct rproc_srm_pin_info {
+ struct list_head list;
+ unsigned int index;
+ char *name;
+};
+
+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 pinctrl *pctrl;
+
+ 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_put_irqs(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct rproc_srm_irq_info *info, *tmp;
+ struct list_head *irq_head = &rproc_srm_dev->irq_list_head;
+
+ list_for_each_entry_safe(info, tmp, irq_head, list) {
+ devm_free_irq(dev, info->irq, NULL);
+ dev_dbg(dev, "Put irq %d (%s)\n", info->irq, info->name);
+ list_del(&info->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_get_irqs(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;
+ struct list_head *irq_head = &rproc_srm_dev->irq_list_head;
+ const char *name;
+ int nr, ret, irq;
+ unsigned int i;
+
+ if (!np)
+ return 0;
+
+ nr = platform_irq_count(pdev);
+ dev_dbg(dev, "irq count = %d\n", nr);
+ if (!nr)
+ 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);
+
+ if (!of_property_read_string_index(np, "interrupt-names", i,
+ &name))
+ info->name = devm_kstrdup(dev, name, GFP_KERNEL);
+
+ info->index = i;
+
+ list_add_tail(&info->list, irq_head);
+ dev_dbg(dev, "Got irq %d (%s)\n", info->irq, info->name);
+ }
+
+ return 0;
+
+err:
+ rproc_srm_dev_put_irqs(rproc_srm_dev);
+
+ return ret;
+}
+
+/* Clocks */
+static void rproc_srm_dev_deconfig_clocks(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct rproc_srm_clk_info *c;
+ struct list_head *clk_head = &rproc_srm_dev->clk_list_head;
+
+ list_for_each_entry(c, clk_head, list) {
+ if (!c->enabled)
+ continue;
+
+ clk_disable_unprepare(c->clk);
+ c->enabled = false;
+ dev_dbg(rproc_srm_dev->dev, "clk %d (%s) deconfigured\n",
+ c->index, c->name);
+ }
+}
+
+static int rproc_srm_dev_config_clocks(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct rproc_srm_clk_info *c;
+ struct list_head *clk_head = &rproc_srm_dev->clk_list_head;
+ int ret;
+
+ /* Note: not only configuring, but also enabling */
+
+ list_for_each_entry(c, clk_head, list) {
+ if (c->enabled)
+ continue;
+
+ ret = clk_prepare_enable(c->clk);
+ if (ret) {
+ dev_err(rproc_srm_dev->dev, "clk %d (%s) cfg failed\n",
+ c->index, c->name);
+ rproc_srm_dev_deconfig_clocks(rproc_srm_dev);
+ return ret;
+ }
+ c->enabled = true;
+ dev_dbg(rproc_srm_dev->dev, "clk %d (%s) configured\n",
+ c->index, c->name);
+ }
+
+ return 0;
+}
+
+static void rproc_srm_dev_put_clocks(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct rproc_srm_clk_info *c, *tmp;
+ struct list_head *clk_head = &rproc_srm_dev->clk_list_head;
+
+ list_for_each_entry_safe(c, tmp, clk_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_get_clocks(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;
+ struct list_head *clk_head = &rproc_srm_dev->clk_list_head;
+ 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;
+ }
+
+ if (!of_property_read_string_index(np, "clock-names", i,
+ &name))
+ c->name = devm_kstrdup(dev, name, GFP_KERNEL);
+
+ c->index = i;
+
+ list_add_tail(&c->list, clk_head);
+ dev_dbg(dev, "got clock %d (%s)\n", c->index, c->name);
+ }
+
+ return 0;
+
+err:
+ rproc_srm_dev_put_clocks(rproc_srm_dev);
+ return ret;
+}
+
+/* Regulators */
+static void rproc_srm_dev_deconfig_regus(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct rproc_srm_regu_info *r;
+ struct list_head *regu_head = &rproc_srm_dev->regu_list_head;
+
+ list_for_each_entry(r, regu_head, list) {
+ if (!r->enabled)
+ continue;
+
+ regulator_disable(r->regu);
+ r->enabled = false;
+ dev_dbg(rproc_srm_dev->dev, "regu %d (%s) disabled\n",
+ r->index, r->name);
+ }
+}
+
+static int rproc_srm_dev_config_regus(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct rproc_srm_regu_info *r;
+ struct list_head *regu_head = &rproc_srm_dev->regu_list_head;
+ int ret;
+
+ /* Enable all the regulators */
+ list_for_each_entry(r, regu_head, list) {
+ if (r->enabled)
+ 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_deconfig_regus(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 void rproc_srm_dev_put_regus(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct rproc_srm_regu_info *r, *tmp;
+ struct list_head *regu_head = &rproc_srm_dev->regu_list_head;
+
+ list_for_each_entry_safe(r, tmp, regu_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_get_regus(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;
+ struct list_head *regu_head = &rproc_srm_dev->regu_list_head;
+ int ret, nb_s = 0;
+
+ if (!np)
+ return 0;
+
+ for_each_property_of_node(np, p) {
+ n = strstr(p->name, SUPPLY_SUFFIX);
+ 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_SUFFIX)] = '\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, regu_head);
+ dev_dbg(dev, "got regu %d (%s)\n", r->index, r->name);
+ }
+
+ return 0;
+
+err_list:
+ rproc_srm_dev_put_regus(rproc_srm_dev);
+ return ret;
+}
+
+/* Pins */
+static void rproc_srm_dev_put_pins(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct rproc_srm_pin_info *p, *tmp;
+ struct list_head *pin_head = &rproc_srm_dev->pin_list_head;
+
+ list_for_each_entry_safe(p, tmp, pin_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_get_pins(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;
+ struct list_head *pin_head = &rproc_srm_dev->pin_list_head;
+ int ret, nb_p;
+ unsigned int i;
+ const char *name;
+
+ if (!np)
+ return 0;
+
+ /* Assumption here is that "default" pinctrl applied before probe */
+
+ 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);
+
+ p->index = i;
+
+ list_add_tail(&p->list, pin_head);
+ dev_dbg(dev, "found pin cfg %d (%s)\n", p->index, p->name);
+ }
+ return 0;
+
+err:
+ rproc_srm_dev_put_pins(rproc_srm_dev);
+ return ret;
+}
+
+/* Core */
+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_deconfig_regus(rproc_srm_dev);
+ rproc_srm_dev_deconfig_clocks(rproc_srm_dev);
+
+ /* For pins and IRQs: nothing to deconfigure */
+}
+
+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_config_clocks(rproc_srm_dev);
+ if (ret)
+ return ret;
+
+ ret = rproc_srm_dev_config_regus(rproc_srm_dev);
+ if (ret)
+ return ret;
+
+ /* For pins and IRQs: nothing to configure */
+ 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;
+ 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;
+ 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_get_clocks(rproc_srm_dev);
+ if (ret)
+ return ret;
+
+ ret = rproc_srm_dev_get_regus(rproc_srm_dev);
+ if (ret)
+ goto err;
+
+ ret = rproc_srm_dev_get_pins(rproc_srm_dev);
+ if (ret)
+ goto err;
+
+ ret = rproc_srm_dev_get_irqs(rproc_srm_dev);
+ if (ret)
+ goto err;
+
+ dev_set_drvdata(dev, rproc_srm_dev);
+
+ return component_add(dev, &rproc_srm_dev_ops);
+
+err:
+ rproc_srm_dev_put_irqs(rproc_srm_dev);
+ rproc_srm_dev_put_pins(rproc_srm_dev);
+ rproc_srm_dev_put_regus(rproc_srm_dev);
+ rproc_srm_dev_put_clocks(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_dev_put_irqs(rproc_srm_dev);
+ rproc_srm_dev_put_regus(rproc_srm_dev);
+ rproc_srm_dev_put_pins(rproc_srm_dev);
+ rproc_srm_dev_put_clocks(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");