summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/utils/i2c/Kconfig4
-rw-r--r--lib/utils/i2c/fdt_i2c_spacemit.c220
-rw-r--r--lib/utils/i2c/objects.mk3
-rw-r--r--platform/generic/configs/defconfig1
4 files changed, 228 insertions, 0 deletions
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 800f3f14..91f59a33 100644
--- a/platform/generic/configs/defconfig
+++ b/platform/generic/configs/defconfig
@@ -32,6 +32,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