summaryrefslogtreecommitdiff
path: root/drivers/bootcount/bootcount_syscon.c
blob: 413fd5bb9df5eb87f148b314e62855c1a8086dd5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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,
};