[PATCH 1/2] lib: utils/i2c: add minimal SpacemiT I2C driver
Anup Patel
anup at brainfault.org
Mon May 11 06:55:59 PDT 2026
On Sun, Apr 19, 2026 at 8:39 PM Aurelien Jarno <aurelien at aurel32.net> wrote:
>
> Add a simple SpacemiT I2C driver for basic byte transfers over the I2C
> bus, prioritizing simplicity over performance. The driver operates in
> PIO mode and does not use interrupts, FIFO, or DMA.
>
> The controller is reset at the start of each transaction to ensure a
> known initial state, regardless of prior configuration by the kernel.
> This also avoids the need for additional error recovery code.
>
> This will be used for communication with onboard PMIC to reset and
> power-off the board.
>
> Signed-off-by: Aurelien Jarno <aurelien at aurel32.net>
Applied this patch to the riscv/opensbi repo.
Thanks,
Anup
> ---
> lib/utils/i2c/Kconfig | 4 +
> lib/utils/i2c/fdt_i2c_spacemit.c | 220 +++++++++++++++++++++++++++++
> lib/utils/i2c/objects.mk | 3 +
> platform/generic/configs/defconfig | 1 +
> 4 files changed, 228 insertions(+)
> create mode 100644 lib/utils/i2c/fdt_i2c_spacemit.c
>
> diff --git a/lib/utils/i2c/Kconfig b/lib/utils/i2c/Kconfig
> index 7fa32fcf..bdaaff62 100644
> --- a/lib/utils/i2c/Kconfig
> +++ b/lib/utils/i2c/Kconfig
> @@ -14,6 +14,10 @@ config FDT_I2C_SIFIVE
> bool "SiFive I2C FDT driver"
> default n
>
> +config FDT_I2C_SPACEMIT
> + bool "SpacemiT I2C FDT driver"
> + default n
> +
> config FDT_I2C_DW
> bool "Synopsys Designware I2C FDT driver"
> select I2C_DW
> diff --git a/lib/utils/i2c/fdt_i2c_spacemit.c b/lib/utils/i2c/fdt_i2c_spacemit.c
> new file mode 100644
> index 00000000..9009b6b6
> --- /dev/null
> +++ b/lib/utils/i2c/fdt_i2c_spacemit.c
> @@ -0,0 +1,220 @@
> +/*
> + * SPDX-License-Identifier: BSD-2-Clause
> + *
> + * Copyright (c) 2026 Aurelien Jarno
> + *
> + * Authors:
> + * Aurelien Jarno <aurelien at aurel32.net>
> + */
> +
> +#include <sbi/riscv_io.h>
> +#include <sbi/sbi_error.h>
> +#include <sbi/sbi_heap.h>
> +#include <sbi/sbi_timer.h>
> +#include <sbi_utils/fdt/fdt_helper.h>
> +#include <sbi_utils/i2c/fdt_i2c.h>
> +
> +/* Controller registers */
> +#define ICR_OFFSET 0x00 /* I2C control register */
> +#define IDBR_OFFSET 0x0c /* I2C data buffer register */
> +
> +/* Control register bits */
> +#define ICR_START BIT(0) /* start */
> +#define ICR_STOP BIT(1) /* stop */
> +#define ICR_ACKNAK BIT(2) /* ACK(0) or NAK(1) */
> +#define ICR_TB BIT(3) /* transfer byte */
> +#define ICR_UR BIT(10) /* unit reset */
> +#define ICR_SCLE BIT(13) /* SCL enable */
> +#define ICR_IUE BIT(14) /* unit enable */
> +
> +/* Timing */
> +#define I2C_RESET_US 10
> +#define I2C_TIMEOUT_US 1000
> +
> +struct spacemit_i2c_adapter {
> + unsigned long base;
> + struct i2c_adapter adapter;
> +};
> +
> +static inline void spacemit_i2c_set_reg(struct spacemit_i2c_adapter *adap,
> + uint8_t reg, uint32_t val)
> +{
> + writel(val, (void *)adap->base + reg);
> +}
> +
> +static inline uint32_t spacemit_i2c_get_reg(struct spacemit_i2c_adapter *adap,
> + uint32_t reg)
> +{
> + return readl((void *)adap->base + reg);
> +}
> +
> +static void spacemit_i2c_reset(struct spacemit_i2c_adapter *adap)
> +{
> + /* disable unit */
> + spacemit_i2c_set_reg(adap, ICR_OFFSET, 0);
> + sbi_timer_udelay(I2C_RESET_US);
> +
> + /* reset unit */
> + spacemit_i2c_set_reg(adap, ICR_OFFSET, ICR_UR);
> + sbi_timer_udelay(I2C_RESET_US);
> +
> + /* clear reset and enable unit and SCL */
> + spacemit_i2c_set_reg(adap, ICR_OFFSET, ICR_IUE | ICR_SCLE);
> +}
> +
> +static int spacemit_i2c_wait_xfer_done(struct spacemit_i2c_adapter *adap)
> +{
> + for (int i = 0; i < I2C_TIMEOUT_US; i++) {
> + uint32_t val = spacemit_i2c_get_reg(adap, ICR_OFFSET);
> +
> + if (!(val & ICR_TB))
> + return 0;
> +
> + sbi_timer_udelay(1);
> + };
> +
> + return SBI_ETIMEDOUT;
> +}
> +
> +static void spacemit_i2c_start_xfer(struct spacemit_i2c_adapter *adap,
> + uint32_t ctrl)
> +{
> + const uint32_t ctrl_mask = ICR_START | ICR_STOP | ICR_ACKNAK;
> + uint32_t val;
> +
> + val = spacemit_i2c_get_reg(adap, ICR_OFFSET);
> + val &= ~ctrl_mask;
> + val |= (ctrl & ctrl_mask);
> + val |= ICR_TB;
> +
> + spacemit_i2c_set_reg(adap, ICR_OFFSET, val);
> +}
> +
> +static int spacemit_i2c_xfer_write(struct spacemit_i2c_adapter *adap,
> + uint8_t byte, uint32_t ctrl)
> +{
> + spacemit_i2c_set_reg(adap, IDBR_OFFSET, byte);
> + spacemit_i2c_start_xfer(adap, ctrl);
> +
> + return spacemit_i2c_wait_xfer_done(adap);
> +}
> +
> +static int spacemit_i2c_xfer_read(struct spacemit_i2c_adapter *adap,
> + uint8_t *byte, uint32_t ctrl)
> +{
> + int rc;
> +
> + spacemit_i2c_start_xfer(adap, ctrl);
> +
> + rc = spacemit_i2c_wait_xfer_done(adap);
> + if (rc)
> + return rc;
> +
> + *byte = spacemit_i2c_get_reg(adap, IDBR_OFFSET);
> + return 0;
> +}
> +
> +static int spacemit_i2c_adapter_write(struct i2c_adapter *ia, uint8_t addr,
> + uint8_t reg, uint8_t *buffer, int len)
> +{
> + struct spacemit_i2c_adapter *adap =
> + container_of(ia, struct spacemit_i2c_adapter, adapter);
> + int rc;
> +
> + /* reset controller to a known state */
> + spacemit_i2c_reset(adap);
> +
> + /* send device address (in write mode) */
> + rc = spacemit_i2c_xfer_write(adap, addr << 1, ICR_START);
> + if (rc)
> + return rc;
> +
> + /* send register + data bytes */
> + for (int i = 0; i < len + 1; i++) {
> + uint32_t ctrl = (i == len) ? ICR_STOP : 0;
> + uint8_t byte = (i == 0) ? reg : buffer[i - 1];
> +
> + rc = spacemit_i2c_xfer_write(adap, byte, ctrl);
> + if (rc)
> + return rc;
> + }
> +
> + return 0;
> +}
> +
> +static int spacemit_i2c_adapter_read(struct i2c_adapter *ia, uint8_t addr,
> + uint8_t reg, uint8_t *buffer, int len)
> +{
> + struct spacemit_i2c_adapter *adap =
> + container_of(ia, struct spacemit_i2c_adapter, adapter);
> + int rc;
> +
> + /* reset controller to a known state */
> + spacemit_i2c_reset(adap);
> +
> + /* send device address (in write mode) */
> + rc = spacemit_i2c_xfer_write(adap, addr << 1, ICR_START);
> + if (rc)
> + return rc;
> +
> + /* send register */
> + rc = spacemit_i2c_xfer_write(adap, reg, 0);
> + if (rc)
> + return rc;
> +
> + /* repeated start and send device address (in read mode) */
> + rc = spacemit_i2c_xfer_write(adap, (addr << 1) | 1, ICR_START);
> + if (rc)
> + return rc;
> +
> + /* read data bytes */
> + for (int i = 0; i < len; i++) {
> + uint32_t ctrl = (i == len - 1) ? (ICR_ACKNAK | ICR_STOP) : 0;
> +
> + rc = spacemit_i2c_xfer_read(adap, &buffer[i], ctrl);
> + if (rc)
> + return rc;
> + }
> +
> + return 0;
> +}
> +
> +static int spacemit_i2c_init(const void *fdt, int nodeoff,
> + const struct fdt_match *match)
> +{
> + struct spacemit_i2c_adapter *adapter;
> + uint64_t base;
> + int rc;
> +
> + adapter = sbi_zalloc(sizeof(*adapter));
> + if (!adapter)
> + return SBI_ENOMEM;
> +
> + rc = fdt_get_node_addr_size(fdt, nodeoff, 0, &base, NULL);
> + if (rc) {
> + sbi_free(adapter);
> + return rc;
> + }
> +
> + adapter->base = base;
> + adapter->adapter.id = nodeoff;
> + adapter->adapter.write = spacemit_i2c_adapter_write;
> + adapter->adapter.read = spacemit_i2c_adapter_read;
> + rc = i2c_adapter_add(&adapter->adapter);
> + if (rc) {
> + sbi_free(adapter);
> + return rc;
> + }
> +
> + return 0;
> +}
> +
> +static const struct fdt_match spacemit_i2c_match[] = {
> + { .compatible = "spacemit,k1-i2c" },
> + { },
> +};
> +
> +const struct fdt_driver fdt_i2c_adapter_spacemit = {
> + .match_table = spacemit_i2c_match,
> + .init = spacemit_i2c_init,
> +};
> diff --git a/lib/utils/i2c/objects.mk b/lib/utils/i2c/objects.mk
> index d34d6648..91ac17ec 100644
> --- a/lib/utils/i2c/objects.mk
> +++ b/lib/utils/i2c/objects.mk
> @@ -15,6 +15,9 @@ libsbiutils-objs-$(CONFIG_FDT_I2C) += i2c/fdt_i2c_adapter_drivers.carray.o
> carray-fdt_i2c_adapter_drivers-$(CONFIG_FDT_I2C_SIFIVE) += fdt_i2c_adapter_sifive
> libsbiutils-objs-$(CONFIG_FDT_I2C_SIFIVE) += i2c/fdt_i2c_sifive.o
>
> +carray-fdt_i2c_adapter_drivers-$(CONFIG_FDT_I2C_SPACEMIT) += fdt_i2c_adapter_spacemit
> +libsbiutils-objs-$(CONFIG_FDT_I2C_SPACEMIT) += i2c/fdt_i2c_spacemit.o
> +
> carray-fdt_i2c_adapter_drivers-$(CONFIG_FDT_I2C_DW) += fdt_i2c_adapter_dw
> libsbiutils-objs-$(CONFIG_FDT_I2C_DW) += i2c/fdt_i2c_dw.o
>
> diff --git a/platform/generic/configs/defconfig b/platform/generic/configs/defconfig
> index 1d3431e7..a9cb0f06 100644
> --- a/platform/generic/configs/defconfig
> +++ b/platform/generic/configs/defconfig
> @@ -31,6 +31,7 @@ CONFIG_FDT_HSM_RPMI=y
> CONFIG_FDT_HSM_SIFIVE_TMC0=y
> CONFIG_FDT_I2C=y
> CONFIG_FDT_I2C_SIFIVE=y
> +CONFIG_FDT_I2C_SPACEMIT=y
> CONFIG_FDT_I2C_DW=y
> CONFIG_FDT_IPI=y
> CONFIG_FDT_IPI_MSWI=y
> --
> 2.53.0
>
>
> --
> opensbi mailing list
> opensbi at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/opensbi
More information about the opensbi
mailing list