[PATCH v2] i2c: add bcm283x i2c host controller support

Daniel Brát danek.brat at gmail.com
Wed Jun 15 13:39:09 PDT 2022


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 at gmail.com>
---
 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




More information about the barebox mailing list