Commit c50b21b70523939c561d0455a2c423f63a9162ca
Committed by
Heiko Schocher
1 parent
7282b4352e
Exists in
emb_lf_v2023.04
and in
1 other branch
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
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
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, ®val); | |
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", ®_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 | +}; |
test/dm/bootcount.c
... | ... | @@ -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); |