From 6405cd40f6d66f691228ffd819a33dc10602055d Mon Sep 17 00:00:00 2001 From: kettenis Date: Mon, 6 Apr 2026 10:30:27 +0000 Subject: [PATCH] Add smtiic(4), a driver for the I2C controller found on the SpacemiT K1 SoC. This is a close relative of mviic(4), but the register layout changed and some bits moved within the registers. ok jca@ --- sys/arch/riscv64/conf/GENERIC | 4 +- sys/arch/riscv64/conf/RAMDISK | 4 +- sys/arch/riscv64/conf/files.riscv64 | 7 +- sys/arch/riscv64/dev/smtclock.c | 105 ++++++++- sys/arch/riscv64/dev/smtiic.c | 324 ++++++++++++++++++++++++++++ 5 files changed, 440 insertions(+), 4 deletions(-) create mode 100644 sys/arch/riscv64/dev/smtiic.c diff --git a/sys/arch/riscv64/conf/GENERIC b/sys/arch/riscv64/conf/GENERIC index 225677d5620..6f6e3c1e35d 100644 --- a/sys/arch/riscv64/conf/GENERIC +++ b/sys/arch/riscv64/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.59 2026/04/05 18:08:11 kettenis Exp $ +# $OpenBSD: GENERIC,v 1.60 2026/04/06 10:30:27 kettenis Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -76,6 +76,8 @@ sfuart* at fdt? smtclock* at fdt? early 1 smtcomphy* at fdt? smtgpio* at fdt? +smtiic* at fdt? +iic* at smtiic? # StarFive SoCs stfclock* at fdt? early 1 diff --git a/sys/arch/riscv64/conf/RAMDISK b/sys/arch/riscv64/conf/RAMDISK index 9625c9c44de..c1bd48d72c2 100644 --- a/sys/arch/riscv64/conf/RAMDISK +++ b/sys/arch/riscv64/conf/RAMDISK @@ -1,4 +1,4 @@ -# $OpenBSD: RAMDISK,v 1.51 2026/04/05 18:08:11 kettenis Exp $ +# $OpenBSD: RAMDISK,v 1.52 2026/04/06 10:30:27 kettenis Exp $ machine riscv64 maxusers 4 @@ -67,6 +67,8 @@ sfuart* at fdt? smtclock* at fdt? early 1 smtcomphy* at fdt? smtgpio* at fdt? +smtiic* at fdt? +iic* at smtiic? # StarFive SoCs stfclock* at fdt? early 1 diff --git a/sys/arch/riscv64/conf/files.riscv64 b/sys/arch/riscv64/conf/files.riscv64 index 36335eefb95..b8d57d50db4 100644 --- a/sys/arch/riscv64/conf/files.riscv64 +++ b/sys/arch/riscv64/conf/files.riscv64 @@ -1,4 +1,4 @@ -# $OpenBSD: files.riscv64,v 1.34 2026/04/05 18:08:11 kettenis Exp $ +# $OpenBSD: files.riscv64,v 1.35 2026/04/06 10:30:27 kettenis Exp $ # Standard stanzas config(8) can't run without maxpartitions 16 @@ -140,6 +140,11 @@ device smtgpio attach smtgpio at fdt file arch/riscv64/dev/smtgpio.c smtgpio +# SpacemiT I2C controller +device smtiic: i2cbus +attach smtiic at fdt +file arch/riscv64/dev/smtiic.c smtiic + # StarFive clock controller device stfclock attach stfclock at fdt diff --git a/sys/arch/riscv64/dev/smtclock.c b/sys/arch/riscv64/dev/smtclock.c index 99829cbafc9..2d72342c1c4 100644 --- a/sys/arch/riscv64/dev/smtclock.c +++ b/sys/arch/riscv64/dev/smtclock.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtclock.c,v 1.1 2026/04/05 11:40:50 kettenis Exp $ */ +/* $OpenBSD: smtclock.c,v 1.2 2026/04/06 10:30:27 kettenis Exp $ */ /* * Copyright (c) 2026 Mark Kettenis * @@ -37,6 +37,22 @@ #define K1_CLK_UART7 6 #define K1_CLK_UART8 7 #define K1_CLK_UART9 8 +#define K1_CLK_TWSI0 32 +#define K1_CLK_TWSI1 33 +#define K1_CLK_TWSI2 34 +#define K1_CLK_TWSI4 35 +#define K1_CLK_TWSI5 36 +#define K1_CLK_TWSI6 37 +#define K1_CLK_TWSI7 38 +#define K1_CLK_TWSI8 39 +#define K1_CLK_TWSI0_BUS 84 +#define K1_CLK_TWSI1_BUS 85 +#define K1_CLK_TWSI2_BUS 86 +#define K1_CLK_TWSI4_BUS 87 +#define K1_CLK_TWSI5_BUS 88 +#define K1_CLK_TWSI6_BUS 89 +#define K1_CLK_TWSI7_BUS 90 +#define K1_CLK_TWSI8_BUS 91 /* APMU resets */ #define K1_RESET_UART0 0 @@ -48,6 +64,14 @@ #define K1_RESET_UART7 6 #define K1_RESET_UART8 7 #define K1_RESET_UART9 8 +#define K1_RESET_TWSI0 32 +#define K1_RESET_TWSI1 33 +#define K1_RESET_TWSI2 34 +#define K1_RESET_TWSI4 35 +#define K1_RESET_TWSI5 36 +#define K1_RESET_TWSI6 37 +#define K1_RESET_TWSI7 38 +#define K1_RESET_TWSI8 39 /* APMU clocks */ #define K1_CLK_USB30 16 @@ -63,11 +87,24 @@ #define K1_RESET_PCIE0_SLAVE 24 #define K1_RESET_PCIE0_DBI 25 #define K1_RESET_PCIE0_GLOBAL 26 +#define K1_RESET_PCIE1_GLOBAL 30 +#define K1_RESET_PCIE2_GLOBAL 34 /* APBC registers */ #define APBC_UART1_CLK_RST 0x0000 #define APBC_UART2_CLK_RST 0x0004 +#define APBC_TWSI8_CLK_RST 0x0020 +#define APBC_TWSI8_CLK_RST_RST (1U << 2) +#define APBC_TWSI8_CLK_RST_FNCLK (1U << 1) +#define APBC_TWSI8_CLK_RST_APBCLK (1U << 0) #define APBC_UART3_CLK_RST 0x0024 +#define APBC_TWSI0_CLK_RST 0x002c +#define APBC_TWSI1_CLK_RST 0x0030 +#define APBC_TWSI2_CLK_RST 0x0038 +#define APBC_TWSI4_CLK_RST 0x0040 +#define APBC_TWSI5_CLK_RST 0x004c +#define APBC_TWSI6_CLK_RST 0x0060 +#define APBC_TWSI7_CLK_RST 0x0068 #define APBC_UART4_CLK_RST 0x0070 #define APBC_UART5_CLK_RST 0x0074 #define APBC_UART6_CLK_RST 0x0078 @@ -79,6 +116,8 @@ /* APMU registers */ #define APMU_USB_CLK_RES_CTRL 0x005c #define APMU_PCIE_CLK_RES_CTRL_PORTA 0x03cc +#define APMU_PCIE_CLK_RES_CTRL_PORTB 0x03d4 +#define APMU_PCIE_CLK_RES_CTRL_PORTC 0x03dc #define HREAD4(sc, reg) \ (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg))) @@ -112,6 +151,22 @@ static struct smtclock k1_apbc_clocks[] = { { K1_CLK_UART7, APBC_UART7_CLK_RST, 1 }, { K1_CLK_UART8, APBC_UART8_CLK_RST, 1 }, { K1_CLK_UART9, APBC_UART9_CLK_RST, 1 }, + { K1_CLK_TWSI0, APBC_TWSI0_CLK_RST, 1 }, + { K1_CLK_TWSI1, APBC_TWSI1_CLK_RST, 1 }, + { K1_CLK_TWSI2, APBC_TWSI2_CLK_RST, 1 }, + { K1_CLK_TWSI4, APBC_TWSI4_CLK_RST, 1 }, + { K1_CLK_TWSI5, APBC_TWSI5_CLK_RST, 1 }, + { K1_CLK_TWSI6, APBC_TWSI6_CLK_RST, 1 }, + { K1_CLK_TWSI7, APBC_TWSI7_CLK_RST, 1 }, + { K1_CLK_TWSI8, APBC_TWSI8_CLK_RST, 1 }, + { K1_CLK_TWSI0_BUS, APBC_TWSI0_CLK_RST, 0 }, + { K1_CLK_TWSI1_BUS, APBC_TWSI1_CLK_RST, 0 }, + { K1_CLK_TWSI2_BUS, APBC_TWSI2_CLK_RST, 0 }, + { K1_CLK_TWSI4_BUS, APBC_TWSI4_CLK_RST, 0 }, + { K1_CLK_TWSI5_BUS, APBC_TWSI5_CLK_RST, 0 }, + { K1_CLK_TWSI6_BUS, APBC_TWSI6_CLK_RST, 0 }, + { K1_CLK_TWSI7_BUS, APBC_TWSI7_CLK_RST, 0 }, + { K1_CLK_TWSI8_BUS, APBC_TWSI8_CLK_RST, 0 }, { -1 }, }; @@ -125,6 +180,14 @@ static struct smtreset k1_apbc_resets[] = { { K1_RESET_UART7, APBC_UART1_CLK_RST, 2, -1 }, { K1_RESET_UART8, APBC_UART1_CLK_RST, 2, -1 }, { K1_RESET_UART9, APBC_UART1_CLK_RST, 2, -1 }, + { K1_RESET_TWSI0, APBC_TWSI0_CLK_RST, 2, -1 }, + { K1_RESET_TWSI1, APBC_TWSI1_CLK_RST, 2, -1 }, + { K1_RESET_TWSI2, APBC_TWSI2_CLK_RST, 2, -1 }, + { K1_RESET_TWSI4, APBC_TWSI4_CLK_RST, 2, -1 }, + { K1_RESET_TWSI5, APBC_TWSI5_CLK_RST, 2, -1 }, + { K1_RESET_TWSI6, APBC_TWSI6_CLK_RST, 2, -1 }, + { K1_RESET_TWSI7, APBC_TWSI7_CLK_RST, 2, -1 }, + { K1_RESET_TWSI8, APBC_TWSI8_CLK_RST, 2, -1 }, { -1 }, }; @@ -144,6 +207,8 @@ static struct smtreset k1_apmu_resets[] = { { K1_RESET_PCIE0_SLAVE, APMU_PCIE_CLK_RES_CTRL_PORTA, -1, 4 }, { K1_RESET_PCIE0_DBI, APMU_PCIE_CLK_RES_CTRL_PORTA, -1, 3 }, { K1_RESET_PCIE0_GLOBAL, APMU_PCIE_CLK_RES_CTRL_PORTA, 8, -1 }, + { K1_RESET_PCIE1_GLOBAL, APMU_PCIE_CLK_RES_CTRL_PORTB, 8, -1 }, + { K1_RESET_PCIE2_GLOBAL, APMU_PCIE_CLK_RES_CTRL_PORTC, 8, -1 }, { -1 }, }; @@ -301,6 +366,25 @@ smtclock_enable(void *cookie, uint32_t *cells, int on) return; } + if (OF_is_compatible(sc->sc_node, "spacemit,k1-syscon-apbc")) { + /* + * To work around the fact that the APBC_TWSI8_CLK_RST + * register is write-only, we enable both clocks and + * clear the reset at the same time. + */ + switch (idx) { + case K1_CLK_TWSI8: + case K1_CLK_TWSI8_BUS: + if (on) + HWRITE4(sc, APBC_TWSI8_CLK_RST, + APBC_TWSI8_CLK_RST_FNCLK | + APBC_TWSI8_CLK_RST_APBCLK); + else + HWRITE4(sc, clock->reg, 0); + return; + } + } + if (on) HSET4(sc, clock->reg, (1U << clock->bit)); else @@ -327,6 +411,25 @@ smtclock_reset(void *cookie, uint32_t *cells, int assert) return; } + if (OF_is_compatible(sc->sc_node, "spacemit,k1-syscon-apbc")) { + /* + * To work around the fact that the APBC_TWSI8_CLK_RST + * register is write-only, we enable both clocks and + * clear the reset at the same time. + */ + switch (idx) { + case K1_RESET_TWSI8: + if (assert) + HWRITE4(sc, APBC_TWSI8_CLK_RST, + APBC_TWSI8_CLK_RST_RST); + else + HWRITE4(sc, APBC_TWSI8_CLK_RST, + APBC_TWSI8_CLK_RST_FNCLK | + APBC_TWSI8_CLK_RST_APBCLK); + return; + } + } + if (reset->assert_bit != -1) assert_mask = (1U << reset->assert_bit); if (reset->deassert_bit != -1) diff --git a/sys/arch/riscv64/dev/smtiic.c b/sys/arch/riscv64/dev/smtiic.c new file mode 100644 index 00000000000..9abc6e130ae --- /dev/null +++ b/sys/arch/riscv64/dev/smtiic.c @@ -0,0 +1,324 @@ +/* $OpenBSD: smtiic.c,v 1.1 2026/04/06 10:30:27 kettenis Exp $ */ +/* + * Copyright (c) 2019 Patrick Wildt + * Copyright (c) 2026 Mark Kettenis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#define _I2C_PRIVATE +#include + +#include +#include + +#include +#include +#include +#include +#include + +/* registers */ +#define ICR 0x00 +#define ICR_BEIE (1U << 22) +#define ICR_GCD (1U << 21) +#define ICR_DRFIE (1U << 20) +#define ICR_ITEIE (1U << 19) +#define ICR_IUE (1U << 14) +#define ICR_SCLE (1U << 13) +#define ICR_UR (1U << 10) +#define ICR_MODE_MASK (0x3 << 8) +#define ICR_MODE_FAST (0x1 << 8) +#define ICR_TB (1U << 3) +#define ICR_ACKNAK (1U << 2) +#define ICR_STOP (1U << 1) +#define ICR_START (1U << 0) + +#define ISR 0x04 +#define ISR_INIT 0xfdfc0000 +#define ISR_BED (1U << 22) +#define ISR_IRF (1U << 20) +#define ISR_ITE (1U << 19) +#define ISR_IBB (1U << 16) +#define ISR_UB (1U << 15) +#define ISR_ACKNAK (1U << 14) +#define IDBR 0x0c + +struct smtiic_softc { + struct device sc_dev; + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + + int sc_node; + uint32_t sc_freq; + + struct i2c_controller sc_ic; +}; + +int smtiic_match(struct device *, void *, void *); +void smtiic_attach(struct device *, struct device *, void *); + +#define HREAD4(sc, reg) \ + (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg))) +#define HWRITE4(sc, reg, val) \ + bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) +#define HSET4(sc, reg, bits) \ + HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits)) +#define HCLR4(sc, reg, bits) \ + HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits)) + +const struct cfattach smtiic_ca = { + sizeof(struct smtiic_softc), smtiic_match, smtiic_attach +}; + +struct cfdriver smtiic_cd = { + NULL, "smtiic", DV_DULL +}; + +int smtiic_i2c_acquire_bus(void *, int); +void smtiic_i2c_release_bus(void *, int); +int smtiic_wait_state(struct smtiic_softc *, uint32_t, uint32_t); +int smtiic_send_start(void *, int); +int smtiic_send_stop(void *, int); +int smtiic_initiate_xfer(void *, i2c_addr_t, int); +int smtiic_read_byte(void *, uint8_t *, int); +int smtiic_write_byte(void *, uint8_t, int); + +void smtiic_bus_scan(struct device *, struct i2cbus_attach_args *, void *); + +int +smtiic_match(struct device *parent, void *match, void *aux) +{ + struct fdt_attach_args *faa = aux; + + return OF_is_compatible(faa->fa_node, "spacemit,k1-i2c"); +} + +void +smtiic_attach(struct device *parent, struct device *self, void *aux) +{ + struct smtiic_softc *sc = (struct smtiic_softc *)self; + struct fdt_attach_args *faa = aux; + struct i2cbus_attach_args iba; + + if (faa->fa_nreg < 1) { + printf(": no registers\n"); + return; + } + + sc->sc_iot = faa->fa_iot; + if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, + faa->fa_reg[0].size, 0, &sc->sc_ioh)) { + printf(": can't map registers\n"); + return; + } + sc->sc_node = faa->fa_node; + + printf("\n"); + + pinctrl_byname(sc->sc_node, "default"); + + clock_enable_all(sc->sc_node); + reset_deassert_all(sc->sc_node); + + sc->sc_freq = OF_getpropint(sc->sc_node, "clock-frequency", 100000); + + /* reset */ + HWRITE4(sc, ICR, ICR_UR); + delay(5); + HWRITE4(sc, ICR, 0); + HWRITE4(sc, ISR, ISR_INIT); + + /* set defaults */ + HSET4(sc, ICR, ICR_SCLE | ICR_GCD); + if (sc->sc_freq == 400000) + HSET4(sc, ICR, ICR_MODE_FAST); + + sc->sc_ic.ic_cookie = sc; + sc->sc_ic.ic_acquire_bus = smtiic_i2c_acquire_bus; + sc->sc_ic.ic_release_bus = smtiic_i2c_release_bus; + sc->sc_ic.ic_exec = NULL; + sc->sc_ic.ic_send_start = smtiic_send_start; + sc->sc_ic.ic_send_stop = smtiic_send_stop; + sc->sc_ic.ic_initiate_xfer = smtiic_initiate_xfer; + sc->sc_ic.ic_read_byte = smtiic_read_byte; + sc->sc_ic.ic_write_byte = smtiic_write_byte; + + bzero(&iba, sizeof iba); + iba.iba_name = "iic"; + iba.iba_tag = &sc->sc_ic; + iba.iba_bus_scan = smtiic_bus_scan; + iba.iba_bus_scan_arg = &sc->sc_node; + config_found(&sc->sc_dev, &iba, iicbus_print); +} + +int +smtiic_wait_state(struct smtiic_softc *sc, uint32_t mask, uint32_t value) +{ + uint32_t state; + int timeout; + + for (timeout = 1000; timeout > 0; timeout--) { + if (((state = HREAD4(sc, ISR)) & mask) == value) + return 0; + delay(10); + } + return ETIMEDOUT; +} + +int +smtiic_i2c_acquire_bus(void *cookie, int flags) +{ + struct smtiic_softc *sc = cookie; + + HSET4(sc, ICR, ICR_IUE); + return 0; +} + +void +smtiic_i2c_release_bus(void *cookie, int flags) +{ + struct smtiic_softc *sc = cookie; + + HCLR4(sc, ICR, ICR_IUE); +} + +int +smtiic_send_start(void *v, int flags) +{ + struct smtiic_softc *sc = v; + + HSET4(sc, ICR, ICR_START); + return 0; +} + +int +smtiic_send_stop(void *v, int flags) +{ + struct smtiic_softc *sc = v; + + HSET4(sc, ICR, ICR_STOP); + return 0; +} + +int +smtiic_initiate_xfer(void *v, i2c_addr_t addr, int flags) +{ + struct smtiic_softc *sc = v; + + if (smtiic_wait_state(sc, ISR_IBB, 0)) + return EIO; + + HCLR4(sc, ICR, ICR_START); + HCLR4(sc, ICR, ICR_STOP); + HCLR4(sc, ICR, ICR_ACKNAK); + if (flags & I2C_F_READ) + HWRITE4(sc, IDBR, addr << 1 | 1); + else + HWRITE4(sc, IDBR, addr << 1); + HSET4(sc, ICR, ICR_START); + + HSET4(sc, ICR, ICR_TB); + if (smtiic_wait_state(sc, ISR_ITE, ISR_ITE)) + return EIO; + HWRITE4(sc, ISR, ISR_ITE); + if (HREAD4(sc, ISR) & ISR_ACKNAK) + return EIO; + + return 0; +} + +int +smtiic_read_byte(void *v, uint8_t *valp, int flags) +{ + struct smtiic_softc *sc = v; + + if (smtiic_wait_state(sc, ISR_IBB, 0)) + return EIO; + + HCLR4(sc, ICR, ICR_START); + HCLR4(sc, ICR, ICR_STOP); + HCLR4(sc, ICR, ICR_ACKNAK); + if ((flags & (I2C_F_LAST | I2C_F_STOP)) == (I2C_F_LAST | I2C_F_STOP)) + HSET4(sc, ICR, ICR_STOP); + if (flags & I2C_F_LAST) + HSET4(sc, ICR, ICR_ACKNAK); + + HSET4(sc, ICR, ICR_TB); + if (smtiic_wait_state(sc, ISR_IRF, ISR_IRF)) + return EIO; + *valp = HREAD4(sc, IDBR); + HWRITE4(sc, ISR, ISR_IRF); + + return 0; +} + +int +smtiic_write_byte(void *v, uint8_t val, int flags) +{ + struct smtiic_softc *sc = v; + + HCLR4(sc, ICR, ICR_START); + HCLR4(sc, ICR, ICR_STOP); + HCLR4(sc, ICR, ICR_ACKNAK); + HWRITE4(sc, IDBR, val); + if (flags & I2C_F_STOP) + HSET4(sc, ICR, ICR_STOP); + + HSET4(sc, ICR, ICR_TB); + if (smtiic_wait_state(sc, ISR_ITE, ISR_ITE)) + return EIO; + HWRITE4(sc, ISR, ISR_ITE); + if (HREAD4(sc, ISR) & ISR_ACKNAK) + return EIO; + + return 0; +} + +void +smtiic_bus_scan(struct device *self, struct i2cbus_attach_args *iba, void *aux) +{ + int iba_node = *(int *)aux; + struct i2c_attach_args ia; + char name[32]; + uint32_t reg[1]; + int node; + + for (node = OF_child(iba_node); node; node = OF_peer(node)) { + memset(name, 0, sizeof(name)); + memset(reg, 0, sizeof(reg)); + + if (!OF_is_enabled(node)) + continue; + + if (OF_getprop(node, "compatible", name, sizeof(name)) == -1) + continue; + if (name[0] == '\0') + continue; + + if (OF_getprop(node, "reg", ®, sizeof(reg)) != sizeof(reg)) + continue; + + memset(&ia, 0, sizeof(ia)); + ia.ia_tag = iba->iba_tag; + ia.ia_addr = bemtoh32(®[0]); + ia.ia_name = name; + ia.ia_cookie = &node; + config_found(self, &ia, iic_print); + } +}