diff options
author | Ahmad Fatoum <a.fatoum@pengutronix.de> | 2020-09-28 16:45:06 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2020-09-29 10:25:02 +0200 |
commit | fc8511d20ab32a31e9502c54a28a3f7f55223f39 (patch) | |
tree | fbc05fee8e84068489490060be1e3889c0b2b61c /drivers/power | |
parent | 5a5c5178e7dc6a58a059fe4a1f4167a31809ccaf (diff) | |
download | barebox-fc8511d20ab32a31e9502c54a28a3f7f55223f39.tar.gz barebox-fc8511d20ab32a31e9502c54a28a3f7f55223f39.tar.xz |
drivers: add reboot-mode infrastructure
Reboot modes provide a well-defined way to exchange information between
different stage of the boot process. When configured, users can type
`reboot bootloader` in the OS and barebox can read it out a device
parameter. Likewise barebox can write a reboot mode for the BootROM to
evaluate and then reset to fall into a serial recovery mode for example.
Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers/power')
-rw-r--r-- | drivers/power/Kconfig | 2 | ||||
-rw-r--r-- | drivers/power/Makefile | 2 | ||||
-rw-r--r-- | drivers/power/reset/Kconfig | 5 | ||||
-rw-r--r-- | drivers/power/reset/Makefile | 2 | ||||
-rw-r--r-- | drivers/power/reset/reboot-mode.c | 185 |
5 files changed, 196 insertions, 0 deletions
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..5554fc122d --- /dev/null +++ b/drivers/power/reset/Kconfig @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +# + +config REBOOT_MODE + bool diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile new file mode 100644 index 0000000000..68231f044a --- /dev/null +++ b/drivers/power/reset/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_REBOOT_MODE) += 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..5992a2acd9 --- /dev/null +++ b/drivers/power/reset/reboot-mode.c @@ -0,0 +1,185 @@ +// 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; + u32 magic; + + magic = reboot->magics[reboot->reboot_mode_next]; + + return reboot->write(reboot, magic); +} + +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 int reboot_mode_add_globalvar(void) +{ + struct reboot_mode_driver *reboot = __boot_mode; + + if (!reboot) + return 0; + + 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, u32 magic) +{ + dev_dbg(reboot->dev, "%s: %08x\n", prefix, magic); +} + +/** + * 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, u32 reboot_mode) +{ + 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->magics = xzalloc(nmodes * 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]; + mode = &reboot->modes[i]; + + if (strncmp(prop->name, PREFIX, len)) + continue; + + if (of_property_read_u32(np, prop->name, magic)) { + 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 (reboot->magics[i] == reboot_mode) { + reboot->reboot_mode_prev = i; + break; + } + } + + reboot_mode_add_param(reboot->dev, "", reboot); + + /* clear mode for next reboot */ + reboot->write(reboot, 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_NAMED(global_system_reboot_mode_prev, + global.system.reboot_mode.prev, + "reboot-mode: Mode set previously, before barebox start"); +BAREBOX_MAGICVAR_NAMED(global_system_reboot_mode_next, + global.system.reboot_mode.next, + "reboot-mode: Mode to set next, to be evaluated after reset"); |