mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-13 00:17:23 +00:00
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>
This commit is contained in:
parent
7282b4352e
commit
c50b21b705
7 changed files with 256 additions and 3 deletions
|
@ -731,6 +731,20 @@
|
||||||
i2c-eeprom = <&bootcount_i2c>;
|
i2c-eeprom = <&bootcount_i2c>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bootcount_4@0 {
|
||||||
|
compatible = "u-boot,bootcount-syscon";
|
||||||
|
syscon = <&syscon0>;
|
||||||
|
reg = <0x0 0x04>, <0x0 0x04>;
|
||||||
|
reg-names = "syscon_reg", "offset";
|
||||||
|
};
|
||||||
|
|
||||||
|
bootcount_2@0 {
|
||||||
|
compatible = "u-boot,bootcount-syscon";
|
||||||
|
syscon = <&syscon0>;
|
||||||
|
reg = <0x0 0x04>, <0x0 0x02> ;
|
||||||
|
reg-names = "syscon_reg", "offset";
|
||||||
|
};
|
||||||
|
|
||||||
adc: adc@0 {
|
adc: adc@0 {
|
||||||
compatible = "sandbox,adc";
|
compatible = "sandbox,adc";
|
||||||
#io-channel-cells = <1>;
|
#io-channel-cells = <1>;
|
||||||
|
|
|
@ -131,6 +131,7 @@ CONFIG_AXI=y
|
||||||
CONFIG_AXI_SANDBOX=y
|
CONFIG_AXI_SANDBOX=y
|
||||||
CONFIG_BOOTCOUNT_LIMIT=y
|
CONFIG_BOOTCOUNT_LIMIT=y
|
||||||
CONFIG_DM_BOOTCOUNT=y
|
CONFIG_DM_BOOTCOUNT=y
|
||||||
|
CONFIG_DM_BOOTCOUNT_SYSCON=y
|
||||||
CONFIG_DM_BOOTCOUNT_RTC=y
|
CONFIG_DM_BOOTCOUNT_RTC=y
|
||||||
CONFIG_DM_BOOTCOUNT_I2C_EEPROM=y
|
CONFIG_DM_BOOTCOUNT_I2C_EEPROM=y
|
||||||
CONFIG_BUTTON=y
|
CONFIG_BUTTON=y
|
||||||
|
|
24
doc/device-tree-bindings/bootcount-syscon.txt
Normal file
24
doc/device-tree-bindings/bootcount-syscon.txt
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
Bootcount Configuration
|
||||||
|
This is the implementation of the feature as described in
|
||||||
|
https://www.denx.de/wiki/DULG/UBootBootCountLimit.
|
||||||
|
|
||||||
|
Required Properties:
|
||||||
|
- compatible: must be "u-boot,bootcount-syscon".
|
||||||
|
- syscon: reference to the syscon device used.
|
||||||
|
- reg: contains address and size of the register and the location and size of the bootcount value.
|
||||||
|
The driver supports a 4 bytes register length and 2 and 4 bytes bootcount value length.
|
||||||
|
- reg-names: must be "syscon_reg", "offset";
|
||||||
|
|
||||||
|
Example:
|
||||||
|
...
|
||||||
|
syscon0: syscon@0 {
|
||||||
|
compatible = "sandbox,syscon0";
|
||||||
|
reg = <0x10 16>;
|
||||||
|
};
|
||||||
|
...
|
||||||
|
bootcount@0 {
|
||||||
|
compatible = "u-boot,bootcount-syscon";
|
||||||
|
syscon = <&syscon0>;
|
||||||
|
reg = <0x0 0x04>, <0x0 0x04>;
|
||||||
|
reg-names = "syscon_reg", "offset";
|
||||||
|
};
|
|
@ -144,6 +144,18 @@ config BOOTCOUNT_MEM
|
||||||
is not cleared on softreset.
|
is not cleared on softreset.
|
||||||
compatible = "u-boot,bootcount";
|
compatible = "u-boot,bootcount";
|
||||||
|
|
||||||
|
config DM_BOOTCOUNT_SYSCON
|
||||||
|
bool "Support SYSCON devices as a backing store for bootcount"
|
||||||
|
select REGMAP
|
||||||
|
select SYSCON
|
||||||
|
help
|
||||||
|
Enable reading/writing the bootcount value in a DM SYSCON device.
|
||||||
|
The driver supports a fixed 32 bits size register using the native
|
||||||
|
endianness. However, this can be controlled from the SYSCON DT node
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
Accessing the backend is done using the regmap interface.
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
|
|
@ -14,3 +14,4 @@ obj-$(CONFIG_DM_BOOTCOUNT) += bootcount-uclass.o
|
||||||
obj-$(CONFIG_DM_BOOTCOUNT_RTC) += rtc.o
|
obj-$(CONFIG_DM_BOOTCOUNT_RTC) += rtc.o
|
||||||
obj-$(CONFIG_DM_BOOTCOUNT_I2C_EEPROM) += i2c-eeprom.o
|
obj-$(CONFIG_DM_BOOTCOUNT_I2C_EEPROM) += i2c-eeprom.o
|
||||||
obj-$(CONFIG_DM_BOOTCOUNT_SPI_FLASH) += spi-flash.o
|
obj-$(CONFIG_DM_BOOTCOUNT_SPI_FLASH) += spi-flash.o
|
||||||
|
obj-$(CONFIG_DM_BOOTCOUNT_SYSCON) += bootcount_syscon.o
|
||||||
|
|
159
drivers/bootcount/bootcount_syscon.c
Normal file
159
drivers/bootcount/bootcount_syscon.c
Normal file
|
@ -0,0 +1,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, ®val);
|
||||||
|
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", ®_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,
|
||||||
|
};
|
|
@ -12,12 +12,13 @@
|
||||||
#include <test/test.h>
|
#include <test/test.h>
|
||||||
#include <test/ut.h>
|
#include <test/ut.h>
|
||||||
|
|
||||||
static int dm_test_bootcount(struct unit_test_state *uts)
|
static int dm_test_bootcount_rtc(struct unit_test_state *uts)
|
||||||
{
|
{
|
||||||
struct udevice *dev;
|
struct udevice *dev;
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
ut_assertok(uclass_get_device(UCLASS_BOOTCOUNT, 0, &dev));
|
ut_assertok(uclass_get_device_by_name(UCLASS_BOOTCOUNT, "bootcount@0",
|
||||||
|
&dev));
|
||||||
ut_assertok(dm_bootcount_set(dev, 0));
|
ut_assertok(dm_bootcount_set(dev, 0));
|
||||||
ut_assertok(dm_bootcount_get(dev, &val));
|
ut_assertok(dm_bootcount_get(dev, &val));
|
||||||
ut_assert(val == 0);
|
ut_assert(val == 0);
|
||||||
|
@ -36,5 +37,46 @@ static int dm_test_bootcount(struct unit_test_state *uts)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
DM_TEST(dm_test_bootcount, UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT);
|
DM_TEST(dm_test_bootcount_rtc, UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT);
|
||||||
|
|
||||||
|
static int dm_test_bootcount_syscon_four_bytes(struct unit_test_state *uts)
|
||||||
|
{
|
||||||
|
struct udevice *dev;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
|
sandbox_set_enable_memio(true);
|
||||||
|
ut_assertok(uclass_get_device_by_name(UCLASS_BOOTCOUNT, "bootcount_4@0",
|
||||||
|
&dev));
|
||||||
|
ut_assertok(dm_bootcount_set(dev, 0xab));
|
||||||
|
ut_assertok(dm_bootcount_get(dev, &val));
|
||||||
|
ut_assert(val == 0xab);
|
||||||
|
ut_assertok(dm_bootcount_set(dev, 0));
|
||||||
|
ut_assertok(dm_bootcount_get(dev, &val));
|
||||||
|
ut_assert(val == 0);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DM_TEST(dm_test_bootcount_syscon_four_bytes,
|
||||||
|
UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT);
|
||||||
|
|
||||||
|
static int dm_test_bootcount_syscon_two_bytes(struct unit_test_state *uts)
|
||||||
|
{
|
||||||
|
struct udevice *dev;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
|
sandbox_set_enable_memio(true);
|
||||||
|
ut_assertok(uclass_get_device_by_name(UCLASS_BOOTCOUNT, "bootcount_2@0",
|
||||||
|
&dev));
|
||||||
|
ut_assertok(dm_bootcount_set(dev, 0xab));
|
||||||
|
ut_assertok(dm_bootcount_get(dev, &val));
|
||||||
|
ut_assert(val == 0xab);
|
||||||
|
ut_assertok(dm_bootcount_set(dev, 0));
|
||||||
|
ut_assertok(dm_bootcount_get(dev, &val));
|
||||||
|
ut_assert(val == 0);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DM_TEST(dm_test_bootcount_syscon_two_bytes,
|
||||||
|
UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT);
|
||||||
|
|
Loading…
Reference in a new issue