summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2020-10-14 12:46:52 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2020-10-14 12:46:52 +0200
commit2aa7ff0ae2bf2db33e358f18bec5a0239d244aa5 (patch)
tree9417d44f527a5ed3876ff651946cc0a15f658e87 /drivers
parent3b9ce49e17897cc053ed8416685e79902d7432a1 (diff)
parent764941b17e53e40dd1df1ac3137ebc825d5d6480 (diff)
downloadbarebox-2aa7ff0ae2bf2db33e358f18bec5a0239d244aa5.tar.gz
barebox-2aa7ff0ae2bf2db33e358f18bec5a0239d244aa5.tar.xz
Merge branch 'for-next/reboot-mode' into master
Diffstat (limited to 'drivers')
-rw-r--r--drivers/Kconfig1
-rw-r--r--drivers/Makefile1
-rw-r--r--drivers/power/Kconfig2
-rw-r--r--drivers/power/Makefile2
-rw-r--r--drivers/power/reset/Kconfig16
-rw-r--r--drivers/power/reset/Makefile3
-rw-r--r--drivers/power/reset/reboot-mode.c231
-rw-r--r--drivers/power/reset/syscon-reboot-mode.c129
8 files changed, 385 insertions, 0 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 09595433a0..dda2405780 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -42,5 +42,6 @@ source "drivers/memory/Kconfig"
source "drivers/soc/imx/Kconfig"
source "drivers/nvme/Kconfig"
source "drivers/ddr/Kconfig"
+source "drivers/power/Kconfig"
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 08a17ff459..5a03bdceab 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -42,3 +42,4 @@ obj-y += memory/
obj-y += soc/imx/
obj-y += nvme/
obj-y += ddr/
+obj-y += power/
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
new file mode 100644
index 0000000000..b56414c497
--- /dev/null
+++ b/drivers/power/Kconfig
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+source "drivers/power/reset/Kconfig"
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
new file mode 100644
index 0000000000..3009da59bf
--- /dev/null
+++ b/drivers/power/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-y += reset/
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
new file mode 100644
index 0000000000..f65e1f67fd
--- /dev/null
+++ b/drivers/power/reset/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+
+config REBOOT_MODE
+ bool
+
+config SYSCON_REBOOT_MODE
+ bool "Generic SYSCON regmap reboot mode driver"
+ depends on OFDEVICE
+ depends on MFD_SYSCON
+ select REBOOT_MODE
+ help
+ Say y here will enable reboot mode driver. This will
+ get reboot mode arguments and store it in SYSCON mapped
+ register, then the bootloader can read it to take different
+ action according to the mode.
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
new file mode 100644
index 0000000000..56feec78cf
--- /dev/null
+++ b/drivers/power/reset/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_REBOOT_MODE) += reboot-mode.o
+obj-$(CONFIG_SYSCON_REBOOT_MODE) += syscon-reboot-mode.o
diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c
new file mode 100644
index 0000000000..df3be03edf
--- /dev/null
+++ b/drivers/power/reset/reboot-mode.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ * Copyright (c) 2019, Ahmad Fatoum, Pengutronix
+ */
+
+#include <common.h>
+#include <driver.h>
+#include <init.h>
+#include <of.h>
+#include <linux/reboot-mode.h>
+#include <globalvar.h>
+#include <magicvar.h>
+
+#define PREFIX "mode-"
+
+static int __priority;
+static struct reboot_mode_driver *__boot_mode;
+
+static int reboot_mode_param_set(struct param_d *p, void *priv)
+{
+ struct reboot_mode_driver *reboot = priv;
+ size_t i = reboot->reboot_mode_next * reboot->nelems;
+
+ return reboot->write(reboot, &reboot->magics[i]);
+}
+
+static int reboot_mode_add_param(struct device_d *dev,
+ const char *prefix,
+ struct reboot_mode_driver *reboot)
+{
+ char name[sizeof "system.reboot_mode.when"];
+ struct param_d *param;
+
+ scnprintf(name, sizeof(name), "%sprev", prefix);
+
+ param = dev_add_param_enum_ro(dev, name,
+ &reboot->reboot_mode_prev, reboot->modes,
+ reboot->nmodes);
+ if (IS_ERR(param))
+ return PTR_ERR(param);
+
+ scnprintf(name, sizeof(name), "%snext", prefix);
+
+ param = dev_add_param_enum(dev, name,
+ reboot_mode_param_set, NULL,
+ &reboot->reboot_mode_next, reboot->modes,
+ reboot->nmodes, reboot);
+
+ return PTR_ERR_OR_ZERO(param);
+}
+
+static struct device_node *of_get_node_by_reproducible_name(struct device_node *dstroot,
+ struct device_node *srcnp)
+{
+ struct device_node *dstnp;
+ char *name;
+
+ name = of_get_reproducible_name(srcnp);
+ dstnp = of_find_node_by_reproducible_name(dstroot, name);
+ free(name);
+
+ return dstnp;
+}
+
+static int of_reboot_mode_fixup(struct device_node *root, void *ctx)
+{
+ struct reboot_mode_driver *reboot = ctx;
+ struct device_node *dstnp, *srcnp, *dstparent;
+
+ srcnp = reboot->dev->device_node;
+ dstnp = of_get_node_by_reproducible_name(root, srcnp);
+
+ /* nothing to do when called on barebox-internal tree */
+ if (srcnp == dstnp)
+ return 0;
+
+ if (dstnp) {
+ dstparent = dstnp->parent;
+ of_delete_node(dstnp);
+ } else {
+ dstparent = of_get_node_by_reproducible_name(root, srcnp->parent);
+ }
+
+ if (!dstparent)
+ return -EINVAL;
+
+ of_copy_node(dstparent, srcnp);
+
+ return 0;
+}
+
+static int reboot_mode_add_globalvar(void)
+{
+ struct reboot_mode_driver *reboot = __boot_mode;
+
+ if (!reboot)
+ return 0;
+
+ if (!reboot->no_fixup)
+ of_register_fixup(of_reboot_mode_fixup, reboot);
+
+ return reboot_mode_add_param(&global_device, "system.reboot_mode.", reboot);
+}
+late_initcall(reboot_mode_add_globalvar);
+
+
+static void reboot_mode_print(struct reboot_mode_driver *reboot,
+ const char *prefix, const u32 *arr)
+{
+ size_t i;
+ dev_dbg(reboot->dev, "%s: ", prefix);
+ for (i = 0; i < reboot->nelems; i++)
+ __pr_printk(7, "%08x ", arr[i]);
+ __pr_printk(7, "\n");
+}
+
+/**
+ * reboot_mode_register - register a reboot mode driver
+ * @reboot: reboot mode driver
+ * @reboot_mode: reboot mode read from hardware
+ *
+ * Returns: 0 on success or a negative error code on failure.
+ */
+int reboot_mode_register(struct reboot_mode_driver *reboot,
+ const u32 *reboot_mode, size_t nelems)
+{
+ struct property *prop;
+ struct device_node *np = reboot->dev->device_node;
+ size_t len = strlen(PREFIX);
+ const char *alias;
+ size_t nmodes = 0;
+ int i = 0;
+ int ret;
+
+ for_each_property_of_node(np, prop) {
+ u32 magic;
+
+ if (strncmp(prop->name, PREFIX, len))
+ continue;
+ if (of_property_read_u32(np, prop->name, &magic))
+ continue;
+
+ nmodes++;
+ }
+
+ reboot->nmodes = nmodes;
+ reboot->nelems = nelems;
+ reboot->magics = xzalloc(nmodes * nelems * sizeof(u32));
+ reboot->modes = xzalloc(nmodes * sizeof(const char *));
+
+ reboot_mode_print(reboot, "registering magic", reboot_mode);
+
+ for_each_property_of_node(np, prop) {
+ const char **mode;
+ u32 *magic;
+
+ magic = &reboot->magics[i * nelems];
+ mode = &reboot->modes[i];
+
+ if (strncmp(prop->name, PREFIX, len))
+ continue;
+
+ if (of_property_read_u32_array(np, prop->name, magic, nelems)) {
+ dev_err(reboot->dev, "reboot mode %s without magic number\n",
+ *mode);
+ continue;
+ }
+
+ *mode = prop->name + len;
+ if (*mode[0] == '\0') {
+ ret = -EINVAL;
+ dev_err(reboot->dev, "invalid mode name(%s): too short!\n",
+ prop->name);
+ goto error;
+ }
+
+ reboot_mode_print(reboot, *mode, magic);
+
+ i++;
+ }
+
+ for (i = 0; i < reboot->nmodes; i++) {
+ if (memcmp(&reboot->magics[i * nelems], reboot_mode, nelems * sizeof(u32)))
+ continue;
+
+ reboot->reboot_mode_prev = i;
+ break;
+ }
+
+ reboot_mode_add_param(reboot->dev, "", reboot);
+
+ /* clear mode for next reboot */
+ reboot->write(reboot, &(u32) { 0 });
+
+ if (!reboot->priority)
+ reboot->priority = REBOOT_MODE_DEFAULT_PRIORITY;
+
+ if (reboot->priority >= __priority) {
+ __priority = reboot->priority;
+ __boot_mode = reboot;
+ }
+
+
+ alias = of_alias_get(np);
+ if (alias)
+ dev_set_name(reboot->dev, alias);
+
+ return 0;
+
+error:
+ free(reboot->magics);
+ free(reboot->modes);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(reboot_mode_register);
+
+const char *reboot_mode_get(void)
+{
+ if (!__boot_mode)
+ return NULL;
+
+ return __boot_mode->modes[__boot_mode->reboot_mode_prev];
+}
+EXPORT_SYMBOL_GPL(reboot_mode_get);
+
+BAREBOX_MAGICVAR(global.system.reboot_mode.prev,
+ "reboot-mode: Mode set previously, before barebox start");
+BAREBOX_MAGICVAR(global.system.reboot_mode.next,
+ "reboot-mode: Mode to set next, to be evaluated after reset");
diff --git a/drivers/power/reset/syscon-reboot-mode.c b/drivers/power/reset/syscon-reboot-mode.c
new file mode 100644
index 0000000000..0cbc9d0803
--- /dev/null
+++ b/drivers/power/reset/syscon-reboot-mode.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ */
+
+#include <common.h>
+#include <init.h>
+#include <driver.h>
+#include <of.h>
+#include <regmap.h>
+#include <mfd/syscon.h>
+#include <linux/reboot-mode.h>
+#include <linux/overflow.h>
+
+struct mode_reg {
+ u32 offset;
+ u32 mask;
+};
+
+struct syscon_reboot_mode {
+ struct regmap *map;
+ struct reboot_mode_driver reboot;
+ struct mode_reg reg[];
+};
+
+static int syscon_reboot_mode_write(struct reboot_mode_driver *reboot,
+ const u32 *magic)
+{
+ struct syscon_reboot_mode *syscon_rbm;
+ size_t i;
+ int ret = 0;
+
+ syscon_rbm = container_of(reboot, struct syscon_reboot_mode, reboot);
+
+ for (i = 0; i < reboot->nelems; i++) {
+ struct mode_reg *reg = &syscon_rbm->reg[i];
+
+ ret = regmap_update_bits(syscon_rbm->map, reg->offset,
+ reg->mask, *magic++);
+ if (ret < 0) {
+ dev_err(reboot->dev, "update reboot mode bits failed\n");
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int syscon_reboot_mode_probe(struct device_d *dev)
+{
+ int ret, i, nelems;
+ struct syscon_reboot_mode *syscon_rbm;
+ struct reboot_mode_driver *reboot_template;
+ struct device_node *np = dev->device_node;
+ u32 *magic;
+
+ nelems = of_property_count_elems_of_size(np, "offset", sizeof(__be32));
+ if (nelems <= 0)
+ return -EINVAL;
+
+ syscon_rbm = xzalloc(struct_size(syscon_rbm, reg, nelems));
+
+ ret = dev_get_drvdata(dev, (const void **)&reboot_template);
+ if (ret)
+ return ret;
+
+ syscon_rbm->reboot = *reboot_template;
+ syscon_rbm->reboot.dev = dev;
+
+ syscon_rbm->map = syscon_node_to_regmap(dev->parent->device_node);
+ if (IS_ERR(syscon_rbm->map))
+ return PTR_ERR(syscon_rbm->map);
+
+ magic = xzalloc(nelems * sizeof(*magic));
+
+ for (i = 0; i < nelems; i++) {
+ struct mode_reg *reg = &syscon_rbm->reg[i];
+
+ ret = of_property_read_u32_index(np, "offset", i, &reg->offset);
+ if (ret)
+ goto free_magic;
+
+ reg->mask = 0xffffffff;
+ of_property_read_u32_index(np, "mask", i, &reg->mask);
+
+ ret = regmap_read(syscon_rbm->map, reg->offset, &magic[i]);
+ if (ret) {
+ dev_err(dev, "error reading reboot mode: %s\n",
+ strerror(-ret));
+ goto free_magic;
+ }
+
+ magic[i] &= reg->mask;
+ }
+
+ ret = reboot_mode_register(&syscon_rbm->reboot, magic, nelems);
+ if (ret)
+ dev_err(dev, "can't register reboot mode\n");
+
+free_magic:
+ free(magic);
+ return ret;
+
+}
+
+static struct reboot_mode_driver reboot_fixup = {
+ .write = syscon_reboot_mode_write,
+ .priority = 100,
+ .no_fixup = false,
+};
+
+static struct reboot_mode_driver reboot_nofixup = {
+ .write = syscon_reboot_mode_write,
+ .priority = 50,
+ .no_fixup = true,
+};
+
+static const struct of_device_id syscon_reboot_mode_of_match[] = {
+ { .compatible = "syscon-reboot-mode", .data = &reboot_fixup },
+ { .compatible = "barebox,syscon-reboot-mode", .data = &reboot_nofixup },
+ { /* sentinel */ }
+};
+
+static struct driver_d syscon_reboot_mode_driver = {
+ .probe = syscon_reboot_mode_probe,
+ .name = "syscon-reboot-mode",
+ .of_compatible = syscon_reboot_mode_of_match,
+};
+coredevice_platform_driver(syscon_reboot_mode_driver);