/* * 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 DEVICE_API(i2c, 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 #ifdef CONFIG_I2C_RTIO .iodev_submit = i2c_iodev_submit_fallback, #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)