You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
953 lines
26 KiB
953 lines
26 KiB
/* |
|
* Copyright 2020 Broadcom |
|
* Copyright 2024 Meta Platforms |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT brcm_iproc_i2c |
|
|
|
#include <errno.h> |
|
#include <stdint.h> |
|
|
|
#include <zephyr/drivers/i2c.h> |
|
#include <zephyr/sys/util.h> |
|
#include <zephyr/logging/log.h> |
|
|
|
#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)
|
|
|