diff options
Diffstat (limited to 'lib/utils/i2c/dw_i2c.c')
-rw-r--r-- | lib/utils/i2c/dw_i2c.c | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/lib/utils/i2c/dw_i2c.c b/lib/utils/i2c/dw_i2c.c new file mode 100644 index 0000000..e2ffc71 --- /dev/null +++ b/lib/utils/i2c/dw_i2c.c @@ -0,0 +1,190 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 starfivetech.com + * + * Authors: + * Minda Chen <minda.chen@starfivetech.com> + */ + +#include <sbi/riscv_io.h> +#include <sbi/sbi_error.h> +#include <sbi/sbi_timer.h> +#include <sbi/sbi_console.h> +#include <sbi/sbi_string.h> +#include <sbi/sbi_bitops.h> +#include <sbi_utils/i2c/dw_i2c.h> + +#define DW_IC_CON 0x00 +#define DW_IC_TAR 0x04 +#define DW_IC_SAR 0x08 +#define DW_IC_DATA_CMD 0x10 +#define DW_IC_SS_SCL_HCNT 0x14 +#define DW_IC_SS_SCL_LCNT 0x18 +#define DW_IC_FS_SCL_HCNT 0x1c +#define DW_IC_FS_SCL_LCNT 0x20 +#define DW_IC_HS_SCL_HCNT 0x24 +#define DW_IC_HS_SCL_LCNT 0x28 +#define DW_IC_INTR_STAT 0x2c +#define DW_IC_INTR_MASK 0x30 +#define DW_IC_RAW_INTR_STAT 0x34 +#define DW_IC_RX_TL 0x38 +#define DW_IC_TX_TL 0x3c +#define DW_IC_CLR_INTR 0x40 +#define DW_IC_CLR_RX_UNDER 0x44 +#define DW_IC_CLR_RX_OVER 0x48 +#define DW_IC_CLR_TX_OVER 0x4c +#define DW_IC_CLR_RD_REQ 0x50 +#define DW_IC_CLR_TX_ABRT 0x54 +#define DW_IC_CLR_RX_DONE 0x58 +#define DW_IC_CLR_ACTIVITY 0x5c +#define DW_IC_CLR_STOP_DET 0x60 +#define DW_IC_CLR_START_DET 0x64 +#define DW_IC_CLR_GEN_CALL 0x68 +#define DW_IC_ENABLE 0x6c +#define DW_IC_STATUS 0x70 +#define DW_IC_TXFLR 0x74 +#define DW_IC_RXFLR 0x78 +#define DW_IC_SDA_HOLD 0x7c +#define DW_IC_TX_ABRT_SOURCE 0x80 +#define DW_IC_ENABLE_STATUS 0x9c +#define DW_IC_CLR_RESTART_DET 0xa8 +#define DW_IC_COMP_PARAM_1 0xf4 +#define DW_IC_COMP_VERSION 0xf8 + +#define DW_I2C_STATUS_TXFIFO_EMPTY BIT(2) +#define DW_I2C_STATUS_RXFIFO_NOT_EMPTY BIT(3) + +#define IC_DATA_CMD_READ BIT(8) +#define IC_DATA_CMD_STOP BIT(9) +#define IC_DATA_CMD_RESTART BIT(10) +#define IC_INT_STATUS_STOPDET BIT(9) + +static inline void dw_i2c_setreg(struct dw_i2c_adapter *adap, + u8 reg, u32 value) +{ + writel(value, (void *)adap->addr + reg); +} + +static inline u32 dw_i2c_getreg(struct dw_i2c_adapter *adap, + u32 reg) +{ + return readl((void *)adap->addr + reg); +} + +static int dw_i2c_adapter_poll(struct dw_i2c_adapter *adap, + u32 mask, u32 addr, + bool inverted) +{ + unsigned int timeout = 10; /* msec */ + int count = 0; + u32 val; + + do { + val = dw_i2c_getreg(adap, addr); + if (inverted) { + if (!(val & mask)) + return 0; + } else { + if (val & mask) + return 0; + } + sbi_timer_udelay(2); + count += 1; + if (count == (timeout * 1000)) + return SBI_ETIMEDOUT; + } while (1); +} + +#define dw_i2c_adapter_poll_rxrdy(adap) \ + dw_i2c_adapter_poll(adap, DW_I2C_STATUS_RXFIFO_NOT_EMPTY, DW_IC_STATUS, 0) +#define dw_i2c_adapter_poll_txfifo_ready(adap) \ + dw_i2c_adapter_poll(adap, DW_I2C_STATUS_TXFIFO_EMPTY, DW_IC_STATUS, 0) + +static int dw_i2c_write_addr(struct dw_i2c_adapter *adap, u8 addr) +{ + dw_i2c_setreg(adap, DW_IC_ENABLE, 0); + dw_i2c_setreg(adap, DW_IC_TAR, addr); + dw_i2c_setreg(adap, DW_IC_ENABLE, 1); + + return 0; +} + +static int dw_i2c_adapter_read(struct i2c_adapter *ia, u8 addr, + u8 reg, u8 *buffer, int len) +{ + struct dw_i2c_adapter *adap = + container_of(ia, struct dw_i2c_adapter, adapter); + int rc; + + dw_i2c_write_addr(adap, addr); + + rc = dw_i2c_adapter_poll_txfifo_ready(adap); + if (rc) + return rc; + + /* set register address */ + dw_i2c_setreg(adap, DW_IC_DATA_CMD, reg); + + /* set value */ + while (len) { + if (len == 1) + dw_i2c_setreg(adap, DW_IC_DATA_CMD, + IC_DATA_CMD_READ | IC_DATA_CMD_STOP); + else + dw_i2c_setreg(adap, DW_IC_DATA_CMD, IC_DATA_CMD_READ); + + rc = dw_i2c_adapter_poll_rxrdy(adap); + if (rc) + return rc; + + *buffer = dw_i2c_getreg(adap, DW_IC_DATA_CMD) & 0xff; + buffer++; + len--; + } + + return 0; +} + +static int dw_i2c_adapter_write(struct i2c_adapter *ia, u8 addr, + u8 reg, u8 *buffer, int len) +{ + struct dw_i2c_adapter *adap = + container_of(ia, struct dw_i2c_adapter, adapter); + int rc; + + dw_i2c_write_addr(adap, addr); + + rc = dw_i2c_adapter_poll_txfifo_ready(adap); + if (rc) + return rc; + + /* set register address */ + dw_i2c_setreg(adap, DW_IC_DATA_CMD, reg); + + while (len) { + rc = dw_i2c_adapter_poll_txfifo_ready(adap); + if (rc) + return rc; + + if (len == 1) + dw_i2c_setreg(adap, DW_IC_DATA_CMD, *buffer | IC_DATA_CMD_STOP); + else + dw_i2c_setreg(adap, DW_IC_DATA_CMD, *buffer); + + buffer++; + len--; + } + rc = dw_i2c_adapter_poll_txfifo_ready(adap); + + return rc; +} + +int dw_i2c_init(struct i2c_adapter *adapter, int nodeoff) +{ + adapter->id = nodeoff; + adapter->write = dw_i2c_adapter_write; + adapter->read = dw_i2c_adapter_read; + + return i2c_adapter_add(adapter); +} |