Mainline U-Boot currently lacks an I2C controller driver for the
Broadcom BCM2711 SoC (Raspberry Pi 4).

This commit adds a new DM-compliant I2C driver for Broadcom
BCM2835/BCM2711 SoCs based on the Linux kernel's i2c-bcm2835.c.
(based on Linux commit 32a0a94aa09e ("i2c: Switch back to struct 
platform_driver::remove()")).

The driver dynamically queries the actual VPU core clock rate from
the VideoCore firmware via mailbox property messages to calculate
the CDIV clock divider, ensuring accurate I2C timing and baud rates
on BCM2711.

Enable CONFIG_SYS_I2C_BCM2835 in rpi_4_defconfig.

Reviewed-by: Heiko Schocher <[email protected]>
Signed-off-by: Giau Nguyen <[email protected]>
---
v3:
 - Added the Linux commit ID to the commit message.
 - Fixed alphabetical sorting of CONFIG_SYS_I2C_BCM2835 in drivers/i2c/Makefile.
 - Added Reviewed-by tag from Heiko.

v2:
 - Clarified commit message: U-Boot mainline currently lacks BCM2711 I2C driver.
 - Fixed line endings and code formatting per checkpatch.pl.
 - Converted register offsets to 'struct bcm2835_i2c_regs' abstraction.
 - Removed 'bcm2835_i2c_readl/writel' wrappers in favor of standard 
readl/writel.
 - Moved mailbox clock querying to bcm2835_get_clock_rate() in mach-bcm283x.
 - Enabled CONFIG_SYS_I2C_BCM2835 in rpi_4_defconfig.
 - Sorted drivers/i2c/Kconfig and Makefile alphabetically.
 - Removed 'Assisted by AI' tag.

 MAINTAINERS                              |   1 +
 arch/arm/mach-bcm283x/include/mach/msg.h |   9 +
 arch/arm/mach-bcm283x/msg.c              |  22 ++
 configs/rpi_4_defconfig                  |   4 +
 drivers/i2c/Kconfig                      |  10 +
 drivers/i2c/Makefile                     |   1 +
 drivers/i2c/i2c-bcm2835.c                | 370 +++++++++++++++++++++++
 7 files changed, 417 insertions(+)
 create mode 100644 drivers/i2c/i2c-bcm2835.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 8c6c0c2a4bc..3260024a736 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -215,6 +215,7 @@ F:  arch/arm/dts/bcm283*
 F:     arch/arm/mach-bcm283x/
 F:     board/raspberrypi/
 F:     drivers/gpio/bcm2835_gpio.c
+F:     drivers/i2c/i2c-bcm2835.c
 F:     drivers/mmc/bcm2835_sdhci.c
 F:     drivers/mmc/bcm2835_sdhost.c
 F:     drivers/serial/serial_bcm283x_mu.c
diff --git a/arch/arm/mach-bcm283x/include/mach/msg.h 
b/arch/arm/mach-bcm283x/include/mach/msg.h
index e54da86e353..d6adec07ae2 100644
--- a/arch/arm/mach-bcm283x/include/mach/msg.h
+++ b/arch/arm/mach-bcm283x/include/mach/msg.h
@@ -22,6 +22,15 @@ int bcm2835_power_on_module(u32 module);
  */
 int bcm2835_get_mmc_clock(u32 clock_id);
 
+/**
+ * bcm2835_get_clock_rate() - get the current rate of a clock
+ *
+ * @clock_id: ID of clock to get frequency for (BCM2835_MBOX_CLOCK_ID_...)
+ * @rate: Returns the clock rate in Hz
+ * Return: 0 if OK, -EIO or -EINVAL on error
+ */
+int bcm2835_get_clock_rate(u32 clock_id, ulong *rate);
+
 /**
  * bcm2835_set_sdhost_clock() - determine if firmware controls sdhost cdiv
  *
diff --git a/arch/arm/mach-bcm283x/msg.c b/arch/arm/mach-bcm283x/msg.c
index 4993c0bdb81..74f847c5e54 100644
--- a/arch/arm/mach-bcm283x/msg.c
+++ b/arch/arm/mach-bcm283x/msg.c
@@ -115,6 +115,28 @@ int bcm2835_get_mmc_clock(u32 clock_id)
        return clock_rate;
 }
 
+int bcm2835_get_clock_rate(u32 clock_id, ulong *rate)
+{
+       ALLOC_CACHE_ALIGN_BUFFER(struct msg_get_clock_rate, msg_clk, 1);
+       int ret;
+
+       BCM2835_MBOX_INIT_HDR(msg_clk);
+       BCM2835_MBOX_INIT_TAG(&msg_clk->get_clock_rate, GET_CLOCK_RATE);
+       msg_clk->get_clock_rate.body.req.clock_id = clock_id;
+
+       ret = bcm2835_mbox_call_prop(BCM2835_MBOX_PROP_CHAN, &msg_clk->hdr);
+       if (ret) {
+               printf("bcm2835: Could not query clock rate for %u\n", 
clock_id);
+               return -EIO;
+       }
+
+       *rate = msg_clk->get_clock_rate.body.resp.rate_hz;
+       if (!*rate)
+               return -EINVAL;
+
+       return 0;
+}
+
 int bcm2835_set_sdhost_clock(u32 rate_hz, u32 *rate_1, u32 *rate_2)
 {
        ALLOC_CACHE_ALIGN_BUFFER(struct msg_set_sdhost_clock, msg_sdhost_clk, 
1);
diff --git a/configs/rpi_4_defconfig b/configs/rpi_4_defconfig
index f5fb322aa8f..2ddaf003558 100644
--- a/configs/rpi_4_defconfig
+++ b/configs/rpi_4_defconfig
@@ -22,6 +22,7 @@ CONFIG_MISC_INIT_R=y
 CONFIG_SYS_PROMPT="U-Boot> "
 CONFIG_CMD_DFU=y
 CONFIG_CMD_GPIO=y
+CONFIG_CMD_I2C=y
 CONFIG_CMD_MMC=y
 CONFIG_CMD_PCI=y
 CONFIG_CMD_USB=y
@@ -34,6 +35,9 @@ CONFIG_DM_DMA=y
 CONFIG_DFU_MMC=y
 CONFIG_SYS_DFU_DATA_BUF_SIZE=0x100000
 CONFIG_SYS_DFU_MAX_FILE_SIZE=0x200000
+CONFIG_I2C=y
+CONFIG_DM_I2C=y
+CONFIG_SYS_I2C_BCM2835=y
 CONFIG_BCM2835_GPIO=y
 CONFIG_MMC_SDHCI=y
 CONFIG_MMC_SDHCI_SDMA=y
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
index 52067fa7c1f..1b26906e403 100644
--- a/drivers/i2c/Kconfig
+++ b/drivers/i2c/Kconfig
@@ -164,6 +164,16 @@ config SYS_I2C_AT91
          i2c-gpio driver unless your system can cope with this limitation.
          Binding info: doc/device-tree-bindings/i2c/i2c-at91.txt
 
+config SYS_I2C_BCM2835
+       bool "BCM2835 I2C controller"
+       depends on DM_I2C
+       help
+         Enable support for the Broadcom BCM2835/BCM2711 I2C controller.
+         This driver provides standard I2C support for Raspberry Pi
+         devices, including the newer Raspberry Pi 4. It dynamically
+         calculates the clock divider based on the VPU core clock
+         queried via the firmware mailbox.
+
 config SYS_I2C_IPROC
        bool "Broadcom I2C driver"
        depends on DM_I2C
diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
index bebd728e7da..aac6ce4b6e0 100644
--- a/drivers/i2c/Makefile
+++ b/drivers/i2c/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_$(XPL_)SYS_I2C_LEGACY) += i2c_core.o
 obj-$(CONFIG_SYS_I2C_ASPEED) += ast_i2c.o
 obj-$(CONFIG_SYS_I2C_AST2600) += ast2600_i2c.o
 obj-$(CONFIG_SYS_I2C_AT91) += at91_i2c.o
+obj-$(CONFIG_SYS_I2C_BCM2835) += i2c-bcm2835.o
 obj-$(CONFIG_SYS_I2C_CADENCE) += i2c-cdns.o
 obj-$(CONFIG_SYS_I2C_CA) += i2c-cortina.o
 obj-$(CONFIG_SYS_I2C_DAVINCI) += davinci_i2c.o
diff --git a/drivers/i2c/i2c-bcm2835.c b/drivers/i2c/i2c-bcm2835.c
new file mode 100644
index 00000000000..e6abb076bdb
--- /dev/null
+++ b/drivers/i2c/i2c-bcm2835.c
@@ -0,0 +1,370 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BCM2835 I2C controller driver
+ * (C) Copyright 2026
+ *
+ * Author: Giau Nguyen <[email protected]>
+ * Based on Linux driver: drivers/i2c/busses/i2c-bcm2835.c
+ */
+
+#include <log.h>
+#include <asm/io.h>
+#include <asm/arch/mbox.h>
+#include <asm/arch/msg.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <i2c.h>
+#include <time.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+
+#define BCM2835_I2C_C_READ     BIT(0)
+#define BCM2835_I2C_C_CLEAR    BIT(4) /* bits 4 and 5 both clear */
+#define BCM2835_I2C_C_ST       BIT(7)
+#define BCM2835_I2C_C_INTD     BIT(8)
+#define BCM2835_I2C_C_INTT     BIT(9)
+#define BCM2835_I2C_C_INTR     BIT(10)
+#define BCM2835_I2C_C_I2CEN    BIT(15)
+
+#define BCM2835_I2C_S_TA       BIT(0)
+#define BCM2835_I2C_S_DONE     BIT(1)
+#define BCM2835_I2C_S_TXW      BIT(2)
+#define BCM2835_I2C_S_RXR      BIT(3)
+#define BCM2835_I2C_S_TXD      BIT(4)
+#define BCM2835_I2C_S_RXD      BIT(5)
+#define BCM2835_I2C_S_TXE      BIT(6)
+#define BCM2835_I2C_S_RXF      BIT(7)
+#define BCM2835_I2C_S_ERR      BIT(8)
+#define BCM2835_I2C_S_CLKT     BIT(9)
+#define BCM2835_I2C_S_LEN      BIT(10) /* Fake bit for SW error reporting */
+
+#define BCM2835_I2C_FEDL_SHIFT 16
+#define BCM2835_I2C_REDL_SHIFT 0
+
+#define BCM2835_I2C_CDIV_MIN   0x0002
+#define BCM2835_I2C_CDIV_MAX   0xFFFE
+
+#define BCM2835_I2C_CLKT_TOUT_MS 35
+#define BCM2835_I2C_XFER_TIMEOUT_MS 1000
+
+struct bcm2835_i2c_regs {
+       u32 c;
+       u32 s;
+       u32 dlen;
+       u32 a;
+       u32 fifo;
+       u32 div;
+       u32 del;
+       u32 clkt;
+};
+
+struct bcm2835_i2c_priv {
+       struct bcm2835_i2c_regs __iomem *regs;
+       ulong input_rate;
+       size_t msg_buf_remaining;
+       u8 *msg_buf;
+       struct i2c_msg *curr_msg;
+       int msg_err;
+       int num_msgs;
+};
+
+static void bcm2835_fill_txfifo(struct bcm2835_i2c_priv *i2c_dev)
+{
+       struct bcm2835_i2c_regs __iomem *regs = i2c_dev->regs;
+
+       while (i2c_dev->msg_buf_remaining) {
+               if (!(readl(&regs->s) & BCM2835_I2C_S_TXD))
+                       break;
+
+               writel(*i2c_dev->msg_buf, &regs->fifo);
+               i2c_dev->msg_buf++;
+               i2c_dev->msg_buf_remaining--;
+       }
+}
+
+static void bcm2835_drain_rxfifo(struct bcm2835_i2c_priv *i2c_dev)
+{
+       struct bcm2835_i2c_regs __iomem *regs = i2c_dev->regs;
+
+       while (i2c_dev->msg_buf_remaining) {
+               if (!(readl(&regs->s) & BCM2835_I2C_S_RXD))
+                       break;
+
+               *i2c_dev->msg_buf = readl(&regs->fifo);
+               i2c_dev->msg_buf++;
+               i2c_dev->msg_buf_remaining--;
+       }
+}
+
+static int bcm2835_i2c_calc_divider(unsigned long rate,
+                                   unsigned long parent_rate)
+{
+       int divider = DIV_ROUND_UP(parent_rate, rate);
+
+       /*
+        * 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 (divider & 1)
+               divider++;
+
+       if (divider < BCM2835_I2C_CDIV_MIN ||
+           divider > BCM2835_I2C_CDIV_MAX)
+               return -EINVAL;
+
+       return divider;
+}
+
+static int bcm2835_i2c_set_rate(struct udevice *bus, unsigned int rate)
+{
+       struct bcm2835_i2c_priv *i2c_dev = dev_get_priv(bus);
+       struct bcm2835_i2c_regs __iomem *regs = i2c_dev->regs;
+       ulong parent_rate = i2c_dev->input_rate;
+       u32 redl, fedl;
+       u32 clk_tout;
+       int divider;
+
+       if (!rate || !parent_rate)
+               return -EINVAL;
+
+       divider = bcm2835_i2c_calc_divider(rate, parent_rate);
+       if (divider < 0)
+               return divider;
+
+       writel(divider, &regs->div);
+
+       /*
+        * Number of core clocks to wait after falling edge before
+        * outputting the next data bit. Note that both FEDL and REDL
+        * can't be greater than CDIV/2.
+        */
+       fedl = max_t(u32, divider / 16, 1);
+
+       /*
+        * Number of core clocks to wait after rising edge before
+        * sampling the next incoming data bit.
+        */
+       redl = max_t(u32, divider / 4, 1);
+
+       writel((fedl << BCM2835_I2C_FEDL_SHIFT) |
+              (redl << BCM2835_I2C_REDL_SHIFT), &regs->del);
+
+       /*
+        * Set the clock stretch timeout.
+        */
+       if (rate > 0xffff * 1000 / BCM2835_I2C_CLKT_TOUT_MS)
+               clk_tout = 0xffff;
+       else
+               clk_tout = BCM2835_I2C_CLKT_TOUT_MS * rate / 1000;
+
+       writel(clk_tout, &regs->clkt);
+
+       return 0;
+}
+
+static void bcm2835_i2c_start_transfer(struct bcm2835_i2c_priv *i2c_dev)
+{
+       struct bcm2835_i2c_regs __iomem *regs = i2c_dev->regs;
+       u32 c = BCM2835_I2C_C_ST | BCM2835_I2C_C_I2CEN;
+       struct i2c_msg *msg = i2c_dev->curr_msg;
+       bool last_msg = (i2c_dev->num_msgs == 1);
+
+       if (!i2c_dev->num_msgs)
+               return;
+
+       i2c_dev->num_msgs--;
+       i2c_dev->msg_buf = msg->buf;
+       i2c_dev->msg_buf_remaining = msg->len;
+
+       if (msg->flags & I2C_M_RD)
+               c |= BCM2835_I2C_C_READ;
+       else
+               c |= BCM2835_I2C_C_INTT;
+
+       if (last_msg)
+               c |= BCM2835_I2C_C_INTD;
+
+       writel(msg->addr, &regs->a);
+       writel(msg->len, &regs->dlen);
+       writel(c, &regs->c);
+}
+
+static void bcm2835_i2c_finish_transfer(struct bcm2835_i2c_priv *i2c_dev)
+{
+       i2c_dev->curr_msg = NULL;
+       i2c_dev->num_msgs = 0;
+       i2c_dev->msg_buf = NULL;
+       i2c_dev->msg_buf_remaining = 0;
+}
+
+static int bcm2835_i2c_xfer(struct udevice *dev, struct i2c_msg *msgs, int 
count)
+{
+       struct bcm2835_i2c_priv *i2c_dev = dev_get_priv(dev);
+       struct bcm2835_i2c_regs __iomem *regs = i2c_dev->regs;
+       bool ignore_nak = false;
+       bool done = false;
+       ulong start;
+       int i;
+       u32 val, err;
+       int ret = 0;
+
+       if (!msgs || count <= 0)
+               return -EINVAL;
+
+       for (i = 0; i < count; i++) {
+               if (msgs[i].flags & I2C_M_TEN)
+                       return -EOPNOTSUPP;
+               if (msgs[i].flags & I2C_M_RD) {
+                       if (i < count - 1) {
+                               dev_err(dev, "read message must be last\n");
+                               return -EOPNOTSUPP;
+                       }
+               }
+               if (msgs[i].len > 0xffff)
+                       return -EINVAL;
+               if (msgs[i].flags & I2C_M_IGNORE_NAK)
+                       ignore_nak = true;
+       }
+
+       /* Clear stale status and FIFO state before starting a new transaction. 
*/
+       writel(BCM2835_I2C_C_CLEAR, &regs->c);
+       writel(BCM2835_I2C_S_CLKT | BCM2835_I2C_S_ERR | BCM2835_I2C_S_DONE,
+              &regs->s);
+
+       i2c_dev->curr_msg = msgs;
+       i2c_dev->num_msgs = count;
+       i2c_dev->msg_err = 0;
+
+       bcm2835_i2c_start_transfer(i2c_dev);
+       start = get_timer(0);
+       while (get_timer(start) < BCM2835_I2C_XFER_TIMEOUT_MS) {
+               val = readl(&regs->s);
+
+               err = val & (BCM2835_I2C_S_CLKT | BCM2835_I2C_S_ERR);
+               if (err && !(val & BCM2835_I2C_S_TA))
+                       i2c_dev->msg_err = err;
+
+               if (val & BCM2835_I2C_S_DONE) {
+                       if (!i2c_dev->curr_msg) {
+                               dev_err(dev, "unexpected completion status\n");
+                       } else if (i2c_dev->curr_msg->flags & I2C_M_RD) {
+                               bcm2835_drain_rxfifo(i2c_dev);
+                               val = readl(&regs->s);
+                       }
+
+                       if ((val & BCM2835_I2C_S_RXD) ||
+                           i2c_dev->msg_buf_remaining)
+                               i2c_dev->msg_err = BCM2835_I2C_S_LEN;
+                       done = true;
+                       break;
+               }
+
+               if (val & BCM2835_I2C_S_TXW) {
+                       if (!i2c_dev->msg_buf_remaining) {
+                               i2c_dev->msg_err = val | BCM2835_I2C_S_LEN;
+                               break;
+                       }
+
+                       bcm2835_fill_txfifo(i2c_dev);
+
+                       if (i2c_dev->num_msgs > 0 && 
!i2c_dev->msg_buf_remaining) {
+                               i2c_dev->curr_msg++;
+                               bcm2835_i2c_start_transfer(i2c_dev);
+                       }
+               }
+
+               if (val & BCM2835_I2C_S_RXR) {
+                       if (!i2c_dev->msg_buf_remaining) {
+                               i2c_dev->msg_err = val | BCM2835_I2C_S_LEN;
+                               break;
+                       }
+
+                       bcm2835_drain_rxfifo(i2c_dev);
+               }
+               udelay(1);
+       }
+
+       if (!done) {
+               val = readl(&regs->s);
+               dev_err(dev, "transfer timeout: status=%08x remaining=%zu\n",
+                       val, i2c_dev->msg_buf_remaining);
+               ret = -ETIMEDOUT;
+       }
+
+       /* Always abort/reset the controller and clear W1C status bits. */
+       writel(BCM2835_I2C_C_CLEAR, &regs->c);
+       writel(BCM2835_I2C_S_CLKT | BCM2835_I2C_S_ERR | BCM2835_I2C_S_DONE,
+              &regs->s);
+
+       bcm2835_i2c_finish_transfer(i2c_dev);
+       if (ret)
+               return ret;
+
+       if (ignore_nak)
+               i2c_dev->msg_err &= ~BCM2835_I2C_S_ERR;
+
+       if (!i2c_dev->msg_err)
+               return 0;
+
+       if (i2c_dev->msg_err & BCM2835_I2C_S_ERR)
+               return -EREMOTEIO;
+
+       return -EIO;
+}
+
+static const struct dm_i2c_ops bcm2835_i2c_ops = {
+       .xfer          = bcm2835_i2c_xfer,
+       .set_bus_speed = bcm2835_i2c_set_rate,
+};
+
+static const struct udevice_id bcm2835_i2c_ids[] = {
+       {.compatible = "brcm,bcm2711-i2c"},
+       {.compatible = "brcm,bcm2835-i2c"},
+       {}
+};
+
+static int bcm2835_i2c_probe(struct udevice *bus)
+{
+       struct bcm2835_i2c_priv *priv = dev_get_priv(bus);
+       struct bcm2835_i2c_regs __iomem *regs;
+       unsigned int bus_clk_rate;
+       int ret;
+
+       priv->regs = dev_read_addr_ptr(bus);
+       if (!priv->regs) {
+               printf("bcm2835_i2c: cannot get base address\n");
+               return -EINVAL;
+       }
+
+       regs = priv->regs;
+
+       ret = bcm2835_get_clock_rate(BCM2835_MBOX_CLOCK_ID_CORE, 
&priv->input_rate);
+       if (ret)
+               return ret;
+
+       bus_clk_rate = dev_read_u32_default(bus, "clock-frequency",
+                                           I2C_SPEED_STANDARD_RATE);
+       ret = bcm2835_i2c_set_rate(bus, bus_clk_rate);
+       if (ret)
+               return ret;
+
+       /* clear FIFO + status */
+       writel(BCM2835_I2C_C_CLEAR, &regs->c);
+       writel(BCM2835_I2C_S_CLKT | BCM2835_I2C_S_ERR | BCM2835_I2C_S_DONE,
+              &regs->s);
+
+       return 0;
+}
+
+U_BOOT_DRIVER(bcm2835_i2c) = {
+       .name       = "bcm2835_i2c",
+       .id         = UCLASS_I2C,
+       .of_match   = bcm2835_i2c_ids,
+       .probe      = bcm2835_i2c_probe,
+       .priv_auto  = sizeof(struct bcm2835_i2c_priv),
+       .ops        = &bcm2835_i2c_ops,
+};
-- 
2.34.1

Reply via email to