mail archive of the barebox mailing list
 help / color / mirror / Atom feed
From: "Daniel Brát" <danek.brat@gmail.com>
To: barebox@lists.infradead.org
Cc: "Daniel Brát" <danek.brat@gmail.com>
Subject: [PATCH] i2c: add bcm283x i2c host controller support
Date: Sat, 11 Jun 2022 23:28:42 +0200	[thread overview]
Message-ID: <20220611212842.6413-1-danek.brat@gmail.com> (raw)

Add a driver to support the i2c host controller (BSC)
found on bcm283x family os SoCs.

Signed-off-by: Daniel Brát <danek.brat@gmail.com>
---
 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@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

             reply	other threads:[~2022-06-11 21:30 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-06-11 21:28 Daniel Brát [this message]
2022-06-14  9:41 ` Sascha Hauer
2022-06-14  9:52 ` Ahmad Fatoum

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220611212842.6413-1-danek.brat@gmail.com \
    --to=danek.brat@gmail.com \
    --cc=barebox@lists.infradead.org \
    --subject='Re: [PATCH] i2c: add bcm283x i2c host controller support' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox