Commit c50b21b70523939c561d0455a2c423f63a9162ca

Authored by Nandor Han
Committed by Heiko Schocher
1 parent 7282b4352e

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>

Showing 7 changed files with 256 additions and 3 deletions Side-by-side Diff

arch/sandbox/dts/test.dts
... ... @@ -731,6 +731,20 @@
731 731 i2c-eeprom = <&bootcount_i2c>;
732 732 };
733 733  
  734 + bootcount_4@0 {
  735 + compatible = "u-boot,bootcount-syscon";
  736 + syscon = <&syscon0>;
  737 + reg = <0x0 0x04>, <0x0 0x04>;
  738 + reg-names = "syscon_reg", "offset";
  739 + };
  740 +
  741 + bootcount_2@0 {
  742 + compatible = "u-boot,bootcount-syscon";
  743 + syscon = <&syscon0>;
  744 + reg = <0x0 0x04>, <0x0 0x02> ;
  745 + reg-names = "syscon_reg", "offset";
  746 + };
  747 +
734 748 adc: adc@0 {
735 749 compatible = "sandbox,adc";
736 750 #io-channel-cells = <1>;
configs/sandbox_defconfig
... ... @@ -131,6 +131,7 @@
131 131 CONFIG_AXI_SANDBOX=y
132 132 CONFIG_BOOTCOUNT_LIMIT=y
133 133 CONFIG_DM_BOOTCOUNT=y
  134 +CONFIG_DM_BOOTCOUNT_SYSCON=y
134 135 CONFIG_DM_BOOTCOUNT_RTC=y
135 136 CONFIG_DM_BOOTCOUNT_I2C_EEPROM=y
136 137 CONFIG_BUTTON=y
doc/device-tree-bindings/bootcount-syscon.txt
  1 +Bootcount Configuration
  2 +This is the implementation of the feature as described in
  3 +https://www.denx.de/wiki/DULG/UBootBootCountLimit.
  4 +
  5 +Required Properties:
  6 +- compatible: must be "u-boot,bootcount-syscon".
  7 +- syscon: reference to the syscon device used.
  8 +- reg: contains address and size of the register and the location and size of the bootcount value.
  9 + The driver supports a 4 bytes register length and 2 and 4 bytes bootcount value length.
  10 +- reg-names: must be "syscon_reg", "offset";
  11 +
  12 +Example:
  13 + ...
  14 + syscon0: syscon@0 {
  15 + compatible = "sandbox,syscon0";
  16 + reg = <0x10 16>;
  17 + };
  18 + ...
  19 + bootcount@0 {
  20 + compatible = "u-boot,bootcount-syscon";
  21 + syscon = <&syscon0>;
  22 + reg = <0x0 0x04>, <0x0 0x04>;
  23 + reg-names = "syscon_reg", "offset";
  24 + };
drivers/bootcount/Kconfig
... ... @@ -144,6 +144,18 @@
144 144 is not cleared on softreset.
145 145 compatible = "u-boot,bootcount";
146 146  
  147 +config DM_BOOTCOUNT_SYSCON
  148 + bool "Support SYSCON devices as a backing store for bootcount"
  149 + select REGMAP
  150 + select SYSCON
  151 + help
  152 + Enable reading/writing the bootcount value in a DM SYSCON device.
  153 + The driver supports a fixed 32 bits size register using the native
  154 + endianness. However, this can be controlled from the SYSCON DT node
  155 + configuration.
  156 +
  157 + Accessing the backend is done using the regmap interface.
  158 +
147 159 endmenu
148 160  
149 161 endif
drivers/bootcount/Makefile
... ... @@ -14,4 +14,5 @@
14 14 obj-$(CONFIG_DM_BOOTCOUNT_RTC) += rtc.o
15 15 obj-$(CONFIG_DM_BOOTCOUNT_I2C_EEPROM) += i2c-eeprom.o
16 16 obj-$(CONFIG_DM_BOOTCOUNT_SPI_FLASH) += spi-flash.o
  17 +obj-$(CONFIG_DM_BOOTCOUNT_SYSCON) += bootcount_syscon.o
drivers/bootcount/bootcount_syscon.c
  1 +// SPDX-License-Identifier: GPL-2.0-only
  2 +/*
  3 + * Copyright (c) Vaisala Oyj. All rights reserved.
  4 + */
  5 +
  6 +#include <common.h>
  7 +#include <bootcount.h>
  8 +#include <dm.h>
  9 +#include <dm/device_compat.h>
  10 +#include <linux/ioport.h>
  11 +#include <regmap.h>
  12 +#include <syscon.h>
  13 +
  14 +#define BYTES_TO_BITS(bytes) ((bytes) << 3)
  15 +#define GEN_REG_MASK(val_size, val_addr) \
  16 + (GENMASK(BYTES_TO_BITS(val_size) - 1, 0) \
  17 + << (!!((val_addr) == 0x02) * BYTES_TO_BITS(2)))
  18 +#define GET_DEFAULT_VALUE(val_size) \
  19 + (CONFIG_SYS_BOOTCOUNT_MAGIC >> \
  20 + (BYTES_TO_BITS((sizeof(u32) - (val_size)))))
  21 +
  22 +/**
  23 + * struct bootcount_syscon_priv - driver's private data
  24 + *
  25 + * @regmap: syscon regmap
  26 + * @reg_addr: register address used to store the bootcount value
  27 + * @size: size of the bootcount value (2 or 4 bytes)
  28 + * @magic: magic used to validate/save the bootcount value
  29 + * @magic_mask: magic value bitmask
  30 + * @reg_mask: mask used to identify the location of the bootcount value
  31 + * in the register when 2 bytes length is used
  32 + * @shift: value used to extract the botcount value from the register
  33 + */
  34 +struct bootcount_syscon_priv {
  35 + struct regmap *regmap;
  36 + fdt_addr_t reg_addr;
  37 + fdt_size_t size;
  38 + u32 magic;
  39 + u32 magic_mask;
  40 + u32 reg_mask;
  41 + int shift;
  42 +};
  43 +
  44 +static int bootcount_syscon_set(struct udevice *dev, const u32 val)
  45 +{
  46 + struct bootcount_syscon_priv *priv = dev_get_priv(dev);
  47 + u32 regval;
  48 +
  49 + if ((val & priv->magic_mask) != 0)
  50 + return -EINVAL;
  51 +
  52 + regval = (priv->magic & priv->magic_mask) | (val & ~priv->magic_mask);
  53 +
  54 + if (priv->size == 2) {
  55 + regval &= 0xffff;
  56 + regval |= (regval & 0xffff) << BYTES_TO_BITS(priv->size);
  57 + }
  58 +
  59 + debug("%s: Prepare to write reg value: 0x%08x with register mask: 0x%08x\n",
  60 + __func__, regval, priv->reg_mask);
  61 +
  62 + return regmap_update_bits(priv->regmap, priv->reg_addr, priv->reg_mask,
  63 + regval);
  64 +}
  65 +
  66 +static int bootcount_syscon_get(struct udevice *dev, u32 *val)
  67 +{
  68 + struct bootcount_syscon_priv *priv = dev_get_priv(dev);
  69 + u32 regval;
  70 + int ret;
  71 +
  72 + ret = regmap_read(priv->regmap, priv->reg_addr, &regval);
  73 + if (ret)
  74 + return ret;
  75 +
  76 + regval &= priv->reg_mask;
  77 + regval >>= priv->shift;
  78 +
  79 + if ((regval & priv->magic_mask) == (priv->magic & priv->magic_mask)) {
  80 + *val = regval & ~priv->magic_mask;
  81 + } else {
  82 + dev_err(dev, "%s: Invalid bootcount magic\n", __func__);
  83 + return -EINVAL;
  84 + }
  85 +
  86 + debug("%s: Read bootcount value: 0x%08x from regval: 0x%08x\n",
  87 + __func__, *val, regval);
  88 + return 0;
  89 +}
  90 +
  91 +static int bootcount_syscon_of_to_plat(struct udevice *dev)
  92 +{
  93 + struct bootcount_syscon_priv *priv = dev_get_priv(dev);
  94 + fdt_addr_t bootcount_offset;
  95 + fdt_size_t reg_size;
  96 +
  97 + priv->regmap = syscon_regmap_lookup_by_phandle(dev, "syscon");
  98 + if (IS_ERR(priv->regmap)) {
  99 + dev_err(dev, "%s: Unable to find regmap (%ld)\n", __func__,
  100 + PTR_ERR(priv->regmap));
  101 + return PTR_ERR(priv->regmap);
  102 + }
  103 +
  104 + priv->reg_addr = dev_read_addr_size_name(dev, "syscon_reg", &reg_size);
  105 + if (priv->reg_addr == FDT_ADDR_T_NONE) {
  106 + dev_err(dev, "%s: syscon_reg address not found\n", __func__);
  107 + return -EINVAL;
  108 + }
  109 + if (reg_size != 4) {
  110 + dev_err(dev, "%s: Unsupported register size: %d\n", __func__,
  111 + reg_size);
  112 + return -EINVAL;
  113 + }
  114 +
  115 + bootcount_offset = dev_read_addr_size_name(dev, "offset", &priv->size);
  116 + if (bootcount_offset == FDT_ADDR_T_NONE) {
  117 + dev_err(dev, "%s: offset configuration not found\n", __func__);
  118 + return -EINVAL;
  119 + }
  120 + if (bootcount_offset + priv->size > reg_size) {
  121 + dev_err(dev,
  122 + "%s: Bootcount value doesn't fit in the reserved space\n",
  123 + __func__);
  124 + return -EINVAL;
  125 + }
  126 + if (priv->size != 2 && priv->size != 4) {
  127 + dev_err(dev,
  128 + "%s: Driver supports only 2 and 4 bytes bootcount size\n",
  129 + __func__);
  130 + return -EINVAL;
  131 + }
  132 +
  133 + priv->magic = GET_DEFAULT_VALUE(priv->size);
  134 + priv->magic_mask = GENMASK(BYTES_TO_BITS(priv->size) - 1,
  135 + BYTES_TO_BITS(priv->size >> 1));
  136 + priv->shift = !!(bootcount_offset == 0x02) * BYTES_TO_BITS(priv->size);
  137 + priv->reg_mask = GEN_REG_MASK(priv->size, bootcount_offset);
  138 +
  139 + return 0;
  140 +}
  141 +
  142 +static const struct bootcount_ops bootcount_syscon_ops = {
  143 + .get = bootcount_syscon_get,
  144 + .set = bootcount_syscon_set,
  145 +};
  146 +
  147 +static const struct udevice_id bootcount_syscon_ids[] = {
  148 + { .compatible = "u-boot,bootcount-syscon" },
  149 + {}
  150 +};
  151 +
  152 +U_BOOT_DRIVER(bootcount_syscon) = {
  153 + .name = "bootcount-syscon",
  154 + .id = UCLASS_BOOTCOUNT,
  155 + .of_to_plat = bootcount_syscon_of_to_plat,
  156 + .priv_auto = sizeof(struct bootcount_syscon_priv),
  157 + .of_match = bootcount_syscon_ids,
  158 + .ops = &bootcount_syscon_ops,
  159 +};
... ... @@ -12,12 +12,13 @@
12 12 #include <test/test.h>
13 13 #include <test/ut.h>
14 14  
15   -static int dm_test_bootcount(struct unit_test_state *uts)
  15 +static int dm_test_bootcount_rtc(struct unit_test_state *uts)
16 16 {
17 17 struct udevice *dev;
18 18 u32 val;
19 19  
20   - ut_assertok(uclass_get_device(UCLASS_BOOTCOUNT, 0, &dev));
  20 + ut_assertok(uclass_get_device_by_name(UCLASS_BOOTCOUNT, "bootcount@0",
  21 + &dev));
21 22 ut_assertok(dm_bootcount_set(dev, 0));
22 23 ut_assertok(dm_bootcount_get(dev, &val));
23 24 ut_assert(val == 0);
... ... @@ -36,5 +37,47 @@
36 37 return 0;
37 38 }
38 39  
39   -DM_TEST(dm_test_bootcount, UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT);
  40 +DM_TEST(dm_test_bootcount_rtc, UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT);
  41 +
  42 +static int dm_test_bootcount_syscon_four_bytes(struct unit_test_state *uts)
  43 +{
  44 + struct udevice *dev;
  45 + u32 val;
  46 +
  47 + sandbox_set_enable_memio(true);
  48 + ut_assertok(uclass_get_device_by_name(UCLASS_BOOTCOUNT, "bootcount_4@0",
  49 + &dev));
  50 + ut_assertok(dm_bootcount_set(dev, 0xab));
  51 + ut_assertok(dm_bootcount_get(dev, &val));
  52 + ut_assert(val == 0xab);
  53 + ut_assertok(dm_bootcount_set(dev, 0));
  54 + ut_assertok(dm_bootcount_get(dev, &val));
  55 + ut_assert(val == 0);
  56 +
  57 + return 0;
  58 +}
  59 +
  60 +DM_TEST(dm_test_bootcount_syscon_four_bytes,
  61 + UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT);
  62 +
  63 +static int dm_test_bootcount_syscon_two_bytes(struct unit_test_state *uts)
  64 +{
  65 + struct udevice *dev;
  66 + u32 val;
  67 +
  68 + sandbox_set_enable_memio(true);
  69 + ut_assertok(uclass_get_device_by_name(UCLASS_BOOTCOUNT, "bootcount_2@0",
  70 + &dev));
  71 + ut_assertok(dm_bootcount_set(dev, 0xab));
  72 + ut_assertok(dm_bootcount_get(dev, &val));
  73 + ut_assert(val == 0xab);
  74 + ut_assertok(dm_bootcount_set(dev, 0));
  75 + ut_assertok(dm_bootcount_get(dev, &val));
  76 + ut_assert(val == 0);
  77 +
  78 + return 0;
  79 +}
  80 +
  81 +DM_TEST(dm_test_bootcount_syscon_two_bytes,
  82 + UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT);