diff options
author | Tom Rini <trini@konsulko.com> | 2021-08-23 09:17:07 -0400 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2021-08-23 09:17:07 -0400 |
commit | 926fe46a6d9d3def405440227b0f77ed98ee2f9d (patch) | |
tree | 760829c8df31cdebe7178a52e25935a2e2bd05ee /drivers | |
parent | 3ee343cd7cc1eaa4e0b905ab3d8d0c764985d264 (diff) | |
parent | c50b21b70523939c561d0455a2c423f63a9162ca (diff) | |
download | u-boot-926fe46a6d9d3def405440227b0f77ed98ee2f9d.zip u-boot-926fe46a6d9d3def405440227b0f77ed98ee2f9d.tar.gz u-boot-926fe46a6d9d3def405440227b0f77ed98ee2f9d.tar.bz2 |
Merge tag 'for-v2021.10-rc3' of https://source.denx.de/u-boot/custodians/u-boot-i2c
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
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/bootcount/Kconfig | 12 | ||||
-rw-r--r-- | drivers/bootcount/Makefile | 1 | ||||
-rw-r--r-- | drivers/bootcount/bootcount_syscon.c | 159 | ||||
-rw-r--r-- | drivers/gpio/Kconfig | 10 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpio/mcp230xx_gpio.c | 235 | ||||
-rw-r--r-- | drivers/i2c/i2c-gpio.c | 9 | ||||
-rw-r--r-- | drivers/i2c/i2c-uclass.c | 15 |
8 files changed, 442 insertions, 0 deletions
diff --git a/drivers/bootcount/Kconfig b/drivers/bootcount/Kconfig index 0de2b7b..607027c 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 12658ff..3a784bb 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 0000000..413fd5b --- /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 <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, +}; diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 09695f6..4a89c1a 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 16b09fb..58f4704 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 0000000..9f02fd4 --- /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 <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, +}; diff --git a/drivers/i2c/i2c-gpio.c b/drivers/i2c/i2c-gpio.c index cf8f8f4..1aedad5 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; diff --git a/drivers/i2c/i2c-uclass.c b/drivers/i2c/i2c-uclass.c index 04c8850..db1c9d9 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 * |