From 2aefa6e3f2ab621483ca645b16c7bf8cb9334fa3 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 15 Jul 2021 17:39:59 +0200 Subject: [PATCH 1/4] i2c: add dm_i2c_reg_clrset Add function to apply a bitmask to an i2c register, so that specific bits can be cleared and/or set. Suggested-by: Simon Glass Signed-off-by: Sebastian Reichel Reviewed-by: Simon Glass --- drivers/i2c/i2c-uclass.c | 15 +++++++++++++++ include/i2c.h | 14 ++++++++++++++ test/dm/i2c.c | 29 +++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/drivers/i2c/i2c-uclass.c b/drivers/i2c/i2c-uclass.c index 04c88503a2..db1c9d9462 100644 --- a/drivers/i2c/i2c-uclass.c +++ b/drivers/i2c/i2c-uclass.c @@ -247,6 +247,21 @@ int dm_i2c_reg_write(struct udevice *dev, uint offset, uint value) return dm_i2c_write(dev, offset, &val, 1); } +int dm_i2c_reg_clrset(struct udevice *dev, uint offset, u32 clr, u32 set) +{ + uint8_t val; + int ret; + + ret = dm_i2c_read(dev, offset, &val, 1); + if (ret < 0) + return ret; + + val &= ~clr; + val |= set; + + return dm_i2c_write(dev, offset, &val, 1); +} + /** * i2c_probe_chip() - probe for a chip on a bus * diff --git a/include/i2c.h b/include/i2c.h index 8db34a67fe..3d9ecaba0b 100644 --- a/include/i2c.h +++ b/include/i2c.h @@ -242,6 +242,20 @@ int dm_i2c_reg_read(struct udevice *dev, uint offset); */ int dm_i2c_reg_write(struct udevice *dev, uint offset, unsigned int val); +/** + * dm_i2c_reg_clrset() - Apply bitmask to an I2C register + * + * Read value, apply bitmask and write modified value back to the + * given address in an I2C chip + * + * @dev: Device to use for transfer + * @offset: Address for the R/W operation + * @clr: Bitmask of bits that should be cleared + * @set: Bitmask of bits that should be set + * @return 0 on success, -ve on error + */ +int dm_i2c_reg_clrset(struct udevice *dev, uint offset, u32 clr, u32 set); + /** * dm_i2c_xfer() - Transfer messages over I2C * diff --git a/test/dm/i2c.c b/test/dm/i2c.c index d74f5f9fbc..74b2097195 100644 --- a/test/dm/i2c.c +++ b/test/dm/i2c.c @@ -304,3 +304,32 @@ static int dm_test_i2c_addr_offset(struct unit_test_state *uts) } DM_TEST(dm_test_i2c_addr_offset, UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT); + +static int dm_test_i2c_reg_clrset(struct unit_test_state *uts) +{ + struct udevice *eeprom; + struct udevice *dev; + u8 buf[5]; + + ut_assertok(i2c_get_chip_for_busnum(busnum, chip, 1, &dev)); + + /* Do a transfer so we can find the emulator */ + ut_assertok(dm_i2c_read(dev, 0, buf, 5)); + ut_assertok(uclass_first_device(UCLASS_I2C_EMUL, &eeprom)); + + /* Dummy data for the test */ + ut_assertok(dm_i2c_write(dev, 0, "\xff\x00\xff\x00\x10", 5)); + + /* Do some clrset tests */ + ut_assertok(dm_i2c_reg_clrset(dev, 0, 0xff, 0x10)); + ut_assertok(dm_i2c_reg_clrset(dev, 1, 0x00, 0x11)); + ut_assertok(dm_i2c_reg_clrset(dev, 2, 0xed, 0x00)); + ut_assertok(dm_i2c_reg_clrset(dev, 3, 0xff, 0x13)); + ut_assertok(dm_i2c_reg_clrset(dev, 4, 0x00, 0x14)); + + ut_assertok(dm_i2c_read(dev, 0, buf, 5)); + ut_asserteq_mem("\x10\x11\x12\x13\x14", buf, sizeof(buf)); + + return 0; +} +DM_TEST(dm_test_i2c_reg_clrset, UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT); From 0b3da993a4550d3b990989e3bcdcbbb8cf889258 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Thu, 15 Jul 2021 17:40:00 +0200 Subject: [PATCH 2/4] gpio: mcp230xx: Introduce new driver Introduce driver for I2C based MCP230xx GPIO chips, which are quite common and already well supported by the Linux kernel. Reviewed-by: Simon Glass Signed-off-by: Sebastian Reichel Reviewed-by: Simon Glass --- drivers/gpio/Kconfig | 10 ++ drivers/gpio/Makefile | 1 + drivers/gpio/mcp230xx_gpio.c | 235 +++++++++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 drivers/gpio/mcp230xx_gpio.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 09695f6c2b..4a89c1a62b 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -179,6 +179,16 @@ config LPC32XX_GPIO help Support for the LPC32XX GPIO driver. +config MCP230XX_GPIO + bool "MCP230XX GPIO driver" + depends on DM + help + Support for Microchip's MCP230XX I2C connected GPIO devices. + The following chips are supported: + - MCP23008 + - MCP23017 + - MCP23018 + config MSCC_SGPIO bool "Microsemi Serial GPIO driver" depends on DM_GPIO && SOC_VCOREIII diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 16b09fb1b5..58f4704f6b 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_KIRKWOOD_GPIO) += kw_gpio.o obj-$(CONFIG_KONA_GPIO) += kona_gpio.o obj-$(CONFIG_MARVELL_GPIO) += mvgpio.o obj-$(CONFIG_MARVELL_MFP) += mvmfp.o +obj-$(CONFIG_MCP230XX_GPIO) += mcp230xx_gpio.o obj-$(CONFIG_MXC_GPIO) += mxc_gpio.o obj-$(CONFIG_MXS_GPIO) += mxs_gpio.o obj-$(CONFIG_PCA953X) += pca953x.o diff --git a/drivers/gpio/mcp230xx_gpio.c b/drivers/gpio/mcp230xx_gpio.c new file mode 100644 index 0000000000..9f02fd42b3 --- /dev/null +++ b/drivers/gpio/mcp230xx_gpio.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021, Collabora Ltd. + * Copyright (C) 2021, General Electric Company + * Author(s): Sebastian Reichel + */ + +#define LOG_CATEGORY UCLASS_GPIO + +#include +#include +#include +#include +#include +#include +#include + +enum mcp230xx_type { + UNKNOWN = 0, + MCP23008, + MCP23017, + MCP23018, +}; + +#define MCP230XX_IODIR 0x00 +#define MCP230XX_GPPU 0x06 +#define MCP230XX_GPIO 0x09 +#define MCP230XX_OLAT 0x0a + +#define BANKSIZE 8 + +static int mcp230xx_read(struct udevice *dev, uint reg, uint offset) +{ + struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); + int bank = offset / BANKSIZE; + int mask = 1 << (offset % BANKSIZE); + int shift = (uc_priv->gpio_count / BANKSIZE) - 1; + int ret; + + ret = dm_i2c_reg_read(dev, (reg << shift) | bank); + if (ret < 0) + return ret; + + return !!(ret & mask); +} + +static int mcp230xx_write(struct udevice *dev, uint reg, uint offset, bool val) +{ + struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); + int bank = offset / BANKSIZE; + int mask = 1 << (offset % BANKSIZE); + int shift = (uc_priv->gpio_count / BANKSIZE) - 1; + + return dm_i2c_reg_clrset(dev, (reg << shift) | bank, mask, val ? mask : 0); +} + +static int mcp230xx_get_value(struct udevice *dev, uint offset) +{ + int ret; + + ret = mcp230xx_read(dev, MCP230XX_GPIO, offset); + if (ret < 0) { + dev_err(dev, "%s error: %d\n", __func__, ret); + return ret; + } + + return ret; +} + +static int mcp230xx_set_value(struct udevice *dev, uint offset, int val) +{ + int ret; + + ret = mcp230xx_write(dev, MCP230XX_GPIO, offset, val); + if (ret < 0) { + dev_err(dev, "%s error: %d\n", __func__, ret); + return ret; + } + + return ret; +} + +static int mcp230xx_get_flags(struct udevice *dev, unsigned int offset, + ulong *flags) +{ + int direction, pullup; + + pullup = mcp230xx_read(dev, MCP230XX_GPPU, offset); + if (pullup < 0) { + dev_err(dev, "%s error: %d\n", __func__, pullup); + return pullup; + } + + direction = mcp230xx_read(dev, MCP230XX_IODIR, offset); + if (direction < 0) { + dev_err(dev, "%s error: %d\n", __func__, direction); + return direction; + } + + *flags = direction ? GPIOD_IS_IN : GPIOD_IS_OUT; + + if (pullup) + *flags |= GPIOD_PULL_UP; + + return 0; +} + +static int mcp230xx_set_flags(struct udevice *dev, uint offset, ulong flags) +{ + bool input = !(flags & (GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE)); + bool pullup = flags & GPIOD_PULL_UP; + ulong supported_mask; + int ret; + + /* Note: active-low is ignored (handled by core) */ + supported_mask = GPIOD_ACTIVE_LOW | GPIOD_MASK_DIR | GPIOD_PULL_UP; + if (flags & ~supported_mask) { + dev_err(dev, "%s unsupported flag(s): %lx\n", __func__, flags); + return -EINVAL; + } + + ret = mcp230xx_write(dev, MCP230XX_OLAT, offset, !!(flags & GPIOD_IS_OUT_ACTIVE)); + if (ret) { + dev_err(dev, "%s failed to setup output latch: %d\n", __func__, ret); + return ret; + } + + ret = mcp230xx_write(dev, MCP230XX_GPPU, offset, pullup); + if (ret) { + dev_err(dev, "%s failed to setup pull-up: %d\n", __func__, ret); + return ret; + } + + ret = mcp230xx_write(dev, MCP230XX_IODIR, offset, input); + if (ret) { + dev_err(dev, "%s failed to setup direction: %d\n", __func__, ret); + return ret; + } + + return 0; +} + +static int mcp230xx_direction_input(struct udevice *dev, uint offset) +{ + return mcp230xx_set_flags(dev, offset, GPIOD_IS_IN); +} + +static int mcp230xx_direction_output(struct udevice *dev, uint offset, int val) +{ + int ret = mcp230xx_set_value(dev, offset, val); + if (ret < 0) { + dev_err(dev, "%s error: %d\n", __func__, ret); + return ret; + } + return mcp230xx_set_flags(dev, offset, GPIOD_IS_OUT); +} + +static int mcp230xx_get_function(struct udevice *dev, uint offset) +{ + int ret; + + ret = mcp230xx_read(dev, MCP230XX_IODIR, offset); + if (ret < 0) { + dev_err(dev, "%s error: %d\n", __func__, ret); + return ret; + } + + return ret ? GPIOF_INPUT : GPIOF_OUTPUT; +} + +static const struct dm_gpio_ops mcp230xx_ops = { + .direction_input = mcp230xx_direction_input, + .direction_output = mcp230xx_direction_output, + .get_value = mcp230xx_get_value, + .set_value = mcp230xx_set_value, + .get_function = mcp230xx_get_function, + .set_flags = mcp230xx_set_flags, + .get_flags = mcp230xx_get_flags, +}; + +static int mcp230xx_probe(struct udevice *dev) +{ + struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); + char name[32], label[8], *str; + int addr, gpio_count, size; + const u8 *tmp; + + switch (dev_get_driver_data(dev)) { + case MCP23008: + gpio_count = 8; + break; + case MCP23017: + case MCP23018: + gpio_count = 16; + break; + default: + return -ENODEV; + } + + addr = dev_read_addr(dev); + tmp = dev_read_prop(dev, "label", &size); + if (tmp) { + memcpy(label, tmp, sizeof(label) - 1); + label[sizeof(label) - 1] = '\0'; + snprintf(name, sizeof(name), "%s@%x_", label, addr); + } else { + snprintf(name, sizeof(name), "gpio@%x_", addr); + } + + str = strdup(name); + if (!str) + return -ENOMEM; + + uc_priv->bank_name = str; + uc_priv->gpio_count = gpio_count; + + dev_dbg(dev, "%s is ready\n", str); + + return 0; +} + +static const struct udevice_id mcp230xx_ids[] = { + { .compatible = "microchip,mcp23008", .data = MCP23008, }, + { .compatible = "microchip,mcp23017", .data = MCP23017, }, + { .compatible = "microchip,mcp23018", .data = MCP23018, }, + { } +}; + +U_BOOT_DRIVER(mcp230xx) = { + .name = "mcp230xx", + .id = UCLASS_GPIO, + .ops = &mcp230xx_ops, + .probe = mcp230xx_probe, + .of_match = mcp230xx_ids, +}; From 7282b4352eba74e134c7ebba945338e418ec7faf Mon Sep 17 00:00:00 2001 From: Samuel Holland Date: Sat, 21 Aug 2021 19:25:43 -0500 Subject: [PATCH 3/4] i2c: i2c-gpio: Support the named GPIO binding To avoid confusion about the order of the GPIOs, the i2c-gpio binding was updated to use a separate property for each GPIO instead of an array. However, the driver only supports the old binding. Add support for the new binding as well, so the driver continues to work as device trees are updated. Signed-off-by: Samuel Holland Reviewed-by: Heiko Schocher --- drivers/i2c/i2c-gpio.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/i2c/i2c-gpio.c b/drivers/i2c/i2c-gpio.c index cf8f8f4035..1aedad5c8e 100644 --- a/drivers/i2c/i2c-gpio.c +++ b/drivers/i2c/i2c-gpio.c @@ -336,8 +336,17 @@ static int i2c_gpio_of_to_plat(struct udevice *dev) struct i2c_gpio_bus *bus = dev_get_priv(dev); int ret; + /* "gpios" is deprecated and replaced by "sda-gpios" + "scl-gpios". */ ret = gpio_request_list_by_name(dev, "gpios", bus->gpios, ARRAY_SIZE(bus->gpios), 0); + if (ret == -ENOENT) { + ret = gpio_request_by_name(dev, "sda-gpios", 0, + &bus->gpios[PIN_SDA], 0); + if (ret < 0) + goto error; + ret = gpio_request_by_name(dev, "scl-gpios", 0, + &bus->gpios[PIN_SCL], 0); + } if (ret < 0) goto error; From c50b21b70523939c561d0455a2c423f63a9162ca Mon Sep 17 00:00:00 2001 From: Nandor Han Date: Thu, 10 Jun 2021 15:40:38 +0300 Subject: [PATCH 4/4] 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 Reviewed-by: Simon Glass --- arch/sandbox/dts/test.dts | 14 ++ configs/sandbox_defconfig | 1 + doc/device-tree-bindings/bootcount-syscon.txt | 24 +++ drivers/bootcount/Kconfig | 12 ++ drivers/bootcount/Makefile | 1 + drivers/bootcount/bootcount_syscon.c | 159 ++++++++++++++++++ test/dm/bootcount.c | 48 +++++- 7 files changed, 256 insertions(+), 3 deletions(-) create mode 100644 doc/device-tree-bindings/bootcount-syscon.txt create mode 100644 drivers/bootcount/bootcount_syscon.c diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index d5976318d1..962bdbe556 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -731,6 +731,20 @@ 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 { compatible = "sandbox,adc"; #io-channel-cells = <1>; diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 952d430304..4658f18dfa 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -131,6 +131,7 @@ CONFIG_AXI=y CONFIG_AXI_SANDBOX=y CONFIG_BOOTCOUNT_LIMIT=y CONFIG_DM_BOOTCOUNT=y +CONFIG_DM_BOOTCOUNT_SYSCON=y CONFIG_DM_BOOTCOUNT_RTC=y CONFIG_DM_BOOTCOUNT_I2C_EEPROM=y CONFIG_BUTTON=y diff --git a/doc/device-tree-bindings/bootcount-syscon.txt b/doc/device-tree-bindings/bootcount-syscon.txt new file mode 100644 index 0000000000..e124f7b614 --- /dev/null +++ b/doc/device-tree-bindings/bootcount-syscon.txt @@ -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"; + }; diff --git a/drivers/bootcount/Kconfig b/drivers/bootcount/Kconfig index 0de2b7bd78..607027c968 100644 --- a/drivers/bootcount/Kconfig +++ b/drivers/bootcount/Kconfig @@ -144,6 +144,18 @@ config BOOTCOUNT_MEM is not cleared on softreset. 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 endif diff --git a/drivers/bootcount/Makefile b/drivers/bootcount/Makefile index 12658ffdce..3a784bb0a6 100644 --- a/drivers/bootcount/Makefile +++ b/drivers/bootcount/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_DM_BOOTCOUNT) += bootcount-uclass.o obj-$(CONFIG_DM_BOOTCOUNT_RTC) += rtc.o obj-$(CONFIG_DM_BOOTCOUNT_I2C_EEPROM) += i2c-eeprom.o obj-$(CONFIG_DM_BOOTCOUNT_SPI_FLASH) += spi-flash.o +obj-$(CONFIG_DM_BOOTCOUNT_SYSCON) += bootcount_syscon.o diff --git a/drivers/bootcount/bootcount_syscon.c b/drivers/bootcount/bootcount_syscon.c new file mode 100644 index 0000000000..413fd5bb9d --- /dev/null +++ b/drivers/bootcount/bootcount_syscon.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Vaisala Oyj. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include + +#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, +}; diff --git a/test/dm/bootcount.c b/test/dm/bootcount.c index e0c47b5d7a..b77b472d1f 100644 --- a/test/dm/bootcount.c +++ b/test/dm/bootcount.c @@ -12,12 +12,13 @@ #include #include -static int dm_test_bootcount(struct unit_test_state *uts) +static int dm_test_bootcount_rtc(struct unit_test_state *uts) { struct udevice *dev; 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_get(dev, &val)); ut_assert(val == 0); @@ -36,5 +37,46 @@ static int dm_test_bootcount(struct unit_test_state *uts) 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);