Add a driver to support the i2c host controller (BSC) found on bcm283x family os SoCs. Signed-off-by: Daniel Brát <danek.brat@xxxxxxxxx> --- arch/arm/configs/rpi_v8a_defconfig | 3 +- drivers/i2c/busses/Kconfig | 4 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-bcm283x.c | 372 +++++++++++++++++++++++++++++ 4 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 drivers/i2c/busses/i2c-bcm283x.c 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..429f5ba42 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 + 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..dc9a2ea11 --- /dev/null +++ b/drivers/i2c/busses/i2c-bcm283x.c @@ -0,0 +1,372 @@ +// 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> + +//#define DEBUG +//#define LOGLEVEL 7 + +// 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; +}; + +#define to_bcm283x_i2c(a) container_of(a, 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_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, int count) +{ + int ret, i; + bool msg_read, msg_10bit; + struct i2c_msg *msg; + u16 buf_pos; + u32 reg_c, reg_s, reg_dlen, bytes_left, timeout, bulk_written; + struct device_d *dev = &adapter->dev; + struct bcm283x_i2c *bcm_i2c = to_bcm283x_i2c(adapter); + dev_dbg(dev, "bcm283x_i2c_xfer: count=%d\n", count); + + /* + * 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]; + msg_read = (msg->flags & I2C_M_RD) > 0; + msg_10bit = (msg->flags & I2C_M_TEN) > 0; + reg_dlen = bytes_left = msg->len; + buf_pos = 0; + dev_dbg(dev, "bcm283x_i2c_xfer: msg #%d: " + "msg_read=%d, msg_10bit=%d, " + "reg_dlen=%u, buf_pos=%u\n", + i, msg_read, msg_10bit, reg_dlen, buf_pos); + + 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 addr " + "read initilization\n"); + goto out; + } + if (reg_s & BSC_S_ERR) { + dev_dbg(dev, "device with addr %x didnt ACK\n", + msg->addr); + ret = -EREMOTEIO; + goto out; + } + } 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); + dev_dbg(dev, "bcm283x_i2c_xfer: msg #%d: reg_c=%08x\n", + i, reg_c); + + if (msg_read) { + /* + * Read out data from FIFO as soon as it is available + */ + timeout = calc_byte_timeout_us(bcm_i2c->bitrate); + dev_dbg(dev, "bcm283x_i2c_xfer: msg #%d: " + "reading data from FIFO, timeout=%u, " + "bytes_left=%u\n", + i, timeout, bytes_left); + 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"); + goto out; + } + if (reg_s & BSC_S_ERR) { + ret = -EREMOTEIO; + goto out; + } + msg->buf[buf_pos++] = + (u8) readl(&bcm_i2c->regs->fifo); + dev_dbg(dev, "read %02x from FIFO", + msg->buf[buf_pos-1]); + } + } else { + /* + * Fit as much data to the FIFO as we can in one go + */ + for (bulk_written = min(bytes_left, (msg_10bit ? + (BSC_FIFO_SIZE - 1U) : BSC_FIFO_SIZE)); + bulk_written; bulk_written--, bytes_left--) + { + dev_dbg(dev, "writing %02x to FIFO\n", + msg->buf[buf_pos]); + writel(msg->buf[buf_pos++], + &bcm_i2c->regs->fifo); + } + timeout = calc_byte_timeout_us(bcm_i2c->bitrate); + /* + * Feed any remaining data to FIFO as soon as there + * is space for them + */ + dev_dbg(dev, "remaining bytes after bulk write: %u\n", + bytes_left); + 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"); + goto out; + } + if (reg_s & BSC_S_ERR) { + dev_dbg(dev, "device with addr %x " + "didnt ACK\n", msg->addr); + ret = -EREMOTEIO; + goto out; + } + dev_dbg(dev, "writing %02x to FIFO\n", + msg->buf[buf_pos]); + 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"); + goto out; + } + if (reg_s & BSC_S_ERR) { + dev_dbg(dev, "device with addr %x didnt ACK\n", + msg->addr); + ret = -EREMOTEIO; + goto out; + } + 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); + } + writel(0UL, &bcm_i2c->regs->c); + dev_dbg(dev, "bcm283x_i2c_xfer: returning successfully\n"); + 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); + dev_dbg(dev, "bcm283x_i2c_xfer: returning via err, ret: %d\n", ret); + 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; + } + + if (!IS_ENABLED(CONFIG_OFDEVICE)) { + ret = -ENODEV; + 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); + dev_dbg(dev, "bcm283x_i2c_probe: regs at %p\n", bcm_i2c->regs); + + bcm_i2c->mclk = clk_get(dev, NULL); + if (IS_ERR(bcm_i2c->mclk)) { + dev_err(dev, "could not aquire clock\n"); + ret = PTR_ERR(bcm_i2c->mclk); + goto err; + } + clk_enable(bcm_i2c->mclk); + dev_dbg(dev, "bcm283x_i2c_probe: enabled mclk\n"); + + 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; + } + dev_dbg(dev, "bcm283x_i2c_probe: i2c_bitrate: %u\n", bcm_i2c->bitrate); + + 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); + dev_dbg(dev, "bcm283x_i2c_probe: exiting via err, ret: %d\n", ret); + 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 _______________________________________________ barebox mailing list barebox@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/barebox