blob: e2ffc71259a85102d254b465912677019996f4e7 [file] [log] [blame]
/*
* 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);
}