From 97ba7fd48da66a56bb5c7983b510ed2a12a466da Mon Sep 17 00:00:00 2001 From: Yong Cong Sin Date: Fri, 10 May 2024 18:08:51 +0800 Subject: [PATCH] drivers: i2c: add Broadcom iProc I2C driver Add device driver, bindings and build-only test for Broadcom iProc I2C. Signed-off-by: Yong Cong Sin --- drivers/i2c/CMakeLists.txt | 1 + drivers/i2c/Kconfig | 1 + drivers/i2c/Kconfig.bcm_iproc | 10 + drivers/i2c/i2c_bcm_iproc.c | 950 ++++++++++++++++++ dts/bindings/i2c/brcm,iproc-i2c.yaml | 16 + .../i2c/boards/qemu_cortex_m3.overlay | 12 + 6 files changed, 990 insertions(+) create mode 100644 drivers/i2c/Kconfig.bcm_iproc create mode 100644 drivers/i2c/i2c_bcm_iproc.c create mode 100644 dts/bindings/i2c/brcm,iproc-i2c.yaml diff --git a/drivers/i2c/CMakeLists.txt b/drivers/i2c/CMakeLists.txt index cb180ef24e3..16fa2b450d1 100644 --- a/drivers/i2c/CMakeLists.txt +++ b/drivers/i2c/CMakeLists.txt @@ -10,6 +10,7 @@ zephyr_library_sources_ifdef(CONFIG_I2C_RTIO i2c_rtio.c) zephyr_library_sources_ifdef(CONFIG_I2C_SHELL i2c_shell.c) zephyr_library_sources_ifdef(CONFIG_I2C_BITBANG i2c_bitbang.c) zephyr_library_sources_ifdef(CONFIG_I2C_TELINK_B91 i2c_b91.c) +zephyr_library_sources_ifdef(CONFIG_I2C_IPROC i2c_bcm_iproc.c) zephyr_library_sources_ifdef(CONFIG_I2C_CC13XX_CC26XX i2c_cc13xx_cc26xx.c) zephyr_library_sources_ifdef(CONFIG_I2C_CC32XX i2c_cc32xx.c) zephyr_library_sources_ifdef(CONFIG_I2C_ESP32 i2c_esp32.c) diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index 8af4bbbeb8b..f5dacfe8ea4 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -94,6 +94,7 @@ endif # I2C_RTIO # Include these first so that any properties (e.g. defaults) below can be # overridden (by defining symbols in multiple locations) source "drivers/i2c/Kconfig.b91" +source "drivers/i2c/Kconfig.bcm_iproc" source "drivers/i2c/Kconfig.cc13xx_cc26xx" source "drivers/i2c/Kconfig.dw" source "drivers/i2c/Kconfig.esp32" diff --git a/drivers/i2c/Kconfig.bcm_iproc b/drivers/i2c/Kconfig.bcm_iproc new file mode 100644 index 00000000000..b192dc14831 --- /dev/null +++ b/drivers/i2c/Kconfig.bcm_iproc @@ -0,0 +1,10 @@ +# Copyright 2020 Broadcom +# Copyright 2024 Meta Platforms +# SPDX-License-Identifier: Apache-2.0 + +config I2C_IPROC + bool "Broadcom iProc I2C controller" + default y + depends on DT_HAS_BRCM_IPROC_I2C_ENABLED + help + Enable support for Broadcom iProc I2C controller. diff --git a/drivers/i2c/i2c_bcm_iproc.c b/drivers/i2c/i2c_bcm_iproc.c new file mode 100644 index 00000000000..fbc5dc47583 --- /dev/null +++ b/drivers/i2c/i2c_bcm_iproc.c @@ -0,0 +1,950 @@ +/* + * Copyright 2020 Broadcom + * Copyright 2024 Meta Platforms + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT brcm_iproc_i2c + +#include +#include + +#include +#include +#include + +#define LOG_LEVEL CONFIG_I2C_LOG_LEVEL +LOG_MODULE_REGISTER(iproc_i2c); + +#include "i2c-priv.h" + +/* Registers */ +#define CFG_OFFSET 0x00 +#define CFG_RESET_SHIFT 31 +#define CFG_EN_SHIFT 30 +#define CFG_M_RETRY_CNT_SHIFT 16 +#define CFG_M_RETRY_CNT_MASK 0x0f + +#define TIM_CFG_OFFSET 0x04 +#define TIM_CFG_MODE_400_SHIFT 31 +#define TIM_RAND_TARGET_STRETCH_SHIFT 24 +#define TIM_RAND_TARGET_STRETCH_MASK 0x7f + +#define S_ADDR_OFFSET 0x08 +#define S_ADDR_OFFSET_ADDR0_MASK 0x7f +#define S_ADDR_OFFSET_ADDR0_SHIFT 0 +#define S_ADDR_OFFSET_ADDR0_EN_BIT 7 + +#define M_FIFO_CTRL_OFFSET 0x0c +#define M_FIFO_RX_FLUSH_SHIFT 31 +#define M_FIFO_TX_FLUSH_SHIFT 30 +#define M_FIFO_RX_CNT_SHIFT 16 +#define M_FIFO_RX_CNT_MASK 0x7f +#define M_FIFO_RX_THLD_SHIFT 8 +#define M_FIFO_RX_THLD_MASK 0x3f + +#define S_FIFO_CTRL_OFFSET 0x10 +#define S_FIFO_RX_FLUSH_SHIFT 31 +#define S_FIFO_TX_FLUSH_SHIFT 30 + +#define M_CMD_OFFSET 0x30 +#define M_CMD_START_BUSY_SHIFT 31 +#define M_CMD_STATUS_SHIFT 25 +#define M_CMD_STATUS_MASK 0x07 +#define M_CMD_STATUS_SUCCESS 0x0 +#define M_CMD_STATUS_LOST_ARB 0x1 +#define M_CMD_STATUS_NACK_ADDR 0x2 +#define M_CMD_STATUS_NACK_DATA 0x3 +#define M_CMD_STATUS_TIMEOUT 0x4 +#define M_CMD_STATUS_FIFO_UNDERRUN 0x5 +#define M_CMD_STATUS_RX_FIFO_FULL 0x6 +#define M_CMD_SMB_PROT_SHIFT 9 +#define M_CMD_SMB_PROT_QUICK 0x0 +#define M_CMD_SMB_PROT_MASK 0xf +#define M_CMD_SMB_PROT_BLK_WR 0x7 +#define M_CMD_SMB_PROT_BLK_RD 0x8 +#define M_CMD_PEC_SHIFT 8 +#define M_CMD_RD_CNT_MASK 0xff + +#define S_CMD_OFFSET 0x34 +#define S_CMD_START_BUSY_SHIFT 31 +#define S_CMD_STATUS_SHIFT 23 +#define S_CMD_STATUS_MASK 0x07 +#define S_CMD_STATUS_TIMEOUT 0x5 +#define S_CMD_STATUS_MASTER_ABORT 0x7 + +#define IE_OFFSET 0x38 +#define IE_M_RX_FIFO_FULL_SHIFT 31 +#define IE_M_RX_THLD_SHIFT 30 +#define IE_M_START_BUSY_SHIFT 28 +#define IE_M_TX_UNDERRUN_SHIFT 27 +#define IE_S_RX_FIFO_FULL_SHIFT 26 +#define IE_S_RX_THLD_SHIFT 25 +#define IE_S_RX_EVENT_SHIFT 24 +#define IE_S_START_BUSY_SHIFT 23 +#define IE_S_TX_UNDERRUN_SHIFT 22 +#define IE_S_RD_EN_SHIFT 21 + +#define IS_OFFSET 0x3c +#define IS_M_RX_FIFO_FULL_SHIFT 31 +#define IS_M_RX_THLD_SHIFT 30 +#define IS_M_START_BUSY_SHIFT 28 +#define IS_M_TX_UNDERRUN_SHIFT 27 +#define IS_S_RX_FIFO_FULL_SHIFT 26 +#define IS_S_RX_THLD_SHIFT 25 +#define IS_S_RX_EVENT_SHIFT 24 +#define IS_S_START_BUSY_SHIFT 23 +#define IS_S_TX_UNDERRUN_SHIFT 22 +#define IS_S_RD_EN_SHIFT 21 + +#define M_TX_OFFSET 0x40 +#define M_TX_WR_STATUS_SHIFT 31 +#define M_TX_DATA_MASK 0xff + +#define M_RX_OFFSET 0x44 +#define M_RX_STATUS_SHIFT 30 +#define M_RX_STATUS_MASK 0x03 +#define M_RX_PEC_ERR_SHIFT 29 +#define M_RX_DATA_SHIFT 0 +#define M_RX_DATA_MASK 0xff + +#define S_TX_OFFSET 0x48 +#define S_TX_WR_STATUS_SHIFT 31 + +#define S_RX_OFFSET 0x4c +#define S_RX_STATUS_SHIFT 30 +#define S_RX_STATUS_MASK 0x03 +#define S_RX_DATA_SHIFT 0x0 +#define S_RX_DATA_MASK 0xff + +#define I2C_TIMEOUT_MSEC 100 +#define TX_RX_FIFO_SIZE 64 +#define M_RX_FIFO_MAX_THLD_VALUE (TX_RX_FIFO_SIZE - 1) +#define M_RX_FIFO_THLD_VALUE 50 + +#define I2C_MAX_TARGET_ADDR 0x7f + +#define I2C_TARGET_RX_FIFO_EMPTY 0x0 +#define I2C_TARGET_RX_START 0x1 +#define I2C_TARGET_RX_DATA 0x2 +#define I2C_TARGET_RX_END 0x3 + +#define IE_S_ALL_INTERRUPT_SHIFT 21 +#define IE_S_ALL_INTERRUPT_MASK 0x3f + +#define TARGET_CLOCK_STRETCH_TIME 25 + +/* + * To keep running in ISR for less time, + * max target read per interrupt is set to 10 bytes. + */ +#define MAX_TARGET_RX_PER_INT 10 + +#define ISR_MASK_TARGET \ + (BIT(IS_S_START_BUSY_SHIFT) | BIT(IS_S_RX_EVENT_SHIFT) | BIT(IS_S_RD_EN_SHIFT) | \ + BIT(IS_S_TX_UNDERRUN_SHIFT) | BIT(IS_S_RX_FIFO_FULL_SHIFT) | BIT(IS_S_RX_THLD_SHIFT)) + +#define ISR_MASK \ + (BIT(IS_M_START_BUSY_SHIFT) | BIT(IS_M_TX_UNDERRUN_SHIFT) | BIT(IS_M_RX_THLD_SHIFT)) + +#define DEV_CFG(dev) ((struct iproc_i2c_config *)(dev)->config) +#define DEV_DATA(dev) ((struct iproc_i2c_data *)(dev)->data) +#define DEV_BASE(dev) ((DEV_CFG(dev))->base) + +struct iproc_i2c_config { + mem_addr_t base; + uint32_t bitrate; + void (*irq_config_func)(const struct device *dev); +}; + +struct iproc_i2c_data { + struct i2c_target_config *target_cfg; + struct i2c_msg *msg; + uint32_t tx_bytes; + uint32_t rx_bytes; + uint32_t thld_bytes; + uint32_t tx_underrun; + struct k_sem device_sync_sem; + uint32_t target_int_mask; + bool rx_start_rcvd; + bool target_read_complete; + bool target_rx_only; +}; + +static void iproc_i2c_enable_disable(const struct device *dev, bool enable) +{ + mem_addr_t base = DEV_BASE(dev); + uint32_t val; + + val = sys_read32(base + CFG_OFFSET); + if (enable) { + val |= BIT(CFG_EN_SHIFT); + } else { + val &= ~BIT(CFG_EN_SHIFT); + } + sys_write32(val, base + CFG_OFFSET); +} + +static void iproc_i2c_reset_controller(const struct device *dev) +{ + mem_addr_t base = DEV_BASE(dev); + uint32_t val; + + /* put controller in reset */ + val = sys_read32(base + CFG_OFFSET); + val |= BIT(CFG_RESET_SHIFT); + val &= ~BIT(CFG_EN_SHIFT); + sys_write32(val, base + CFG_OFFSET); + + k_busy_wait(100); + + /* bring controller out of reset */ + sys_clear_bit(base + CFG_OFFSET, CFG_RESET_SHIFT); +} + +#ifdef CONFIG_I2C_TARGET +/* Set target addr */ +static int iproc_i2c_target_set_address(const struct device *dev, uint16_t addr) +{ + mem_addr_t base = DEV_BASE(dev); + uint32_t val; + + if ((addr == 0) && (addr > I2C_MAX_TARGET_ADDR)) { + LOG_ERR("Invalid target address(0x%x) received", addr); + return -EINVAL; + } + + addr = ((addr & S_ADDR_OFFSET_ADDR0_MASK) | BIT(S_ADDR_OFFSET_ADDR0_EN_BIT)); + val = sys_read32(base + S_ADDR_OFFSET); + val &= ~(S_ADDR_OFFSET_ADDR0_MASK | BIT(S_ADDR_OFFSET_ADDR0_EN_BIT)); + val |= addr; + sys_write32(val, base + S_ADDR_OFFSET); + + return 0; +} + +static int iproc_i2c_target_init(const struct device *dev, bool need_reset) +{ + struct iproc_i2c_data *dd = DEV_DATA(dev); + mem_addr_t base = DEV_BASE(dev); + struct i2c_target_config *target_config = dd->target_cfg; + uint32_t val; + int ret; + + if (need_reset) { + iproc_i2c_reset_controller(dev); + } + + /* flush target TX/RX FIFOs */ + val = BIT(S_FIFO_RX_FLUSH_SHIFT) | BIT(S_FIFO_TX_FLUSH_SHIFT); + sys_write32(val, base + S_FIFO_CTRL_OFFSET); + + /* Maximum target stretch time */ + val = sys_read32(base + TIM_CFG_OFFSET); + val &= ~(TIM_RAND_TARGET_STRETCH_MASK << TIM_RAND_TARGET_STRETCH_SHIFT); + val |= (TARGET_CLOCK_STRETCH_TIME << TIM_RAND_TARGET_STRETCH_SHIFT); + sys_write32(val, base + TIM_CFG_OFFSET); + + /* Set target address */ + ret = iproc_i2c_target_set_address(dev, target_config->address); + if (ret) { + return ret; + } + + /* clear all pending target interrupts */ + sys_write32(ISR_MASK_TARGET, base + IS_OFFSET); + + /* Enable interrupt register to indicate a valid byte in receive fifo */ + val = BIT(IE_S_RX_EVENT_SHIFT); + /* Enable interrupt register to indicate target Rx FIFO Full */ + val |= BIT(IE_S_RX_FIFO_FULL_SHIFT); + /* Enable interrupt register to indicate a Master read transaction */ + val |= BIT(IE_S_RD_EN_SHIFT); + /* Enable interrupt register for the target BUSY command */ + val |= BIT(IE_S_START_BUSY_SHIFT); + dd->target_int_mask = val; + sys_write32(val, base + IE_OFFSET); + + return ret; +} + +static int iproc_i2c_check_target_status(const struct device *dev) +{ + mem_addr_t base = DEV_BASE(dev); + uint32_t val; + + val = sys_read32(base + S_CMD_OFFSET); + /* status is valid only when START_BUSY is cleared after it was set */ + if (val & BIT(S_CMD_START_BUSY_SHIFT)) { + return -EBUSY; + } + + val = (val >> S_CMD_STATUS_SHIFT) & S_CMD_STATUS_MASK; + if ((val == S_CMD_STATUS_TIMEOUT) || (val == S_CMD_STATUS_MASTER_ABORT)) { + if (val == S_CMD_STATUS_TIMEOUT) { + LOG_ERR("target random stretch time timeout"); + } else if (val == S_CMD_STATUS_MASTER_ABORT) { + LOG_ERR("Master aborted read transaction"); + } + + /* re-initialize i2c for recovery */ + iproc_i2c_enable_disable(dev, false); + iproc_i2c_target_init(dev, true); + iproc_i2c_enable_disable(dev, true); + + return -ETIMEDOUT; + } + + return 0; +} + +static void iproc_i2c_target_read(const struct device *dev) +{ + struct iproc_i2c_data *dd = DEV_DATA(dev); + struct i2c_target_config *target_cfg = dd->target_cfg; + mem_addr_t base = DEV_BASE(dev); + uint8_t rx_data, rx_status; + uint32_t rx_bytes = 0; + uint32_t val; + + while (rx_bytes < MAX_TARGET_RX_PER_INT) { + val = sys_read32(base + S_RX_OFFSET); + rx_status = (val >> S_RX_STATUS_SHIFT) & S_RX_STATUS_MASK; + rx_data = ((val >> S_RX_DATA_SHIFT) & S_RX_DATA_MASK); + + if (rx_status == I2C_TARGET_RX_START) { + /* Start of SMBUS Master write */ + target_cfg->callbacks->write_requested(target_cfg); + dd->rx_start_rcvd = true; + dd->target_read_complete = false; + } else if ((rx_status == I2C_TARGET_RX_DATA) && dd->rx_start_rcvd) { + /* Middle of SMBUS Master write */ + target_cfg->callbacks->write_received(target_cfg, rx_data); + } else if ((rx_status == I2C_TARGET_RX_END) && dd->rx_start_rcvd) { + /* End of SMBUS Master write */ + if (dd->target_rx_only) { + target_cfg->callbacks->write_received(target_cfg, rx_data); + } + target_cfg->callbacks->stop(target_cfg); + } else if (rx_status == I2C_TARGET_RX_FIFO_EMPTY) { + dd->rx_start_rcvd = false; + dd->target_read_complete = true; + break; + } + + rx_bytes++; + } +} + +static void iproc_i2c_target_rx(const struct device *dev) +{ + struct iproc_i2c_data *dd = DEV_DATA(dev); + mem_addr_t base = DEV_BASE(dev); + + iproc_i2c_target_read(dev); + + if (!dd->target_rx_only && dd->target_read_complete) { + /* + * In case of single byte master-read request, + * IS_S_TX_UNDERRUN_SHIFT event is generated before + * IS_S_START_BUSY_SHIFT event. Hence start target data send + * from first IS_S_TX_UNDERRUN_SHIFT event. + * + * This means don't send any data from target when + * IS_S_RD_EN_SHIFT event is generated else it will increment + * eeprom or other backend target driver read pointer twice. + */ + dd->tx_underrun = 0; + dd->target_int_mask |= BIT(IE_S_TX_UNDERRUN_SHIFT); + + /* clear IS_S_RD_EN_SHIFT interrupt */ + sys_write32(BIT(IS_S_RD_EN_SHIFT), base + IS_OFFSET); + } + + /* enable target interrupts */ + sys_write32(dd->target_int_mask, base + IE_OFFSET); +} + +static void iproc_i2c_target_isr(const struct device *dev, uint32_t status) +{ + struct iproc_i2c_data *dd = DEV_DATA(dev); + struct i2c_target_config *target_cfg = dd->target_cfg; + mem_addr_t base = DEV_BASE(dev); + uint32_t val; + uint8_t data; + + LOG_DBG("iproc_i2c(0x%x): %s: sl_sts 0x%x", (uint32_t)base, __func__, status); + + if (status & BIT(IS_S_RX_EVENT_SHIFT) || status & BIT(IS_S_RD_EN_SHIFT) || + status & BIT(IS_S_RX_FIFO_FULL_SHIFT)) { + /* disable target interrupts */ + val = sys_read32(base + IE_OFFSET); + val &= ~dd->target_int_mask; + sys_write32(val, base + IE_OFFSET); + + if (status & BIT(IS_S_RD_EN_SHIFT)) { + /* Master-write-read request */ + dd->target_rx_only = false; + } else { + /* Master-write request only */ + dd->target_rx_only = true; + } + + /* + * Clear IS_S_RX_EVENT_SHIFT & + * IS_S_RX_FIFO_FULL_SHIFT interrupt + */ + val = BIT(IS_S_RX_EVENT_SHIFT); + if (status & BIT(IS_S_RX_FIFO_FULL_SHIFT)) { + val |= BIT(IS_S_RX_FIFO_FULL_SHIFT); + } + sys_write32(val, base + IS_OFFSET); + + iproc_i2c_target_rx(dev); + } + + if (status & BIT(IS_S_TX_UNDERRUN_SHIFT)) { + dd->tx_underrun++; + if (dd->tx_underrun == 1) { + /* Start of SMBUS for Master Read */ + target_cfg->callbacks->read_requested(target_cfg, &data); + } else { + /* Master read other than start */ + target_cfg->callbacks->read_processed(target_cfg, &data); + } + + sys_write32(data, base + S_TX_OFFSET); + /* start transfer */ + val = BIT(S_CMD_START_BUSY_SHIFT); + sys_write32(val, base + S_CMD_OFFSET); + + sys_write32(BIT(IS_S_TX_UNDERRUN_SHIFT), base + IS_OFFSET); + } + + /* Stop received from master in case of master read transaction */ + if (status & BIT(IS_S_START_BUSY_SHIFT)) { + /* + * Disable interrupt for TX FIFO becomes empty and + * less than PKT_LENGTH bytes were output on the SMBUS + */ + dd->target_int_mask &= ~BIT(IE_S_TX_UNDERRUN_SHIFT); + sys_write32(dd->target_int_mask, base + IE_OFFSET); + + /* End of SMBUS for Master Read */ + val = BIT(S_TX_WR_STATUS_SHIFT); + sys_write32(val, base + S_TX_OFFSET); + + val = BIT(S_CMD_START_BUSY_SHIFT); + sys_write32(val, base + S_CMD_OFFSET); + + /* flush TX FIFOs */ + val = sys_read32(base + S_FIFO_CTRL_OFFSET); + val |= (BIT(S_FIFO_TX_FLUSH_SHIFT)); + sys_write32(val, base + S_FIFO_CTRL_OFFSET); + + target_cfg->callbacks->stop(target_cfg); + + sys_write32(BIT(IS_S_START_BUSY_SHIFT), base + IS_OFFSET); + } + + /* check target transmit status only if target is transmitting */ + if (!dd->target_rx_only) { + iproc_i2c_check_target_status(dev); + } +} + +static int iproc_i2c_target_register(const struct device *dev, + struct i2c_target_config *target_config) +{ + struct iproc_i2c_data *dd = DEV_DATA(dev); + mem_addr_t base = DEV_BASE(dev); + int ret = 0; + + if (dd->target_cfg) { + LOG_ERR("Target already registered"); + return -EBUSY; + } + + /* Save pointer to received target config */ + dd->target_cfg = target_config; + + ret = iproc_i2c_target_init(dev, false); + if (ret < 0) { + LOG_ERR("Failed to register iproc_i2c(0x%x) as target, ret %d", (uint32_t)base, + ret); + return ret; + } + + return 0; +} + +static int iproc_i2c_target_unregister(const struct device *dev, struct i2c_target_config *config) +{ + uint32_t val; + mem_addr_t base = DEV_BASE(dev); + struct iproc_i2c_data *dd = DEV_DATA(dev); + + if (!dd->target_cfg) { + return -EINVAL; + } + + /* Erase the target address programmed */ + sys_write32(0x0, base + S_ADDR_OFFSET); + + /* disable all target interrupts */ + val = sys_read32(base + IE_OFFSET); + val &= ~(IE_S_ALL_INTERRUPT_MASK << IE_S_ALL_INTERRUPT_SHIFT); + sys_write32(val, base + IE_OFFSET); + + dd->target_cfg = NULL; + + return 0; +} +#endif /* CONFIG_I2C_TARGET */ + +static void iproc_i2c_common_init(const struct device *dev) +{ + mem_addr_t base = DEV_BASE(dev); + uint32_t val; + + iproc_i2c_reset_controller(dev); + + /* flush TX/RX FIFOs and set RX FIFO threshold to zero */ + val = BIT(M_FIFO_RX_FLUSH_SHIFT) | BIT(M_FIFO_TX_FLUSH_SHIFT); + sys_write32(val, base + M_FIFO_CTRL_OFFSET); + + /* disable all interrupts */ + sys_write32(0, base + IE_OFFSET); + + /* clear all pending interrupts */ + sys_write32(~0, base + IS_OFFSET); +} + +static int iproc_i2c_check_status(const struct device *dev, uint16_t dev_addr, struct i2c_msg *msg) +{ + mem_addr_t base = DEV_BASE(dev); + uint32_t val; + int rc; + + val = sys_read32(base + M_CMD_OFFSET); + val >>= M_CMD_STATUS_SHIFT; + val &= M_CMD_STATUS_MASK; + + switch (val) { + case M_CMD_STATUS_SUCCESS: + rc = 0; + break; + + case M_CMD_STATUS_LOST_ARB: + LOG_ERR("lost bus arbitration"); + rc = -EAGAIN; + break; + + case M_CMD_STATUS_NACK_ADDR: + LOG_ERR("NAK addr:0x%02x", dev_addr); + rc = -ENXIO; + break; + + case M_CMD_STATUS_NACK_DATA: + LOG_ERR("NAK data"); + rc = -ENXIO; + break; + + case M_CMD_STATUS_TIMEOUT: + LOG_ERR("bus timeout"); + rc = -ETIMEDOUT; + break; + + case M_CMD_STATUS_FIFO_UNDERRUN: + LOG_ERR("FIFO Under-run"); + rc = -ENXIO; + break; + + case M_CMD_STATUS_RX_FIFO_FULL: + LOG_ERR("RX FIFO full"); + rc = -ETIMEDOUT; + break; + + default: + LOG_ERR("Unknown Error : 0x%x", val); + iproc_i2c_enable_disable(dev, false); + iproc_i2c_common_init(dev); + iproc_i2c_enable_disable(dev, true); + rc = -EIO; + break; + } + + if (rc < 0) { + /* flush both Master TX/RX FIFOs */ + val = BIT(M_FIFO_RX_FLUSH_SHIFT) | BIT(M_FIFO_TX_FLUSH_SHIFT); + sys_write32(val, base + M_FIFO_CTRL_OFFSET); + } + + return rc; +} + +static int iproc_i2c_configure(const struct device *dev, uint32_t dev_cfg_raw) +{ + mem_addr_t base = DEV_BASE(dev); + + if (I2C_ADDR_10_BITS & dev_cfg_raw) { + LOG_ERR("10-bit addressing not supported"); + return -ENOTSUP; + } + + switch (I2C_SPEED_GET(dev_cfg_raw)) { + case I2C_SPEED_STANDARD: + sys_clear_bit(base + TIM_CFG_OFFSET, TIM_CFG_MODE_400_SHIFT); + break; + case I2C_SPEED_FAST: + sys_set_bit(base + TIM_CFG_OFFSET, TIM_CFG_MODE_400_SHIFT); + break; + default: + LOG_ERR("Only standard or Fast speed modes are supported"); + return -ENOTSUP; + } + + return 0; +} + +static void iproc_i2c_read_valid_bytes(const struct device *dev) +{ + mem_addr_t base = DEV_BASE(dev); + struct iproc_i2c_data *dd = DEV_DATA(dev); + struct i2c_msg *msg = dd->msg; + uint32_t val; + + /* Read valid data from RX FIFO */ + while (dd->rx_bytes < msg->len) { + val = sys_read32(base + M_RX_OFFSET); + + /* rx fifo empty */ + if (!((val >> M_RX_STATUS_SHIFT) & M_RX_STATUS_MASK)) { + break; + } + + msg->buf[dd->rx_bytes] = (val >> M_RX_DATA_SHIFT) & M_RX_DATA_MASK; + dd->rx_bytes++; + } +} + +static int iproc_i2c_data_recv(const struct device *dev) +{ + struct iproc_i2c_data *dd = DEV_DATA(dev); + mem_addr_t base = DEV_BASE(dev); + struct i2c_msg *msg = dd->msg; + uint32_t bytes_left, val; + + iproc_i2c_read_valid_bytes(dev); + + bytes_left = msg->len - dd->rx_bytes; + if (bytes_left == 0) { + /* finished reading all data, disable rx thld event */ + sys_clear_bit(base + IE_OFFSET, IS_M_RX_THLD_SHIFT); + } else if (bytes_left < dd->thld_bytes) { + /* set bytes left as threshold */ + val = sys_read32(base + M_FIFO_CTRL_OFFSET); + val &= ~(M_FIFO_RX_THLD_MASK << M_FIFO_RX_THLD_SHIFT); + val |= (bytes_left << M_FIFO_RX_THLD_SHIFT); + sys_write32(val, base + M_FIFO_CTRL_OFFSET); + dd->thld_bytes = bytes_left; + } + /* + * if bytes_left >= dd->thld_bytes, no need to change the THRESHOLD. + * It will remain as dd->thld_bytes itself + */ + + return 0; +} + +static int iproc_i2c_transfer_one(const struct device *dev, struct i2c_msg *msg, uint16_t dev_addr) +{ + mem_addr_t base = DEV_BASE(dev); + struct iproc_i2c_data *dd = DEV_DATA(dev); + uint32_t val, addr, tx_bytes, val_intr_en; + int rc; + + if (!!(sys_read32(base + M_CMD_OFFSET) & BIT(M_CMD_START_BUSY_SHIFT))) { + LOG_ERR("Bus busy, prev xfer ongoing"); + return -EBUSY; + } + + LOG_DBG("%s: msg dev_addr 0x%x flags 0x%x len 0x%x val 0x%x\n", __func__, dev_addr, + msg->flags, msg->len, msg->buf[0]); + + /* Save current i2c_msg */ + dd->msg = msg; + + addr = dev_addr << 1 | (msg->flags & I2C_MSG_READ ? 1 : 0); + sys_write32(addr, base + M_TX_OFFSET); + + tx_bytes = MIN(msg->len, (TX_RX_FIFO_SIZE - 1)); + if (!(msg->flags & I2C_MSG_READ)) { + /* Fill master TX fifo */ + for (uint32_t i = 0; i < tx_bytes; i++) { + val = msg->buf[i]; + /* For the last byte, set MASTER_WR_STATUS bit */ + if (i == msg->len - 1) { + val |= BIT(M_TX_WR_STATUS_SHIFT); + } + sys_write32(val, base + M_TX_OFFSET); + } + + dd->tx_bytes = tx_bytes; + } + + /* + * Enable the "start busy" interrupt, which will be triggered after the + * transaction is done, i.e., the internal start_busy bit, transitions + * from 1 to 0. + */ + val_intr_en = BIT(IE_M_START_BUSY_SHIFT); + + if (!(msg->flags & I2C_MSG_READ) && (msg->len > dd->tx_bytes)) { + val_intr_en |= BIT(IE_M_TX_UNDERRUN_SHIFT); + } + + /* + * Program master command register (0x30) with protocol type and set + * start_busy_command bit to initiate the write transaction + */ + val = BIT(M_CMD_START_BUSY_SHIFT); + if (msg->len == 0) { + /* SMBUS QUICK Command (Read/Write) */ + val |= (M_CMD_SMB_PROT_QUICK << M_CMD_SMB_PROT_SHIFT); + } else if (msg->flags & I2C_MSG_READ) { + uint32_t tmp; + + dd->rx_bytes = 0; + + /* SMBUS Block Read Command */ + val |= M_CMD_SMB_PROT_BLK_RD << M_CMD_SMB_PROT_SHIFT; + val |= msg->len; + + if (msg->len > M_RX_FIFO_MAX_THLD_VALUE) { + dd->thld_bytes = M_RX_FIFO_THLD_VALUE; + } else { + dd->thld_bytes = msg->len; + } + + /* set threshold value */ + tmp = sys_read32(base + M_FIFO_CTRL_OFFSET); + tmp &= ~(M_FIFO_RX_THLD_MASK << M_FIFO_RX_THLD_SHIFT); + tmp |= dd->thld_bytes << M_FIFO_RX_THLD_SHIFT; + sys_write32(tmp, base + M_FIFO_CTRL_OFFSET); + + /* enable the RX threshold interrupt */ + val_intr_en |= BIT(IE_M_RX_THLD_SHIFT); + } else { + /* SMBUS Block Write Command */ + val |= M_CMD_SMB_PROT_BLK_WR << M_CMD_SMB_PROT_SHIFT; + } + + sys_write32(val_intr_en, base + IE_OFFSET); + + sys_write32(val, base + M_CMD_OFFSET); + + /* Wait for the transfer to complete or timeout */ + rc = k_sem_take(&dd->device_sync_sem, K_MSEC(I2C_TIMEOUT_MSEC)); + + /* disable all interrupts */ + sys_write32(0, base + IE_OFFSET); + + if (rc != 0) { + /* flush both Master TX/RX FIFOs */ + val = BIT(M_FIFO_RX_FLUSH_SHIFT) | BIT(M_FIFO_TX_FLUSH_SHIFT); + sys_write32(val, base + M_FIFO_CTRL_OFFSET); + return rc; + } + + /* Check for Master Xfer status */ + rc = iproc_i2c_check_status(dev, dev_addr, msg); + + return rc; +} + +static int iproc_i2c_transfer_multi(const struct device *dev, struct i2c_msg *msgs, + uint8_t num_msgs, uint16_t addr) +{ + int rc; + struct i2c_msg *msgs_chk = msgs; + + if (!msgs_chk) { + return -EINVAL; + } + + /* pre-check all msgs */ + for (uint8_t i = 0; i < num_msgs; i++, msgs_chk++) { + if (!msgs_chk->buf) { + LOG_ERR("Invalid msg buffer"); + return -EINVAL; + } + + if (I2C_MSG_ADDR_10_BITS & msgs_chk->flags) { + LOG_ERR("10-bit addressing not supported"); + return -ENOTSUP; + } + } + + for (uint8_t i = 0; i < num_msgs; i++, msgs++) { + rc = iproc_i2c_transfer_one(dev, msgs, addr); + if (rc < 0) { + return rc; + } + } + + return 0; +} + +static void iproc_i2c_send_data(const struct device *dev) +{ + mem_addr_t base = DEV_BASE(dev); + struct iproc_i2c_data *dd = DEV_DATA(dev); + struct i2c_msg *msg = dd->msg; + uint32_t tx_bytes = msg->len - dd->tx_bytes; + + /* can only fill up to the FIFO size */ + tx_bytes = MIN(tx_bytes, TX_RX_FIFO_SIZE); + for (uint32_t i = 0; i < tx_bytes; i++) { + /* start from where we left over */ + uint32_t idx = dd->tx_bytes + i; + + uint32_t val = msg->buf[idx]; + + /* mark the last byte */ + if (idx == (msg->len - 1)) { + uint32_t tmp; + + val |= BIT(M_TX_WR_STATUS_SHIFT); + + /* + * Since this is the last byte, we should now + * disable TX FIFO underrun interrupt + */ + tmp = sys_read32(base + IE_OFFSET); + tmp &= ~BIT(IE_M_TX_UNDERRUN_SHIFT); + sys_write32(tmp, base + IE_OFFSET); + } + + /* load data into TX FIFO */ + sys_write32(val, base + M_TX_OFFSET); + } + + /* update number of transferred bytes */ + dd->tx_bytes += tx_bytes; +} + +static void iproc_i2c_master_isr(const struct device *dev, uint32_t status) +{ + struct iproc_i2c_data *dd = DEV_DATA(dev); + + /* TX FIFO is empty and we have more data to send */ + if (status & BIT(IS_M_TX_UNDERRUN_SHIFT)) { + iproc_i2c_send_data(dev); + } + + /* RX FIFO threshold is reached and data needs to be read out */ + if (status & BIT(IS_M_RX_THLD_SHIFT)) { + iproc_i2c_data_recv(dev); + } + + /* transfer is done */ + if (status & BIT(IS_M_START_BUSY_SHIFT)) { + k_sem_give(&dd->device_sync_sem); + } +} + +static void iproc_i2c_isr(void *arg) +{ + const struct device *dev = (const struct device *)arg; + mem_addr_t base = DEV_BASE(dev); + uint32_t status; + uint32_t sl_status, curr_irqs; + + curr_irqs = sys_read32(base + IE_OFFSET); + status = sys_read32(base + IS_OFFSET); + + /* process only target interrupt which are enabled */ + sl_status = status & curr_irqs & ISR_MASK_TARGET; + LOG_DBG("iproc_i2c(0x%x): sts 0x%x, sl_sts 0x%x, curr_ints 0x%x", (uint32_t)base, status, + sl_status, curr_irqs); + +#ifdef CONFIG_I2C_TARGET + /* target events */ + if (sl_status) { + iproc_i2c_target_isr(dev, sl_status); + return; + } +#endif + + status &= ISR_MASK; + /* master events */ + if (status) { + iproc_i2c_master_isr(dev, status); + sys_write32(status, base + IS_OFFSET); + } +} + +static int iproc_i2c_init(const struct device *dev) +{ + const struct iproc_i2c_config *config = DEV_CFG(dev); + struct iproc_i2c_data *dd = DEV_DATA(dev); + uint32_t bitrate = config->bitrate; + int error; + + k_sem_init(&dd->device_sync_sem, 0, 1); + + iproc_i2c_common_init(dev); + + /* Set default clock frequency */ + bitrate = i2c_map_dt_bitrate(bitrate); + + if (dd->target_cfg == NULL) { + bitrate |= I2C_MODE_CONTROLLER; + } + + error = iproc_i2c_configure(dev, bitrate); + if (error) { + return error; + } + + config->irq_config_func(dev); + + iproc_i2c_enable_disable(dev, true); + + return 0; +} + +static const struct i2c_driver_api iproc_i2c_driver_api = { + .configure = iproc_i2c_configure, + .transfer = iproc_i2c_transfer_multi, +#ifdef CONFIG_I2C_TARGET + .target_register = iproc_i2c_target_register, + .target_unregister = iproc_i2c_target_unregister, +#endif +}; + +#define IPROC_I2C_DEVICE_INIT(n) \ + static void iproc_i2c_irq_config_func_##n(const struct device *dev) \ + { \ + ARG_UNUSED(dev); \ + IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), iproc_i2c_isr, \ + DEVICE_DT_INST_GET(n), 0); \ + \ + irq_enable(DT_INST_IRQN(n)); \ + } \ + \ + static const struct iproc_i2c_config iproc_i2c_config_##n = { \ + .base = DT_INST_REG_ADDR(n), \ + .irq_config_func = iproc_i2c_irq_config_func_##n, \ + .bitrate = DT_INST_PROP(n, clock_frequency), \ + }; \ + \ + static struct iproc_i2c_data iproc_i2c_data_##n; \ + \ + I2C_DEVICE_DT_INST_DEFINE(n, &iproc_i2c_init, NULL, &iproc_i2c_data_##n, \ + &iproc_i2c_config_##n, POST_KERNEL, CONFIG_I2C_INIT_PRIORITY, \ + &iproc_i2c_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(IPROC_I2C_DEVICE_INIT) diff --git a/dts/bindings/i2c/brcm,iproc-i2c.yaml b/dts/bindings/i2c/brcm,iproc-i2c.yaml new file mode 100644 index 00000000000..343674488f4 --- /dev/null +++ b/dts/bindings/i2c/brcm,iproc-i2c.yaml @@ -0,0 +1,16 @@ +# Copyright 2020 Broadcom +# Copyright 2024 Meta Platforms +# SPDX-License-Identifier: Apache-2.0 + +description: Broadcom iProc I2C controller + +compatible: "brcm,iproc-i2c" + +include: i2c-controller.yaml + +properties: + reg: + required: true + + interrupts: + required: true diff --git a/tests/drivers/build_all/i2c/boards/qemu_cortex_m3.overlay b/tests/drivers/build_all/i2c/boards/qemu_cortex_m3.overlay index 1abc687904d..b041b987dd1 100644 --- a/tests/drivers/build_all/i2c/boards/qemu_cortex_m3.overlay +++ b/tests/drivers/build_all/i2c/boards/qemu_cortex_m3.overlay @@ -4,6 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ + #include + / { i2c0: i2c@88888888 { compatible = "xlnx,xps-iic-2.00.a"; @@ -22,4 +24,14 @@ interrupt-parent = <&nvic>; interrupts = <5 1>; }; + + i2c2: i2c@888A8888 { + compatible = "brcm,iproc-i2c"; + clock-frequency = ; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x888A8888 0x100>; + interrupt-parent = <&nvic>; + interrupts = <6 1>; + }; };