i2c changes for for-v2021.10-rc3

new driver:
- Introduce mcp230xx support
  from Sebastian Reichel

new feature:
- i2c-gpio: add support for "sda-gpios" + "scl-gpios" i2c-gpio bindings.
  from Samuel Holland

- bootcount: add a new driver with syscon as backend
  from Nandor Han
This commit is contained in:
Tom Rini 2021-08-23 09:17:07 -04:00
commit 926fe46a6d
14 changed files with 569 additions and 3 deletions

View file

@ -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>;

View file

@ -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

View 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";
};

View file

@ -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

View file

@ -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

View 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, &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,
};

View file

@ -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

View file

@ -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

View file

@ -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 <sebastian.reichel@collabora.com>
*/
#define LOG_CATEGORY UCLASS_GPIO
#include <common.h>
#include <errno.h>
#include <dm.h>
#include <i2c.h>
#include <asm/gpio.h>
#include <dm/device_compat.h>
#include <dt-bindings/gpio/gpio.h>
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,
};

View file

@ -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;

View file

@ -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
*

View file

@ -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
*

View file

@ -12,12 +12,13 @@
#include <test/test.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;
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);

View file

@ -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);