Add a driver to support the i2c host controller (BSC) found in Broadcom's bcm283x family of SoCs (used by rpi boards). Signed-off-by: Daniel Brát <danek.brat@xxxxxxxxx> --- arch/arm/configs/rpi_defconfig | 3 +- arch/arm/configs/rpi_v8a_defconfig | 3 +- drivers/i2c/busses/Kconfig | 4 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-bcm283x.c | 327 +++++++++++++++++++++++++++++ 5 files changed, 336 insertions(+), 2 deletions(-) create mode 100644 drivers/i2c/busses/i2c-bcm283x.c diff --git a/arch/arm/configs/rpi_defconfig b/arch/arm/configs/rpi_defconfig index e0e149748..e7d695f8a 100644 --- a/arch/arm/configs/rpi_defconfig +++ b/arch/arm/configs/rpi_defconfig @@ -24,7 +24,6 @@ CONFIG_BOOTM_OFTREE=y CONFIG_BLSPEC=y CONFIG_CONSOLE_ALLOW_COLOR=y CONFIG_DEFAULT_ENVIRONMENT_GENERIC_NEW=y -CONFIG_DEFAULT_ENVIRONMENT_PATH="" CONFIG_CMD_DMESG=y CONFIG_LONGHELP=y CONFIG_CMD_IOMEM=y @@ -83,6 +82,8 @@ CONFIG_SERIAL_AMBA_PL011=y CONFIG_DRIVER_SERIAL_NS16550=y CONFIG_NET_USB=y CONFIG_NET_USB_SMSC95XX=y +CONFIG_I2C=y +CONFIG_I2C_BCM283X=y CONFIG_USB_HOST=y CONFIG_USB_DWC2_HOST=y CONFIG_MCI=y diff --git a/arch/arm/configs/rpi_v8a_defconfig b/arch/arm/configs/rpi_v8a_defconfig index 68cd2438b..e218311a7 100644 --- a/arch/arm/configs/rpi_v8a_defconfig +++ b/arch/arm/configs/rpi_v8a_defconfig @@ -81,6 +81,8 @@ CONFIG_SERIAL_AMBA_PL011=y CONFIG_DRIVER_SERIAL_NS16550=y CONFIG_NET_USB=y CONFIG_NET_USB_SMSC95XX=y +CONFIG_I2C=y +CONFIG_I2C_BCM283X=y CONFIG_USB_HOST=y CONFIG_USB_DWC2_HOST=y CONFIG_USB_DWC2_GADGET=y @@ -108,4 +110,3 @@ CONFIG_FS_FAT=y CONFIG_FS_FAT_WRITE=y CONFIG_FS_FAT_LFN=y CONFIG_ZLIB=y -CONFIG_LZO_DECOMPRESS=y diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 58f865606..766aa5edf 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -17,6 +17,10 @@ config I2C_AT91 bool "AT91 I2C Master driver" depends on ARCH_AT91 +config I2C_BCM283X + bool "BCM283X I2C Master driver" + depends on ARCH_BCM283X || COMPILE_TEST + config I2C_IMX bool "MPC85xx/MPC5200/i.MX I2C Master driver" depends on ARCH_IMX || ARCH_MPC85XX || ARCH_MPC5200 || ARCH_LAYERSCAPE diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index a8661f605..a1ab46fb2 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_I2C_AT91) += i2c-at91.o +obj-$(CONFIG_I2C_BCM283X) += i2c-bcm283x.o obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o obj-$(CONFIG_I2C_IMX) += i2c-imx.o lwl-$(CONFIG_I2C_IMX_EARLY) += i2c-imx-early.o diff --git a/drivers/i2c/busses/i2c-bcm283x.c b/drivers/i2c/busses/i2c-bcm283x.c new file mode 100644 index 000000000..097f73d98 --- /dev/null +++ b/drivers/i2c/busses/i2c-bcm283x.c @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * I2C bus driver for the BSC peripheral on Broadcom's bcm283x family of SoCs + * + * Based on documentation published by Raspberry Pi foundation and the kernel + * driver written by Stephen Warren. + * + * Copyright (C) Stephen Warren + * Copyright (C) 2022 Daniel Brát + */ + +#include <common.h> +#include <driver.h> +#include <malloc.h> +#include <i2c/i2c.h> +#include <i2c/i2c-algo-bit.h> +#include <linux/iopoll.h> +#include <linux/clk.h> +#include <init.h> +#include <of_address.h> + +// BSC C (Control) register +#define BSC_C_READ BIT(0) +#define BSC_C_CLEAR1 BIT(4) +#define BSC_C_CLEAR2 BIT(5) +#define BSC_C_ST BIT(7) +#define BSC_C_INTD BIT(8) +#define BSC_C_INTT BIT(9) +#define BSC_C_INTR BIT(10) +#define BSC_C_I2CEN BIT(15) + +// BSC S (Status) register +#define BSC_S_TA BIT(0) +#define BSC_S_DONE BIT(1) +#define BSC_S_TXW BIT(2) +#define BSC_S_RXR BIT(3) +#define BSC_S_TXD BIT(4) +#define BSC_S_RXD BIT(5) +#define BSC_S_TXE BIT(6) +#define BSC_S_RXF BIT(7) +#define BSC_S_ERR BIT(8) +#define BSC_S_CLKT BIT(9) + +// BSC A (Address) register +#define BSC_A_MASK 0x7f + +// Constants +#define BSC_CDIV_MIN 0x0002 +#define BSC_CDIV_MAX 0xfffe +#define BSC_FIFO_SIZE 16U + +struct __packed bcm283x_i2c_regs { + u32 c; + u32 s; + u32 dlen; + u32 a; + u32 fifo; + u32 div; + u32 del; + u32 clkt; +}; + +struct bcm283x_i2c { + struct i2c_adapter adapter; + struct clk *mclk; + struct bcm283x_i2c_regs __iomem *regs; + u32 bitrate; +}; + +static inline struct bcm283x_i2c *to_bcm283x_i2c(struct i2c_adapter *adapter) +{ + return container_of(adapter, struct bcm283x_i2c, adapter); +} + +static inline int bcm283x_i2c_init(struct bcm283x_i2c *bcm_i2c) +{ + struct device_d *dev = &bcm_i2c->adapter.dev; + u32 mclk_rate, cdiv, redl, fedl; + + /* + * Reset control reg, flush FIFO, clear all flags and disable + * clock stretching + */ + writel(0UL, &bcm_i2c->regs->c); + writel(BSC_C_CLEAR1, &bcm_i2c->regs->c); + writel(BSC_S_DONE | BSC_S_ERR | BSC_S_CLKT, &bcm_i2c->regs->s); + writel(0UL, &bcm_i2c->regs->clkt); + + /* + * Set the divider based on the master clock frequency and the + * requested i2c bitrate + */ + mclk_rate = clk_get_rate(bcm_i2c->mclk); + cdiv = DIV_ROUND_UP(mclk_rate, bcm_i2c->bitrate); + dev_dbg(dev, "bcm283x_i2c_init: mclk_rate=%u, cdiv=%08x\n", + mclk_rate, cdiv); + /* Note from kernel driver: + * Per the datasheet, the register is always interpreted as an even + * number, by rounding down. In other words, the LSB is ignored. So, + * if the LSB is set, increment the divider to avoid any issue. + */ + if (cdiv & 1) + cdiv++; + if ((cdiv < BSC_CDIV_MIN) || (cdiv > BSC_CDIV_MAX)) { + dev_err(dev, "failed to calculate valid clock divider value\n"); + return -EINVAL; + } + dev_dbg(dev, "bcm283x_i2c_init: cdiv adjusted to %04x\n", cdiv); + fedl = max(cdiv / 16, 1U); + redl = max(cdiv / 4, 1U); + dev_dbg(dev, "bcm283x_i2c_init: fedl=%04x, redl=%04x\n", fedl, redl); + writel(cdiv & 0xffff, &bcm_i2c->regs->div); + writel((fedl << 16) | redl, &bcm_i2c->regs->del); + dev_dbg(dev, "bcm283x_i2c_init: regs->div=%08x, regs->del=%08x\n", + readl(&bcm_i2c->regs->div), readl(&bcm_i2c->regs->del)); + + return 0; +} + +/* + * Macro to calculate generous timeout for given bitrate and number of bytes + */ +#define calc_byte_timeout_us(bitrate) \ + (3 * 9 * DIV_ROUND_UP(1000000, bitrate)) +#define calc_msg_timeout_us(bitrate, bytes) \ + ((bytes + 1) * calc_byte_timeout_us(bitrate)) + +static int bcm283x_i2c_msg_xfer(struct bcm283x_i2c *bcm_i2c, + struct i2c_msg *msg) +{ + int ret; + u32 reg_c, reg_s, reg_dlen, timeout; + struct device_d *dev = &bcm_i2c->adapter.dev; + bool msg_read = (msg->flags & I2C_M_RD) > 0; + bool msg_10bit = (msg->flags & I2C_M_TEN) > 0; + u16 buf_pos = 0; + u32 bytes_left = reg_dlen = msg->len; + + if (msg_10bit && msg_read) { + timeout = calc_byte_timeout_us(bcm_i2c->bitrate); + writel(1UL, &bcm_i2c->regs->dlen); + writel(msg->addr & 0xff, &bcm_i2c->regs->fifo); + writel(((msg->addr >> 8) | 0x78) & BSC_A_MASK, &bcm_i2c->regs->a); + writel(BSC_C_ST | BSC_C_I2CEN, &bcm_i2c->regs->c); + ret = readl_poll_timeout(&bcm_i2c->regs->s, reg_s, + reg_s & (BSC_S_TA | BSC_S_ERR), timeout); + + if (ret) { + dev_err(dev, "timeout: 10bit read initilization\n"); + return ret; + } + if (reg_s & BSC_S_ERR) + goto nack; + + } else if (msg_10bit) { + reg_dlen++; + writel(msg->addr & 0xff, &bcm_i2c->regs->fifo); + writel(((msg->addr >> 8) | 0x78) & BSC_A_MASK, &bcm_i2c->regs->a); + } else { + writel(msg->addr & BSC_A_MASK, &bcm_i2c->regs->a); + } + + writel(reg_dlen, &bcm_i2c->regs->dlen); + reg_c = BSC_C_ST | BSC_C_I2CEN; + if (msg_read) + reg_c |= BSC_C_READ; + writel(reg_c, &bcm_i2c->regs->c); + + if (msg_read) { + /* + * Read out data from FIFO as soon as it is available + */ + timeout = calc_byte_timeout_us(bcm_i2c->bitrate); + for (; bytes_left; bytes_left--) { + ret = readl_poll_timeout(&bcm_i2c->regs->s, reg_s, + reg_s & (BSC_S_RXD | BSC_S_ERR), + timeout); + + if (ret) { + dev_err(dev, "timeout: waiting for data in FIFO\n"); + return ret; + } + if (reg_s & BSC_S_ERR) + goto nack; + + msg->buf[buf_pos++] = (u8) readl(&bcm_i2c->regs->fifo); + } + } else { + timeout = calc_byte_timeout_us(bcm_i2c->bitrate); + /* + * Feed data to FIFO as soon as there is space for them + */ + for (; bytes_left; bytes_left--) { + ret = readl_poll_timeout(&bcm_i2c->regs->s, reg_s, + reg_s & (BSC_S_TXD | BSC_S_ERR), + timeout); + + if (ret) { + dev_err(dev, "timeout: waiting for space in FIFO\n"); + return ret; + } + if (reg_s & BSC_S_ERR) + goto nack; + + writel(msg->buf[buf_pos++], &bcm_i2c->regs->fifo); + } + } + + /* + * Wait for the current transfer to finish and then flush FIFO + * and clear any flags so that we are ready for next msg + */ + timeout = calc_msg_timeout_us(bcm_i2c->bitrate, reg_dlen); + ret = readl_poll_timeout(&bcm_i2c->regs->s, reg_s, + reg_s & (BSC_S_DONE | BSC_S_ERR), timeout); + + if (ret) { + dev_err(dev, "timeout: waiting for transfer to end\n"); + return ret; + } + if (reg_s & BSC_S_ERR) + goto nack; + writel(BSC_S_DONE | BSC_S_ERR | BSC_S_CLKT, &bcm_i2c->regs->s); + writel(BSC_C_CLEAR1 | BSC_C_I2CEN, &bcm_i2c->regs->c); + return 0; +nack: + dev_dbg(dev, "device with addr %x didn't ACK\n", msg->addr); + return -EREMOTEIO; +} + +static int bcm283x_i2c_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, int count) +{ + int ret, i; + struct i2c_msg *msg; + struct bcm283x_i2c *bcm_i2c = to_bcm283x_i2c(adapter); + + /* + * Reset control reg, flush FIFO, clear flags and enable the BSC + */ + writel(0UL, &bcm_i2c->regs->c); + writel(BSC_C_CLEAR1, &bcm_i2c->regs->c); + writel(BSC_S_DONE | BSC_S_ERR | BSC_S_CLKT, &bcm_i2c->regs->s); + writel(BSC_C_I2CEN, &bcm_i2c->regs->c); + + for (i = 0; i < count; i++) { + msg = &msgs[i]; + ret = bcm283x_i2c_msg_xfer(bcm_i2c, msg); + if (ret) + goto out; + } + + writel(0UL, &bcm_i2c->regs->c); + return count; +out: + writel(0UL, &bcm_i2c->regs->c); + writel(BSC_C_CLEAR1, &bcm_i2c->regs->c); + writel(BSC_S_DONE | BSC_S_ERR | BSC_S_CLKT, &bcm_i2c->regs->s); + return ret; +} + +static int bcm283x_i2c_probe(struct device_d *dev) +{ + int ret; + struct resource *iores; + struct bcm283x_i2c *bcm_i2c; + struct device_node *np = dev->device_node; + + bcm_i2c = xzalloc(sizeof(*bcm_i2c)); + + if (!np) { + ret = -ENXIO; + goto err; + } + + iores = dev_request_mem_resource(dev, 0); + if (IS_ERR(iores)) { + dev_err(dev, "could not get iomem region\n"); + ret = PTR_ERR(iores); + goto err; + } + bcm_i2c->regs = IOMEM(iores->start); + + bcm_i2c->mclk = clk_get(dev, NULL); + if (IS_ERR(bcm_i2c->mclk)) { + dev_err(dev, "could not acquire clock\n"); + ret = PTR_ERR(bcm_i2c->mclk); + goto err; + } + clk_enable(bcm_i2c->mclk); + + bcm_i2c->bitrate = I2C_MAX_STANDARD_MODE_FREQ; + of_property_read_u32(np, "clock-frequency", &bcm_i2c->bitrate); + if (bcm_i2c->bitrate > I2C_MAX_FAST_MODE_FREQ) { + dev_err(dev, "clock frequency of %u is not supported\n", + bcm_i2c->bitrate); + ret = -EINVAL; + goto err; + } + + bcm_i2c->adapter.master_xfer = bcm283x_i2c_xfer; + bcm_i2c->adapter.nr = dev->id; + bcm_i2c->adapter.dev.parent = dev; + bcm_i2c->adapter.dev.device_node = np; + + ret = bcm283x_i2c_init(bcm_i2c); + if (ret) + goto err; + + return i2c_add_numbered_adapter(&bcm_i2c->adapter); +err: + free(bcm_i2c); + return ret; +} + +static struct of_device_id bcm283x_i2c_dt_ids[] = { + { .compatible = "brcm,bcm2835-i2c", }, + { .compatible = "brcm,bcm2711-i2c", }, + { /* sentinel */ } +}; + +static struct driver_d bcm283x_i2c_driver = { + .name = "i2c-bcm283x", + .probe = bcm283x_i2c_probe, + .of_compatible = DRV_OF_COMPAT(bcm283x_i2c_dt_ids), +}; +device_platform_driver(bcm283x_i2c_driver); -- 2.17.1