/* * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2026 Aurelien Jarno * * Authors: * Aurelien Jarno */ #include #include #include #include #include #include /* 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, };