Hi Giau,
> Mainline U-Boot currently lacks an I2C controller driver for the
> Broadcom BCM2711 SoC (Raspberry Pi 4).
May I ask what i2c devices you're using to test this on U-Boot and on
which generation of device?
> 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.
Typically the main config used is rpi_arm64_defconfig which covers all
64bit capable generations (3/4/5/Zero2).
I'll test/review in the next few days for a RP
> 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(®s->s) & BCM2835_I2C_S_TXD))
> + break;
> +
> + writel(*i2c_dev->msg_buf, ®s->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(®s->s) & BCM2835_I2C_S_RXD))
> + break;
> +
> + *i2c_dev->msg_buf = readl(®s->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, ®s->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), ®s->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, ®s->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, ®s->a);
> + writel(msg->len, ®s->dlen);
> + writel(c, ®s->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, ®s->c);
> + writel(BCM2835_I2C_S_CLKT | BCM2835_I2C_S_ERR | BCM2835_I2C_S_DONE,
> + ®s->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(®s->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(®s->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(®s->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, ®s->c);
> + writel(BCM2835_I2C_S_CLKT | BCM2835_I2C_S_ERR | BCM2835_I2C_S_DONE,
> + ®s->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, ®s->c);
> + writel(BCM2835_I2C_S_CLKT | BCM2835_I2C_S_ERR | BCM2835_I2C_S_DONE,
> + ®s->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
>