Re: [PATCH 1/2] lib: utils/i2c: add minimal SpacemiT I2C driver
Anup Patel <[email protected]>
| Newsgroups | org.infradead.lists.opensbi |
|---|---|
| Message-ID | <CAAhSdy07cSX2uZmG4gj0Kk-MFh_4gxan2NNCY1obGod3SQzX3Q@mail.gmail.com> |
On Sun, Apr 19, 2026 at 8:39 PM Aurelien Jarno <[email protected]> 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 <[email protected]> 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 <[email protected]> > + */ > + > +#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 > [email protected] > http://lists.infradead.org/mailman/listinfo/opensbi -- opensbi mailing list [email protected] http://lists.infradead.org/mailman/listinfo/opensbi