blob: 5dbc13cd5452e9a9a113a737f857b00223c33e2f [file] [log] [blame]
Nandor Han88895812021-06-10 15:40:38 +03001// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright (c) Vaisala Oyj. All rights reserved.
4 */
5
Nandor Han88895812021-06-10 15:40:38 +03006#include <bootcount.h>
7#include <dm.h>
8#include <dm/device_compat.h>
9#include <linux/ioport.h>
10#include <regmap.h>
11#include <syscon.h>
12
13#define BYTES_TO_BITS(bytes) ((bytes) << 3)
14#define GEN_REG_MASK(val_size, val_addr) \
15 (GENMASK(BYTES_TO_BITS(val_size) - 1, 0) \
16 << (!!((val_addr) == 0x02) * BYTES_TO_BITS(2)))
17#define GET_DEFAULT_VALUE(val_size) \
18 (CONFIG_SYS_BOOTCOUNT_MAGIC >> \
19 (BYTES_TO_BITS((sizeof(u32) - (val_size)))))
20
21/**
22 * struct bootcount_syscon_priv - driver's private data
23 *
24 * @regmap: syscon regmap
25 * @reg_addr: register address used to store the bootcount value
26 * @size: size of the bootcount value (2 or 4 bytes)
27 * @magic: magic used to validate/save the bootcount value
28 * @magic_mask: magic value bitmask
29 * @reg_mask: mask used to identify the location of the bootcount value
30 * in the register when 2 bytes length is used
31 * @shift: value used to extract the botcount value from the register
32 */
33struct bootcount_syscon_priv {
34 struct regmap *regmap;
35 fdt_addr_t reg_addr;
36 fdt_size_t size;
37 u32 magic;
38 u32 magic_mask;
39 u32 reg_mask;
40 int shift;
41};
42
43static int bootcount_syscon_set(struct udevice *dev, const u32 val)
44{
45 struct bootcount_syscon_priv *priv = dev_get_priv(dev);
46 u32 regval;
47
48 if ((val & priv->magic_mask) != 0)
49 return -EINVAL;
50
51 regval = (priv->magic & priv->magic_mask) | (val & ~priv->magic_mask);
52
53 if (priv->size == 2) {
54 regval &= 0xffff;
55 regval |= (regval & 0xffff) << BYTES_TO_BITS(priv->size);
56 }
57
58 debug("%s: Prepare to write reg value: 0x%08x with register mask: 0x%08x\n",
59 __func__, regval, priv->reg_mask);
60
61 return regmap_update_bits(priv->regmap, priv->reg_addr, priv->reg_mask,
62 regval);
63}
64
65static int bootcount_syscon_get(struct udevice *dev, u32 *val)
66{
67 struct bootcount_syscon_priv *priv = dev_get_priv(dev);
68 u32 regval;
69 int ret;
70
71 ret = regmap_read(priv->regmap, priv->reg_addr, &regval);
72 if (ret)
73 return ret;
74
75 regval &= priv->reg_mask;
76 regval >>= priv->shift;
77
78 if ((regval & priv->magic_mask) == (priv->magic & priv->magic_mask)) {
79 *val = regval & ~priv->magic_mask;
80 } else {
81 dev_err(dev, "%s: Invalid bootcount magic\n", __func__);
82 return -EINVAL;
83 }
84
85 debug("%s: Read bootcount value: 0x%08x from regval: 0x%08x\n",
86 __func__, *val, regval);
87 return 0;
88}
89
90static int bootcount_syscon_of_to_plat(struct udevice *dev)
91{
92 struct bootcount_syscon_priv *priv = dev_get_priv(dev);
93 fdt_addr_t bootcount_offset;
94 fdt_size_t reg_size;
95
96 priv->regmap = syscon_regmap_lookup_by_phandle(dev, "syscon");
97 if (IS_ERR(priv->regmap)) {
98 dev_err(dev, "%s: Unable to find regmap (%ld)\n", __func__,
99 PTR_ERR(priv->regmap));
100 return PTR_ERR(priv->regmap);
101 }
102
103 priv->reg_addr = dev_read_addr_size_name(dev, "syscon_reg", &reg_size);
104 if (priv->reg_addr == FDT_ADDR_T_NONE) {
105 dev_err(dev, "%s: syscon_reg address not found\n", __func__);
106 return -EINVAL;
107 }
108 if (reg_size != 4) {
Heinrich Schuchardtcca02872022-01-19 01:33:43 +0100109 dev_err(dev, "%s: Unsupported register size: %pa\n", __func__,
110 &reg_size);
Nandor Han88895812021-06-10 15:40:38 +0300111 return -EINVAL;
112 }
113
114 bootcount_offset = dev_read_addr_size_name(dev, "offset", &priv->size);
115 if (bootcount_offset == FDT_ADDR_T_NONE) {
116 dev_err(dev, "%s: offset configuration not found\n", __func__);
117 return -EINVAL;
118 }
119 if (bootcount_offset + priv->size > reg_size) {
120 dev_err(dev,
121 "%s: Bootcount value doesn't fit in the reserved space\n",
122 __func__);
123 return -EINVAL;
124 }
125 if (priv->size != 2 && priv->size != 4) {
126 dev_err(dev,
127 "%s: Driver supports only 2 and 4 bytes bootcount size\n",
128 __func__);
129 return -EINVAL;
130 }
131
132 priv->magic = GET_DEFAULT_VALUE(priv->size);
133 priv->magic_mask = GENMASK(BYTES_TO_BITS(priv->size) - 1,
134 BYTES_TO_BITS(priv->size >> 1));
135 priv->shift = !!(bootcount_offset == 0x02) * BYTES_TO_BITS(priv->size);
136 priv->reg_mask = GEN_REG_MASK(priv->size, bootcount_offset);
137
138 return 0;
139}
140
141static const struct bootcount_ops bootcount_syscon_ops = {
142 .get = bootcount_syscon_get,
143 .set = bootcount_syscon_set,
144};
145
146static const struct udevice_id bootcount_syscon_ids[] = {
147 { .compatible = "u-boot,bootcount-syscon" },
148 {}
149};
150
151U_BOOT_DRIVER(bootcount_syscon) = {
152 .name = "bootcount-syscon",
153 .id = UCLASS_BOOTCOUNT,
154 .of_to_plat = bootcount_syscon_of_to_plat,
155 .priv_auto = sizeof(struct bootcount_syscon_priv),
156 .of_match = bootcount_syscon_ids,
157 .ops = &bootcount_syscon_ops,
158};