Skip to contentSkip to conversation

i2c: bcm2835: Add new BCM2835/BCM2711 I2C controller driver

PATCH In review Giau Nguyen 2 participants 3 messages Nov 4 – Nov 11 1 message not in the archive

Subsystems (5)
lore
Share & tools
lore
#0 lore raw

PATCH v4 1/2 i2c: bcm2835: Add new BCM2835/BCM2711 I2C controller driver

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 (Linux commit 32a0a94aa09e ("i2c: Switch back to struct platform_driver::remove()")). The driver is polling based.

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.

To query the clock, add bcm2835_get_clock_rate() to mach-bcm283x and reuse it from bcm2835_get_mmc_clock(). The existing fallback to the maximum clock rate when the firmware reports a rate of 0 is kept for the MMC case.

Tested on a Raspberry Pi 4 Model B with a UPS HAT carrying an INA219 power monitor (I2C bus 1, address 0x42), using the 'i2c dev', 'i2c probe', 'i2c write' and 'i2c read' commands to configure the sensor and read back its current register.

---

Notes:
v4:
- Split defconfig changes into a separate patch.
- Reused bcm2835_get_clock_rate() in bcm2835_get_mmc_clock(), keeping the fallback to GET_MAX_CLOCK_RATE when the firmware reports a rate of 0. Define and declare it before bcm2835_get_mmc_clock() in msg.c and msg.h.
- Added 'depends on ARCH_BCM283X' to Kconfig.
- Dropped unsupported I2C_M_IGNORE_NAK flag handling.
- Removed redundant !curr_msg check in polling loop.
- Replaced printf() with dev_err() in probe().
- Dropped redundant set_rate() call in probe(), the uclass already sets the bus speed from 'clock-frequency'. Check for a zero core clock rate in probe() instead, as bcm2835_get_clock_rate() no longer rejects it.
- Fix #include order, .compatible formatting and copyright header.

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.
- 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              |  29 +-
 drivers/i2c/Kconfig                      |  11 +
 drivers/i2c/Makefile                     |   1 +
 drivers/i2c/i2c-bcm2835.c                | 359 +++++++++++++++++++++++
 6 files changed, 402 insertions(+), 8 deletions(-)
 create mode 100644 drivers/i2c/i2c-bcm2835.c
MAINTAINERS +1 -0
index d273603ac64..53bf76964de 100644
@@ -277,6 +277,7 @@ S:	Maintained
 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
arch/arm/mach-bcm283x/include/mach/msg.h +9 -0
index e54da86e353..ca8cc8826da 100644
@@ -14,6 +14,15 @@
  */
 int bcm2835_power_on_module(u32 module);
 
+/**
+ * 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 (may be 0 if the clock is not running)
+ * Return: 0 if OK, -EIO on error
+ */
+int bcm2835_get_clock_rate(u32 clock_id, ulong *rate);
+
 /**
  * bcm2835_get_mmc_clock() - get the frequency of the MMC clock
  *
arch/arm/mach-bcm283x/msg.c +21 -8
index 05e1a02e80f..d1b85bdac24 100644
@@ -77,15 +77,10 @@ int bcm2835_power_on_module(u32 module)
 	return 0;
 }
 
-int bcm2835_get_mmc_clock(u32 clock_id)
+int bcm2835_get_clock_rate(u32 clock_id, ulong *rate)
 {
 	ALLOC_CACHE_ALIGN_BUFFER(struct msg_get_clock_rate, msg_clk, 1);
 	int ret;
-	u32 clock_rate = 0;
-
-	ret = bcm2835_power_on_module(BCM2835_MBOX_POWER_DEVID_SDHCI);
-	if (ret)
-		return ret;
 
 	BCM2835_MBOX_INIT_HDR(msg_clk);
 	BCM2835_MBOX_INIT_TAG(&msg_clk->get_clock_rate, GET_CLOCK_RATE);
@@ -93,11 +88,29 @@ int bcm2835_get_mmc_clock(u32 clock_id)
 
 	ret = bcm2835_mbox_call_prop(BCM2835_MBOX_PROP_CHAN, &msg_clk->hdr);
 	if (ret) {
-		printf("bcm2835: Could not query eMMC clock rate\n");
+		printf("bcm2835: Could not query clock rate for clock %u\n",
+		       clock_id);
 		return -EIO;
 	}
 
-	clock_rate = msg_clk->get_clock_rate.body.resp.rate_hz;
+	*rate = msg_clk->get_clock_rate.body.resp.rate_hz;
+
+	return 0;
+}
+
+int bcm2835_get_mmc_clock(u32 clock_id)
+{
+	ALLOC_CACHE_ALIGN_BUFFER(struct msg_get_clock_rate, msg_clk, 1);
+	ulong clock_rate;
+	int ret;
+
+	ret = bcm2835_power_on_module(BCM2835_MBOX_POWER_DEVID_SDHCI);
+	if (ret)
+		return ret;
+
+	ret = bcm2835_get_clock_rate(clock_id, &clock_rate);
+	if (ret)
+		return ret;
 
 	if (clock_rate == 0) {
 		BCM2835_MBOX_INIT_HDR(msg_clk);
drivers/i2c/Kconfig +11 -0
index 0b5225fd3f5..77f71cd28ca 100644
@@ -171,6 +171,17 @@ 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
+	depends on ARCH_BCM283X
+	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
drivers/i2c/Makefile +1 -0
index 79e6a298808..207449f0f20 100644
@@ -15,6 +15,7 @@ obj-$(CONFIG_SYS_I2C_ADI) += adi_i2c.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
drivers/i2c/i2c-bcm2835.c new file +359 -0

359 lines not on this page · raw message

Signature · 1 line
2.34.1
#1 lore raw

PATCH v4 2/2 configs: rpi: Enable BCM2835 I2C controller

Enable the newly added BCM2835 I2C controller driver (CONFIG_SYS_I2C_BCM2835) and the 'i2c' command in rpi_4_defconfig and rpi_arm64_defconfig.

Note that the I2C controller nodes are disabled in the firmware device tree by default. Add e.g. "dtparam=i2c_arm=on" to config.txt so that the firmware enables the I2C1 bus before U-Boot is started.

---

Notes:
v4:
- Split the defconfig change out of the driver patch into this patch.
- Also enable the driver in rpi_arm64_defconfig, in addition to rpi_4_defconfig.
- Order the options as generated by 'make savedefconfig' and drop the redundant CONFIG_I2C.
- Mention in the commit message that the I2C nodes need to be enabled via config.txt.

 configs/rpi_4_defconfig     | 3 +++
 configs/rpi_arm64_defconfig | 3 +++
 2 files changed, 6 insertions(+)
configs/rpi_4_defconfig +3 -0
index 850f8c7d1c6..b48abd2b040 100644
@@ -24,6 +24,7 @@ CONFIG_SYS_PROMPT="U-Boot> "
 CONFIG_CMD_NVEDIT_EFI=y
 CONFIG_CMD_DFU=y
 CONFIG_CMD_GPIO=y
+CONFIG_CMD_I2C=y
 CONFIG_CMD_MMC=y
 CONFIG_CMD_PCI=y
 CONFIG_CMD_USB=y
@@ -38,6 +39,8 @@ CONFIG_DFU_MMC=y
 CONFIG_SYS_DFU_DATA_BUF_SIZE=0x100000
 CONFIG_SYS_DFU_MAX_FILE_SIZE=0x200000
 CONFIG_BCM2835_GPIO=y
+CONFIG_DM_I2C=y
+CONFIG_SYS_I2C_BCM2835=y
 CONFIG_MMC_SDHCI=y
 CONFIG_MMC_SDHCI_SDMA=y
 CONFIG_MMC_SDHCI_BCM2835=y
configs/rpi_arm64_defconfig +3 -0
index 87780c2286c..7eebf114df2 100644
@@ -23,6 +23,7 @@ CONFIG_MISC_INIT_R=y
 CONFIG_SYS_PROMPT="U-Boot> "
 CONFIG_CMD_NVEDIT_EFI=y
 CONFIG_CMD_GPIO=y
+CONFIG_CMD_I2C=y
 CONFIG_CMD_MMC=y
 CONFIG_CMD_PCI=y
 CONFIG_CMD_USB=y
@@ -38,6 +39,8 @@ CONFIG_DFU_MMC=y
 CONFIG_SYS_DFU_DATA_BUF_SIZE=0x100000
 CONFIG_SYS_DFU_MAX_FILE_SIZE=0x200000
 CONFIG_BCM2835_GPIO=y
+CONFIG_DM_I2C=y
+CONFIG_SYS_I2C_BCM2835=y
 CONFIG_MMC_SDHCI=y
 CONFIG_MMC_SDHCI_SDMA=y
 CONFIG_MMC_SDHCI_BCM2835=y
Signature · 1 line
2.34.1

A message that is not in the archive (sent off-list or not yet received): CAKXvu5i3EKRKTB_LCZB8xb1a0OjD-nGWqnUWnQ-d1xtjimkY9Q@mail.gmail.com

#3 lore raw

Hi Giàu,

It's on my backlog, sorry I've had limited time of late, but I should do it in the next few days.

Peter

Quoted 573 lines · Giàu Nguyễn Anh

Hi Simon and Peter,

Could you please take a moment to review my patches? Your feedback is greatly appreciated.

Thank you! Regards
--------------------------------------------------------
Giau Nguyen
--------------------------------------------------------
Phone: (+84) 387 915 996

Quoted 559 lines · Giau Nguyen

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 (Linux commit 32a0a94aa09e ("i2c: Switch back to struct platform_driver::remove()")). The driver is polling based.

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.

To query the clock, add bcm2835_get_clock_rate() to mach-bcm283x and reuse it from bcm2835_get_mmc_clock(). The existing fallback to the maximum clock rate when the firmware reports a rate of 0 is kept for the MMC case.

Tested on a Raspberry Pi 4 Model B with a UPS HAT carrying an INA219 power monitor (I2C bus 1, address 0x42), using the 'i2c dev', 'i2c probe', 'i2c write' and 'i2c read' commands to configure the sensor and read back its current register.

---

Notes:
v4:
- Split defconfig changes into a separate patch.
- Reused bcm2835_get_clock_rate() in bcm2835_get_mmc_clock(), keeping the fallback to GET_MAX_CLOCK_RATE when the firmware reports a rate of 0. Define and declare it before bcm2835_get_mmc_clock() in msg.c and msg.h.
- Added 'depends on ARCH_BCM283X' to Kconfig.
- Dropped unsupported I2C_M_IGNORE_NAK flag handling.
- Removed redundant !curr_msg check in polling loop.
- Replaced printf() with dev_err() in probe().
- Dropped redundant set_rate() call in probe(), the uclass already sets the bus speed from 'clock-frequency'. Check for a zero core clock rate in probe() instead, as bcm2835_get_clock_rate() no longer rejects it.
- Fix #include order, .compatible formatting and copyright header.

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.
- 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              |  29 +-
 drivers/i2c/Kconfig                      |  11 +
 drivers/i2c/Makefile                     |   1 +
 drivers/i2c/i2c-bcm2835.c                | 359 +++++++++++++++++++++++
 6 files changed, 402 insertions(+), 8 deletions(-)
 create mode 100644 drivers/i2c/i2c-bcm2835.c
MAINTAINERS +1 -0
index d273603ac64..53bf76964de 100644
@@ -277,6 +277,7 @@ S:  Maintained
 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
arch/arm/mach-bcm283x/include/mach/msg.h +9 -0
index e54da86e353..ca8cc8826da 100644
@@ -14,6 +14,15 @@
  */
 int bcm2835_power_on_module(u32 module);
 
+/**
+ * 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 (may be 0 if the clock is not running)
+ * Return: 0 if OK, -EIO on error
+ */
+int bcm2835_get_clock_rate(u32 clock_id, ulong *rate);
+
 /**
  * bcm2835_get_mmc_clock() - get the frequency of the MMC clock
  *
arch/arm/mach-bcm283x/msg.c +21 -8
index 05e1a02e80f..d1b85bdac24 100644
@@ -77,15 +77,10 @@ int bcm2835_power_on_module(u32 module)
        return 0;
 }
 
-int bcm2835_get_mmc_clock(u32 clock_id)
+int bcm2835_get_clock_rate(u32 clock_id, ulong *rate)
 {
        ALLOC_CACHE_ALIGN_BUFFER(struct msg_get_clock_rate, msg_clk, 1);
        int ret;
-       u32 clock_rate = 0;
-
-       ret = bcm2835_power_on_module(BCM2835_MBOX_POWER_DEVID_SDHCI);
-       if (ret)
-               return ret;
 
        BCM2835_MBOX_INIT_HDR(msg_clk);
        BCM2835_MBOX_INIT_TAG(&msg_clk->get_clock_rate, GET_CLOCK_RATE);
@@ -93,11 +88,29 @@ int bcm2835_get_mmc_clock(u32 clock_id)
 
        ret = bcm2835_mbox_call_prop(BCM2835_MBOX_PROP_CHAN, &msg_clk->hdr);
        if (ret) {
-               printf("bcm2835: Could not query eMMC clock rate\n");
+               printf("bcm2835: Could not query clock rate for clock %u\n",
+                      clock_id);
                return -EIO;
        }
 
-       clock_rate = msg_clk->get_clock_rate.body.resp.rate_hz;
+       *rate = msg_clk->get_clock_rate.body.resp.rate_hz;
+
+       return 0;
+}
+
+int bcm2835_get_mmc_clock(u32 clock_id)
+{
+       ALLOC_CACHE_ALIGN_BUFFER(struct msg_get_clock_rate, msg_clk, 1);
+       ulong clock_rate;
+       int ret;
+
+       ret = bcm2835_power_on_module(BCM2835_MBOX_POWER_DEVID_SDHCI);
+       if (ret)
+               return ret;
+
+       ret = bcm2835_get_clock_rate(clock_id, &clock_rate);
+       if (ret)
+               return ret;
 
        if (clock_rate == 0) {
                BCM2835_MBOX_INIT_HDR(msg_clk);
drivers/i2c/Kconfig +11 -0
index 0b5225fd3f5..77f71cd28ca 100644
@@ -171,6 +171,17 @@ 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
+       depends on ARCH_BCM283X
+       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
drivers/i2c/Makefile +1 -0
index 79e6a298808..207449f0f20 100644
@@ -15,6 +15,7 @@ obj-$(CONFIG_SYS_I2C_ADI) += adi_i2c.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
drivers/i2c/i2c-bcm2835.c new file +359 -1
new file mode 100644
index 00000000000..58bc3ce944c
@@ -0,0 +1,359 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BCM2835 I2C controller driver
+ * Copyright (C) 2026 Giau Nguyen <[email protected]>
+ *
+ * Based on Linux driver: drivers/i2c/busses/i2c-bcm2835.c
+ */
+
+#include <dm.h>
+#include <i2c.h>
+#include <time.h>
+#include <log.h>
+#include <asm/io.h>
+#include <asm/arch/mbox.h>
+#include <asm/arch/msg.h>
+#include <dm/device_compat.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 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;
+       }
+
+       /* 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->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 (!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;
+       int ret;
+
+       priv->regs = dev_read_addr_ptr(bus);
+       if (!priv->regs) {
+               dev_err(bus, "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;
+
+       if (!priv->input_rate) {
+               dev_err(bus, "invalid core clock rate\n");
+               return -EINVAL;
+       }
+
+       /* 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