aboutsummaryrefslogtreecommitdiff
path: root/drivers/bootcount
diff options
context:
space:
mode:
authorNandor Han2021-06-10 15:40:38 +0300
committerHeiko Schocher2021-08-22 11:04:52 +0200
commitc50b21b70523939c561d0455a2c423f63a9162ca (patch)
tree2732f82ac8bb61f20e210eb6c2b4a2f5d432c8ff /drivers/bootcount
parent7282b4352eba74e134c7ebba945338e418ec7faf (diff)
bootcount: add a new driver with syscon as backend
The driver will use a syscon regmap as backend and supports both 16 and 32 size value. The value will be stored in the CPU's endianness. Signed-off-by: Nandor Han <nandor.han@vaisala.com> Reviewed-by: Simon Glass <sjg@chromium.org>
Diffstat (limited to 'drivers/bootcount')
-rw-r--r--drivers/bootcount/Kconfig12
-rw-r--r--drivers/bootcount/Makefile1
-rw-r--r--drivers/bootcount/bootcount_syscon.c159
3 files changed, 172 insertions, 0 deletions
diff --git a/drivers/bootcount/Kconfig b/drivers/bootcount/Kconfig
index 0de2b7bd78c..607027c968d 100644
--- a/drivers/bootcount/Kconfig
+++ b/drivers/bootcount/Kconfig
@@ -144,6 +144,18 @@ config BOOTCOUNT_MEM
is not cleared on softreset.
compatible = "u-boot,bootcount";
+config DM_BOOTCOUNT_SYSCON
+ bool "Support SYSCON devices as a backing store for bootcount"
+ select REGMAP
+ select SYSCON
+ help
+ Enable reading/writing the bootcount value in a DM SYSCON device.
+ The driver supports a fixed 32 bits size register using the native
+ endianness. However, this can be controlled from the SYSCON DT node
+ configuration.
+
+ Accessing the backend is done using the regmap interface.
+
endmenu
endif
diff --git a/drivers/bootcount/Makefile b/drivers/bootcount/Makefile
index 12658ffdcec..3a784bb0a64 100644
--- a/drivers/bootcount/Makefile
+++ b/drivers/bootcount/Makefile
@@ -14,3 +14,4 @@ obj-$(CONFIG_DM_BOOTCOUNT) += bootcount-uclass.o
obj-$(CONFIG_DM_BOOTCOUNT_RTC) += rtc.o
obj-$(CONFIG_DM_BOOTCOUNT_I2C_EEPROM) += i2c-eeprom.o
obj-$(CONFIG_DM_BOOTCOUNT_SPI_FLASH) += spi-flash.o
+obj-$(CONFIG_DM_BOOTCOUNT_SYSCON) += bootcount_syscon.o
diff --git a/drivers/bootcount/bootcount_syscon.c b/drivers/bootcount/bootcount_syscon.c
new file mode 100644
index 00000000000..413fd5bb9df
--- /dev/null
+++ b/drivers/bootcount/bootcount_syscon.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Vaisala Oyj. All rights reserved.
+ */
+
+#include <common.h>
+#include <bootcount.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <linux/ioport.h>
+#include <regmap.h>
+#include <syscon.h>
+
+#define BYTES_TO_BITS(bytes) ((bytes) << 3)
+#define GEN_REG_MASK(val_size, val_addr) \
+ (GENMASK(BYTES_TO_BITS(val_size) - 1, 0) \
+ << (!!((val_addr) == 0x02) * BYTES_TO_BITS(2)))
+#define GET_DEFAULT_VALUE(val_size) \
+ (CONFIG_SYS_BOOTCOUNT_MAGIC >> \
+ (BYTES_TO_BITS((sizeof(u32) - (val_size)))))
+
+/**
+ * struct bootcount_syscon_priv - driver's private data
+ *
+ * @regmap: syscon regmap
+ * @reg_addr: register address used to store the bootcount value
+ * @size: size of the bootcount value (2 or 4 bytes)
+ * @magic: magic used to validate/save the bootcount value
+ * @magic_mask: magic value bitmask
+ * @reg_mask: mask used to identify the location of the bootcount value
+ * in the register when 2 bytes length is used
+ * @shift: value used to extract the botcount value from the register
+ */
+struct bootcount_syscon_priv {
+ struct regmap *regmap;
+ fdt_addr_t reg_addr;
+ fdt_size_t size;
+ u32 magic;
+ u32 magic_mask;
+ u32 reg_mask;
+ int shift;
+};
+
+static int bootcount_syscon_set(struct udevice *dev, const u32 val)
+{
+ struct bootcount_syscon_priv *priv = dev_get_priv(dev);
+ u32 regval;
+
+ if ((val & priv->magic_mask) != 0)
+ return -EINVAL;
+
+ regval = (priv->magic & priv->magic_mask) | (val & ~priv->magic_mask);
+
+ if (priv->size == 2) {
+ regval &= 0xffff;
+ regval |= (regval & 0xffff) << BYTES_TO_BITS(priv->size);
+ }
+
+ debug("%s: Prepare to write reg value: 0x%08x with register mask: 0x%08x\n",
+ __func__, regval, priv->reg_mask);
+
+ return regmap_update_bits(priv->regmap, priv->reg_addr, priv->reg_mask,
+ regval);
+}
+
+static int bootcount_syscon_get(struct udevice *dev, u32 *val)
+{
+ struct bootcount_syscon_priv *priv = dev_get_priv(dev);
+ u32 regval;
+ int ret;
+
+ ret = regmap_read(priv->regmap, priv->reg_addr, &regval);
+ if (ret)
+ return ret;
+
+ regval &= priv->reg_mask;
+ regval >>= priv->shift;
+
+ if ((regval & priv->magic_mask) == (priv->magic & priv->magic_mask)) {
+ *val = regval & ~priv->magic_mask;
+ } else {
+ dev_err(dev, "%s: Invalid bootcount magic\n", __func__);
+ return -EINVAL;
+ }
+
+ debug("%s: Read bootcount value: 0x%08x from regval: 0x%08x\n",
+ __func__, *val, regval);
+ return 0;
+}
+
+static int bootcount_syscon_of_to_plat(struct udevice *dev)
+{
+ struct bootcount_syscon_priv *priv = dev_get_priv(dev);
+ fdt_addr_t bootcount_offset;
+ fdt_size_t reg_size;
+
+ priv->regmap = syscon_regmap_lookup_by_phandle(dev, "syscon");
+ if (IS_ERR(priv->regmap)) {
+ dev_err(dev, "%s: Unable to find regmap (%ld)\n", __func__,
+ PTR_ERR(priv->regmap));
+ return PTR_ERR(priv->regmap);
+ }
+
+ priv->reg_addr = dev_read_addr_size_name(dev, "syscon_reg", &reg_size);
+ if (priv->reg_addr == FDT_ADDR_T_NONE) {
+ dev_err(dev, "%s: syscon_reg address not found\n", __func__);
+ return -EINVAL;
+ }
+ if (reg_size != 4) {
+ dev_err(dev, "%s: Unsupported register size: %d\n", __func__,
+ reg_size);
+ return -EINVAL;
+ }
+
+ bootcount_offset = dev_read_addr_size_name(dev, "offset", &priv->size);
+ if (bootcount_offset == FDT_ADDR_T_NONE) {
+ dev_err(dev, "%s: offset configuration not found\n", __func__);
+ return -EINVAL;
+ }
+ if (bootcount_offset + priv->size > reg_size) {
+ dev_err(dev,
+ "%s: Bootcount value doesn't fit in the reserved space\n",
+ __func__);
+ return -EINVAL;
+ }
+ if (priv->size != 2 && priv->size != 4) {
+ dev_err(dev,
+ "%s: Driver supports only 2 and 4 bytes bootcount size\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ priv->magic = GET_DEFAULT_VALUE(priv->size);
+ priv->magic_mask = GENMASK(BYTES_TO_BITS(priv->size) - 1,
+ BYTES_TO_BITS(priv->size >> 1));
+ priv->shift = !!(bootcount_offset == 0x02) * BYTES_TO_BITS(priv->size);
+ priv->reg_mask = GEN_REG_MASK(priv->size, bootcount_offset);
+
+ return 0;
+}
+
+static const struct bootcount_ops bootcount_syscon_ops = {
+ .get = bootcount_syscon_get,
+ .set = bootcount_syscon_set,
+};
+
+static const struct udevice_id bootcount_syscon_ids[] = {
+ { .compatible = "u-boot,bootcount-syscon" },
+ {}
+};
+
+U_BOOT_DRIVER(bootcount_syscon) = {
+ .name = "bootcount-syscon",
+ .id = UCLASS_BOOTCOUNT,
+ .of_to_plat = bootcount_syscon_of_to_plat,
+ .priv_auto = sizeof(struct bootcount_syscon_priv),
+ .of_match = bootcount_syscon_ids,
+ .ops = &bootcount_syscon_ops,
+};