summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/user/defaultenv-2.rst18
-rw-r--r--Documentation/user/reboot-mode.rst95
-rw-r--r--arch/arm/dts/imx6qdl.dtsi21
-rw-r--r--arch/arm/dts/stm32mp151.dtsi15
-rw-r--r--arch/arm/mach-stm32mp/include/mach/bootsource.h12
-rw-r--r--arch/arm/mach-stm32mp/init.c16
-rw-r--r--common/Kconfig5
-rw-r--r--common/startup.c16
-rw-r--r--common/usbgadget.c6
-rw-r--r--defaultenv/Makefile1
-rw-r--r--defaultenv/defaultenv-2-reboot-mode/bmode/bootloader3
-rwxr-xr-xdefaultenv/defaultenv-2-reboot-mode/bmode/loader2
-rw-r--r--defaultenv/defaultenv-2-reboot-mode/bmode/recovery2
-rw-r--r--defaultenv/defaultenv.c2
-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
-rw-r--r--include/linux/reboot-mode.h38
-rw-r--r--include/of.h2
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, &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);
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))