diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2020-10-14 12:46:52 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2020-10-14 12:46:52 +0200 |
commit | 2aa7ff0ae2bf2db33e358f18bec5a0239d244aa5 (patch) | |
tree | 9417d44f527a5ed3876ff651946cc0a15f658e87 /drivers | |
parent | 3b9ce49e17897cc053ed8416685e79902d7432a1 (diff) | |
parent | 764941b17e53e40dd1df1ac3137ebc825d5d6480 (diff) | |
download | barebox-2aa7ff0ae2bf2db33e358f18bec5a0239d244aa5.tar.gz barebox-2aa7ff0ae2bf2db33e358f18bec5a0239d244aa5.tar.xz |
Merge branch 'for-next/reboot-mode' into master
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/Kconfig | 1 | ||||
-rw-r--r-- | drivers/Makefile | 1 | ||||
-rw-r--r-- | drivers/power/Kconfig | 2 | ||||
-rw-r--r-- | drivers/power/Makefile | 2 | ||||
-rw-r--r-- | drivers/power/reset/Kconfig | 16 | ||||
-rw-r--r-- | drivers/power/reset/Makefile | 3 | ||||
-rw-r--r-- | drivers/power/reset/reboot-mode.c | 231 | ||||
-rw-r--r-- | drivers/power/reset/syscon-reboot-mode.c | 129 |
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, ®->offset); + if (ret) + goto free_magic; + + reg->mask = 0xffffffff; + of_property_read_u32_index(np, "mask", i, ®->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); |