]>
Commit | Line | Data |
---|---|---|
c50b21b7 NH |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * Copyright (c) Vaisala Oyj. All rights reserved. | |
4 | */ | |
5 | ||
d678a59d | 6 | #include <common.h> |
c50b21b7 NH |
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) { | |
05ec8991 HS |
110 | dev_err(dev, "%s: Unsupported register size: %pa\n", __func__, |
111 | ®_size); | |
c50b21b7 NH |
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 | }; |