diff options
-rw-r--r-- | Documentation/user/defaultenv-2.rst | 18 | ||||
-rw-r--r-- | Documentation/user/reboot-mode.rst | 95 | ||||
-rw-r--r-- | arch/arm/dts/imx6qdl.dtsi | 21 | ||||
-rw-r--r-- | arch/arm/dts/stm32mp151.dtsi | 15 | ||||
-rw-r--r-- | arch/arm/mach-stm32mp/include/mach/bootsource.h | 12 | ||||
-rw-r--r-- | arch/arm/mach-stm32mp/init.c | 16 | ||||
-rw-r--r-- | common/Kconfig | 5 | ||||
-rw-r--r-- | common/startup.c | 16 | ||||
-rw-r--r-- | common/usbgadget.c | 6 | ||||
-rw-r--r-- | defaultenv/Makefile | 1 | ||||
-rw-r--r-- | defaultenv/defaultenv-2-reboot-mode/bmode/bootloader | 3 | ||||
-rwxr-xr-x | defaultenv/defaultenv-2-reboot-mode/bmode/loader | 2 | ||||
-rw-r--r-- | defaultenv/defaultenv-2-reboot-mode/bmode/recovery | 2 | ||||
-rw-r--r-- | defaultenv/defaultenv.c | 2 | ||||
-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 | ||||
-rw-r--r-- | include/linux/reboot-mode.h | 38 | ||||
-rw-r--r-- | include/of.h | 2 |
24 files changed, 606 insertions, 33 deletions
diff --git a/Documentation/user/defaultenv-2.rst b/Documentation/user/defaultenv-2.rst index a79ae83d56..da766e4edc 100644 --- a/Documentation/user/defaultenv-2.rst +++ b/Documentation/user/defaultenv-2.rst @@ -19,10 +19,11 @@ All new boards should use defaultenv-2 exclusively. The default environment is composed from different directories during compilation:: - defaultenv/defaultenv-2-base -> base files - defaultenv/defaultenv-2-dfu -> overlay for DFU - defaultenv/defaultenv-2-menu -> overlay for menus - arch/$ARCH/boards/<board>/env -> board specific overlay + defaultenv/defaultenv-2-base -> base files + defaultenv/defaultenv-2-dfu -> overlay for DFU + defaultenv/defaultenv-2-reboot-mode -> overlay for reboot modes + defaultenv/defaultenv-2-menu -> overlay for menus + arch/$ARCH/boards/<board>/env -> board specific overlay The content of the above directories is applied one after another. If the same file exists in a later overlay, it will overwrite the preceding one. @@ -37,6 +38,7 @@ and their respective included directories in ``defaultenv/Makefile``: bbenv-$(CONFIG_DEFAULT_ENVIRONMENT_GENERIC_NEW) += defaultenv-2-base bbenv-$(CONFIG_DEFAULT_ENVIRONMENT_GENERIC_NEW_MENU) += defaultenv-2-menu bbenv-$(CONFIG_DEFAULT_ENVIRONMENT_GENERIC_NEW_DFU) += defaultenv-2-dfu + bbenv-$(CONFIG_DEFAULT_ENVIRONMENT_GENERIC_NEW_REBOOT_MODE) += defaultenv-2-reboot-mode bbenv-$(CONFIG_DEFAULT_ENVIRONMENT_GENERIC) += defaultenv-1 /env/bin/init @@ -138,3 +140,11 @@ there will be a file ``eth0`` with a content like this: # put code to discover eth0 (i.e. 'usb') to /env/network/eth0-discover exit 0 + +/env/bmode/ +----------- + +This contains the files to be sourced when barebox detects that the OS +had requested a specific reboot mode (via e.g. ``reboot bootloader`` +under Linux). After the ``/env/init`` scripts were executed, barebox will +``source /env/bmode/${global.system.reboot_mode.prev}`` if available. diff --git a/Documentation/user/reboot-mode.rst b/Documentation/user/reboot-mode.rst new file mode 100644 index 0000000000..9321d928f4 --- /dev/null +++ b/Documentation/user/reboot-mode.rst @@ -0,0 +1,95 @@ +.. _reboot_mode: + +Reboot Mode +----------- + +To simplify debugging, many BootROMs sample registers that survive +a warm reset to customize the boot. These registers can e.g. indicate +that boot should happen from a different boot medium. + +Likewise, many bootloaders reuse such registers, or if unavailable, +non-volatile memory to determine whether the OS requested a special +reboot mode, e.g. rebooting into an USB recovery mode. This is +common on Android systems. + +barebox implements the upstream device tree bindings for +`reboot-modes <https://www.kernel.org/doc/Documentation/devicetree/bindings/power/reset/reboot-mode.txt>`_ +to act upon reboot mode protocols specified in the device tree. + +The device tree nodes list a number of reboot modes along with a +magic value for each. On reboot, an OS implementing the binding +would take the reboot command's argument and match it against the +modes in the device tree. If a match is found the associated magic +is written to the location referenced in the device tree node. + +User API +~~~~~~~~ + +Devices registered with the reboot mode API gain two parameters: + + - ``$dev_of_reboot_mode.prev`` (read-only): The reboot mode that was + set previous to barebox startup + - ``$dev_of_reboot_mode.next``: The next reboot mode, for when the + system is reset + +The reboot mode driver core use the alias name if available to name +the device. By convention, this should end with ``.reboot_mode``, e.g.:: + + / { + aliases { + gpr.reboot_name = &reboot_name_gpr; + }; + }; + +Reboot mode providers have priorities. The provider with the highest +priority has its parameters aliased as ``$global.system.reboot_mode.prev`` +and ``$global.system.reboot_mode.next``. + +Reset +~~~~~ + +Reboot modes can be stored on a syscon wrapping general purpose registers +that survives warm resets. If the system instead did reset via an external +power management IC, the registers may lose their value. + +If such reboot mode storage is used, users must take care to use the correct +reset provider. In barebox, multiple reset providers may co-exist. They +``reset`` command allows listing and choosing a specific reboot mode. + +Disambiguation +~~~~~~~~~~~~~~ + +Some uses of reboot modes partially overlap with other barebox +functionality. They all ultimately serve different purposes, however. + +Comparison to reset reason +--------------------------- + +The reset reason ``$global.system.reset`` is populated by different drivers +to reflect the hardware cause of a reset, e.g. a watchdog. A reboot mode +describes the OS intention behind a reset, e.g. to fall into a recovery +mode. Reboot modes besides the default ``normal`` mode usually accompany +a reset reason of ``RST`` (because the OS intentionally triggered a reset +to activate the next reboot mode). + +Comparison to bootsource +------------------------ + +``$bootsource`` reflects the current boot's medium as indicated by the +SoC. In cases where the reboot mode is used to communicate with the BootROM, +``$bootsource`` and ``$bootsource_instance`` may describe the same device +as the reboot mode. + +For cases, where the communication instead happens between barebox and an OS, +they can be completely different, e.g. ``$bootsource`` may say barebox was +booted from ``spi-nor``, while the reboot mode describes that barebox should +boot the Kernel off an USB flash drive. + +Comparison to barebox state +--------------------------- + +barebox state also allows sharing information between barebox and the OS, +but it does so while providing atomic updates, redundant storage and +optionally wear leveling. In contrast to state, reboot mode is just that: +a mode for a single reboot. barebox clears the reboot mode after reading it, +so this can be reliably used across one reset only. diff --git a/arch/arm/dts/imx6qdl.dtsi b/arch/arm/dts/imx6qdl.dtsi index 828be9ce0d..c3e02d2117 100644 --- a/arch/arm/dts/imx6qdl.dtsi +++ b/arch/arm/dts/imx6qdl.dtsi @@ -6,5 +6,26 @@ pwm2 = &pwm3; pwm3 = &pwm4; ipu0 = &ipu1; + gpr.reboot_mode = &reboot_mode_gpr; + }; +}; + +&src { + compatible = "fsl,imx6q-src", "fsl,imx51-src", "syscon", "simple-mfd"; + + reboot_mode_gpr: reboot-mode { + compatible = "barebox,syscon-reboot-mode"; + offset = <0x40>, <0x44>; /* SRC_GPR{9,10} */ + mask = <0xffffffff>, <0x10000000>; + mode-normal = <0>, <0>; + mode-serial = <0x00000010>, <0x10000000>; + mode-spi0-0 = <0x08000030>, <0x10000000>; + mode-spi0-1 = <0x18000030>, <0x10000000>; + mode-spi0-2 = <0x28000030>, <0x10000000>; + mode-spi0-3 = <0x38000030>, <0x10000000>; + mode-mmc0 = <0x00002040>, <0x10000000>; + mode-mmc1 = <0x00002840>, <0x10000000>; + mode-mmc2 = <0x00003040>, <0x10000000>; + mode-mmc3 = <0x00003840>, <0x10000000>; }; }; diff --git a/arch/arm/dts/stm32mp151.dtsi b/arch/arm/dts/stm32mp151.dtsi index cc25400475..ca11492de5 100644 --- a/arch/arm/dts/stm32mp151.dtsi +++ b/arch/arm/dts/stm32mp151.dtsi @@ -28,6 +28,7 @@ pwm15 = &{/soc/timer@44006000/pwm}; pwm16 = &{/soc/timer@44007000/pwm}; pwm17 = &{/soc/timer@44008000/pwm}; + tamp.reboot_mode = &reboot_mode_tamp; }; }; @@ -42,6 +43,20 @@ compatible = "st,stm32mp1-ddr"; reg = <0x5a003000 0x1000>; }; + + tamp@5c00a000 { + compatible = "simple-bus", "syscon", "simple-mfd"; + reg = <0x5c00a000 0x400>; + + reboot_mode_tamp: reboot-mode { + compatible = "syscon-reboot-mode"; + offset = <0x150>; /* reg20 */ + mask = <0xff>; + mode-normal = <0>; + mode-loader = <0xBB>; + mode-recovery = <0xBC>; + }; + }; }; &bsec { diff --git a/arch/arm/mach-stm32mp/include/mach/bootsource.h b/arch/arm/mach-stm32mp/include/mach/bootsource.h index 1b6f562ac3..5750dc1448 100644 --- a/arch/arm/mach-stm32mp/include/mach/bootsource.h +++ b/arch/arm/mach-stm32mp/include/mach/bootsource.h @@ -18,16 +18,4 @@ enum stm32mp_boot_device { STM32MP_BOOT_SERIAL_USB_OTG = 0x62, }; -enum stm32mp_forced_boot_mode { - STM32MP_BOOT_NORMAL = 0x00, - STM32MP_BOOT_FASTBOOT = 0x01, - STM32MP_BOOT_RECOVERY = 0x02, - STM32MP_BOOT_STM32PROG = 0x03, - STM32MP_BOOT_UMS_MMC0 = 0x10, - STM32MP_BOOT_UMS_MMC1 = 0x11, - STM32MP_BOOT_UMS_MMC2 = 0x12, -}; - -enum stm32mp_forced_boot_mode st32mp_get_forced_boot_mode(void); - #endif diff --git a/arch/arm/mach-stm32mp/init.c b/arch/arm/mach-stm32mp/init.c index e77e99f8fa..01961ae456 100644 --- a/arch/arm/mach-stm32mp/init.c +++ b/arch/arm/mach-stm32mp/init.c @@ -60,12 +60,6 @@ #define FIXUP_CPU_NUM(mask) ((mask) >> 16) #define FIXUP_CPU_HZ(mask) (((mask) & GENMASK(15, 0)) * 1000UL * 1000UL) -static enum stm32mp_forced_boot_mode __stm32mp_forced_boot_mode; -enum stm32mp_forced_boot_mode st32mp_get_forced_boot_mode(void) -{ - return __stm32mp_forced_boot_mode; -} - static void setup_boot_mode(void) { u32 boot_ctx = readl(TAMP_BOOT_CONTEXT); @@ -101,17 +95,11 @@ static void setup_boot_mode(void) break; } - __stm32mp_forced_boot_mode = boot_ctx & TAMP_BOOT_FORCED_MASK; - - pr_debug("[boot_ctx=0x%x] => mode=0x%x, instance=%d forced=0x%x\n", - boot_ctx, boot_mode, instance, __stm32mp_forced_boot_mode); + pr_debug("[boot_ctx=0x%x] => mode=0x%x, instance=%d\n", + boot_ctx, boot_mode, instance); bootsource_set(src); bootsource_set_instance(instance); - - /* clear TAMP for next reboot */ - clrsetbits_le32(TAMP_BOOT_CONTEXT, TAMP_BOOT_FORCED_MASK, - STM32MP_BOOT_NORMAL); } static int __stm32mp_cputype; diff --git a/common/Kconfig b/common/Kconfig index 8c9fe8e788..9b73aa8454 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -906,6 +906,11 @@ config DEFAULT_ENVIRONMENT_GENERIC_NEW_DFU depends on USB_GADGET_DFU default y +config DEFAULT_ENVIRONMENT_GENERIC_NEW_REBOOT_MODE + bool "Generic reboot-mode handlers in the environment" + depends on DEFAULT_ENVIRONMENT_GENERIC_NEW + depends on REBOOT_MODE + config DEFAULT_ENVIRONMENT_PATH string depends on DEFAULT_ENVIRONMENT diff --git a/common/startup.c b/common/startup.c index d9d79aef89..adc487363f 100644 --- a/common/startup.c +++ b/common/startup.c @@ -38,6 +38,7 @@ #include <linux/stat.h> #include <envfs.h> #include <magicvar.h> +#include <linux/reboot-mode.h> #include <asm/sections.h> #include <uncompress.h> #include <globalvar.h> @@ -310,6 +311,7 @@ static int run_init(void) DIR *dir; struct dirent *d; const char *initdir = "/env/init"; + const char *bmode; bool env_bin_init_exists; enum autoboot_state autoboot; struct stat s; @@ -350,6 +352,20 @@ static int run_init(void) closedir(dir); } + /* source matching script in /env/bmode/ */ + bmode = reboot_mode_get(); + if (bmode) { + char *scr, *path; + + scr = xasprintf("source /env/bmode/%s", bmode); + path = &scr[strlen("source ")]; + if (stat(path, &s) == 0) { + pr_info("Invoking '%s'...\n", path); + run_command(scr); + } + free(scr); + } + autoboot = do_autoboot_countdown(); console_ctrlc_allow(); diff --git a/common/usbgadget.c b/common/usbgadget.c index 1790310f79..fb508db947 100644 --- a/common/usbgadget.c +++ b/common/usbgadget.c @@ -100,7 +100,7 @@ int usbgadget_register(bool dfu, const char *dfu_opts, return ret; } -static int usbgadget_autostart(void) +static int usbgadget_autostart_set(struct param_d *param, void *ctx) { bool fastboot_bbu = get_fastboot_bbu(); @@ -109,12 +109,12 @@ static int usbgadget_autostart(void) return usbgadget_register(true, NULL, true, NULL, acm, fastboot_bbu); } -postenvironment_initcall(usbgadget_autostart); static int usbgadget_globalvars_init(void) { if (IS_ENABLED(CONFIG_USB_GADGET_AUTOSTART)) { - globalvar_add_simple_bool("usbgadget.autostart", &autostart); + globalvar_add_bool("usbgadget.autostart", usbgadget_autostart_set, + &autostart, NULL); globalvar_add_simple_bool("usbgadget.acm", &acm); } globalvar_add_simple_string("usbgadget.dfu_function", &dfu_function); diff --git a/defaultenv/Makefile b/defaultenv/Makefile index e030355a40..91293567c0 100644 --- a/defaultenv/Makefile +++ b/defaultenv/Makefile @@ -1,6 +1,7 @@ bbenv-$(CONFIG_DEFAULT_ENVIRONMENT_GENERIC_NEW) += defaultenv-2-base bbenv-$(CONFIG_DEFAULT_ENVIRONMENT_GENERIC_NEW_MENU) += defaultenv-2-menu bbenv-$(CONFIG_DEFAULT_ENVIRONMENT_GENERIC_NEW_DFU) += defaultenv-2-dfu +bbenv-$(CONFIG_DEFAULT_ENVIRONMENT_GENERIC_NEW_REBOOT_MODE) += defaultenv-2-reboot-mode bbenv-$(CONFIG_DEFAULT_ENVIRONMENT_GENERIC) += defaultenv-1 obj-$(CONFIG_DEFAULT_ENVIRONMENT) += defaultenv.o extra-y += barebox_default_env barebox_default_env.h barebox_default_env$(DEFAULT_COMPRESSION_SUFFIX) barebox_zero_env diff --git a/defaultenv/defaultenv-2-reboot-mode/bmode/bootloader b/defaultenv/defaultenv-2-reboot-mode/bmode/bootloader new file mode 100644 index 0000000000..50a7a0f633 --- /dev/null +++ b/defaultenv/defaultenv-2-reboot-mode/bmode/bootloader @@ -0,0 +1,3 @@ +# Mode to re-flash partitions +global.autoboot_timeout=30 +global.usbgadget.autostart=1 diff --git a/defaultenv/defaultenv-2-reboot-mode/bmode/loader b/defaultenv/defaultenv-2-reboot-mode/bmode/loader new file mode 100755 index 0000000000..45647dec29 --- /dev/null +++ b/defaultenv/defaultenv-2-reboot-mode/bmode/loader @@ -0,0 +1,2 @@ +# Development mode +global.autoboot=abort diff --git a/defaultenv/defaultenv-2-reboot-mode/bmode/recovery b/defaultenv/defaultenv-2-reboot-mode/bmode/recovery new file mode 100644 index 0000000000..0496ba3b0d --- /dev/null +++ b/defaultenv/defaultenv-2-reboot-mode/bmode/recovery @@ -0,0 +1,2 @@ +# Interactive mode for recovery +global.autoboot=menu diff --git a/defaultenv/defaultenv.c b/defaultenv/defaultenv.c index b773030fe8..d69446c893 100644 --- a/defaultenv/defaultenv.c +++ b/defaultenv/defaultenv.c @@ -45,6 +45,8 @@ static void defaultenv_add_base(void) defaultenv_append_directory(defaultenv_2_menu); if (IS_ENABLED(CONFIG_DEFAULT_ENVIRONMENT_GENERIC_NEW_DFU)) defaultenv_append_directory(defaultenv_2_dfu); + if (IS_ENABLED(CONFIG_DEFAULT_ENVIRONMENT_GENERIC_NEW_REBOOT_MODE)) + defaultenv_append_directory(defaultenv_2_reboot_mode); if (IS_ENABLED(CONFIG_DEFAULT_ENVIRONMENT_GENERIC)) defaultenv_append_directory(defaultenv_1); } 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); diff --git a/include/linux/reboot-mode.h b/include/linux/reboot-mode.h new file mode 100644 index 0000000000..9d9ce19c0e --- /dev/null +++ b/include/linux/reboot-mode.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __REBOOT_MODE_H__ +#define __REBOOT_MODE_H__ + +#include <linux/types.h> + +struct device_d; + +#ifdef CONFIG_REBOOT_MODE +struct reboot_mode_driver { + struct device_d *dev; + int (*write)(struct reboot_mode_driver *reboot, const u32 *magic); + int priority; + bool no_fixup; + + /* filled by reboot_mode_register */ + int reboot_mode_prev, reboot_mode_next; + unsigned nmodes, nelems; + const char **modes; + u32 *magics; +}; + +int reboot_mode_register(struct reboot_mode_driver *reboot, + const u32 *magic, size_t num); +const char *reboot_mode_get(void); + +#define REBOOT_MODE_DEFAULT_PRIORITY 100 + +#else + +static inline const char *reboot_mode_get(void) +{ + return NULL; +} + +#endif + +#endif diff --git a/include/of.h b/include/of.h index e60cb5307d..f27a0b8ccd 100644 --- a/include/of.h +++ b/include/of.h @@ -734,6 +734,8 @@ static inline int of_autoenable_i2c_by_component(char *path) #endif +#define for_each_property_of_node(dn, pp) \ + list_for_each_entry(pp, &dn->properties, list) #define for_each_node_by_name(dn, name) \ for (dn = of_find_node_by_name(NULL, name); dn; \ dn = of_find_node_by_name(dn, name)) |