[PATCH] i2c: add bcm283x i2c host controller support

Sascha Hauer sha at pengutronix.de
Tue Jun 14 02:41:47 PDT 2022


On Sat, Jun 11, 2022 at 11:28:42PM +0200, Daniel Brát wrote:
> Add a driver to support the i2c host controller (BSC)
> found on bcm283x family os SoCs.
> 
> Signed-off-by: Daniel Brát <danek.brat at 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

Please drop these lines. They are wrong after all. DEBUG has to be
defined before the includes and LOGLEVEL is unused.

> +
> +// 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)

Please make this a static inline function for better type safety. Also
it makes it obvious which type 'a' must have.

> +
> +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++) {

You could move the body of this loop into an extra function. It saves
you one indentation level and thus gives you less linebreaks and better
readability.

> +		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");

s/initilization/initialization/

> +				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);

This driver is far too chatty in debug mode for my taste. Could you
remove some of the messages? This particular one doesn't give any
additional information for example.

> +
> +		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);

Ditto. All information has been printed directly or indirectly before.

> +			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);
> +			}

Filling up the FIFO upfront looks like adding code for no gain. I think
you can drop this and only use the loop below.

> +			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;
> +	}

That shouldn't be necessary. You already tested that you have a
device_node pointer which you will only get when CONFIG_OFDEVICE
is enabled.

> +
> +	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);

Unncecessary, you can view the register in the output of the 'iomem'
command.

> +
> +	bcm_i2c->mclk = clk_get(dev, NULL);
> +	if (IS_ERR(bcm_i2c->mclk)) {
> +		dev_err(dev, "could not aquire clock\n");

s/aquire/acquire/

> +		ret = PTR_ERR(bcm_i2c->mclk);
> +		goto err;
> +	}
> +	clk_enable(bcm_i2c->mclk);
> +	dev_dbg(dev, "bcm283x_i2c_probe: enabled mclk\n");

Also unnecessary. The next message will tell you that you've been here
as well.

Sascha

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |



More information about the barebox mailing list