blob: fe8bc52eb2be76102db0d6fce11fc7ea32c03bc8 [file] [log] [blame]
/*
* Copyright (C) 2018 Marvell International Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
* https://spdx.org/licenses
*/
/* This driver provides I2C support for Marvell A8K and compatible SoCs */
#include <a8k_i2c.h>
#include <debug.h>
#include <delay_timer.h>
#include <errno.h>
#include <mmio.h>
#include <mvebu_def.h>
#if LOG_LEVEL >= LOG_LEVEL_VERBOSE
#define DEBUG_I2C
#endif
#define CONFIG_SYS_TCLK 250000000
#define CONFIG_SYS_I2C_SPEED 100000
#define CONFIG_SYS_I2C_SLAVE 0x0
#define I2C_TIMEOUT_VALUE 0x500
#define I2C_MAX_RETRY_CNT 1000
#define I2C_CMD_WRITE 0x0
#define I2C_CMD_READ 0x1
#define I2C_DATA_ADDR_7BIT_OFFS 0x1
#define I2C_DATA_ADDR_7BIT_MASK (0xFF << I2C_DATA_ADDR_7BIT_OFFS)
#define I2C_CONTROL_ACK 0x00000004
#define I2C_CONTROL_IFLG 0x00000008
#define I2C_CONTROL_STOP 0x00000010
#define I2C_CONTROL_START 0x00000020
#define I2C_CONTROL_TWSIEN 0x00000040
#define I2C_CONTROL_INTEN 0x00000080
#define I2C_STATUS_START 0x08
#define I2C_STATUS_REPEATED_START 0x10
#define I2C_STATUS_ADDR_W_ACK 0x18
#define I2C_STATUS_DATA_W_ACK 0x28
#define I2C_STATUS_LOST_ARB_DATA_ADDR_TRANSFER 0x38
#define I2C_STATUS_ADDR_R_ACK 0x40
#define I2C_STATUS_DATA_R_ACK 0x50
#define I2C_STATUS_DATA_R_NAK 0x58
#define I2C_STATUS_LOST_ARB_GENERAL_CALL 0x78
#define I2C_STATUS_IDLE 0xF8
#define I2C_UNSTUCK_TRIGGER 0x1
#define I2C_UNSTUCK_ONGOING 0x2
#define I2C_UNSTUCK_ERROR 0x4
struct marvell_i2c_regs {
uint32_t slave_address;
uint32_t data;
uint32_t control;
union {
uint32_t status; /* when reading */
uint32_t baudrate; /* when writing */
};
uint32_t xtnd_slave_addr;
uint32_t reserved[2];
uint32_t soft_reset;
uint8_t reserved2[0xa0 - 0x20];
uint32_t unstuck;
};
static struct marvell_i2c_regs *base;
static int marvell_i2c_lost_arbitration(uint32_t *status)
{
*status = mmio_read_32((uintptr_t)&base->status);
if ((*status == I2C_STATUS_LOST_ARB_DATA_ADDR_TRANSFER) ||
(*status == I2C_STATUS_LOST_ARB_GENERAL_CALL))
return -EAGAIN;
return 0;
}
static void marvell_i2c_interrupt_clear(void)
{
uint32_t reg;
reg = mmio_read_32((uintptr_t)&base->control);
reg &= ~(I2C_CONTROL_IFLG);
mmio_write_32((uintptr_t)&base->control, reg);
/* Wait for 1 us for the clear to take effect */
udelay(1);
}
static int marvell_i2c_interrupt_get(void)
{
uint32_t reg;
/* get the interrupt flag bit */
reg = mmio_read_32((uintptr_t)&base->control);
reg &= I2C_CONTROL_IFLG;
return reg && I2C_CONTROL_IFLG;
}
static int marvell_i2c_wait_interrupt(void)
{
uint32_t timeout = 0;
while (!marvell_i2c_interrupt_get() && (timeout++ < I2C_TIMEOUT_VALUE))
;
if (timeout >= I2C_TIMEOUT_VALUE)
return -ETIMEDOUT;
return 0;
}
static int marvell_i2c_start_bit_set(void)
{
int is_int_flag = 0;
uint32_t status;
if (marvell_i2c_interrupt_get())
is_int_flag = 1;
/* set start bit */
mmio_write_32((uintptr_t)&base->control,
mmio_read_32((uintptr_t)&base->control) |
I2C_CONTROL_START);
/* in case that the int flag was set before i.e. repeated start bit */
if (is_int_flag) {
VERBOSE("%s: repeated start Bit\n", __func__);
marvell_i2c_interrupt_clear();
}
if (marvell_i2c_wait_interrupt()) {
ERROR("Start clear bit timeout\n");
return -ETIMEDOUT;
}
/* check that start bit went down */
if ((mmio_read_32((uintptr_t)&base->control) &
I2C_CONTROL_START) != 0) {
ERROR("Start bit didn't went down\n");
return -EPERM;
}
/* check the status */
if (marvell_i2c_lost_arbitration(&status)) {
ERROR("%s - %d: Lost arbitration, got status %x\n",
__func__, __LINE__, status);
return -EAGAIN;
}
if ((status != I2C_STATUS_START) &&
(status != I2C_STATUS_REPEATED_START)) {
ERROR("Got status %x after enable start bit.\n", status);
return -EPERM;
}
return 0;
}
static int marvell_i2c_stop_bit_set(void)
{
int timeout;
uint32_t status;
/* Generate stop bit */
mmio_write_32((uintptr_t)&base->control,
mmio_read_32((uintptr_t)&base->control) |
I2C_CONTROL_STOP);
marvell_i2c_interrupt_clear();
timeout = 0;
/* Read control register, check the control stop bit */
while ((mmio_read_32((uintptr_t)&base->control) & I2C_CONTROL_STOP) &&
(timeout++ < I2C_TIMEOUT_VALUE))
;
if (timeout >= I2C_TIMEOUT_VALUE) {
ERROR("Stop bit didn't went down\n");
return -ETIMEDOUT;
}
/* check that stop bit went down */
if ((mmio_read_32((uintptr_t)&base->control) & I2C_CONTROL_STOP) != 0) {
ERROR("Stop bit didn't went down\n");
return -EPERM;
}
/* check the status */
if (marvell_i2c_lost_arbitration(&status)) {
ERROR("%s - %d: Lost arbitration, got status %x\n",
__func__, __LINE__, status);
return -EAGAIN;
}
if (status != I2C_STATUS_IDLE) {
ERROR("Got status %x after enable stop bit.\n", status);
return -EPERM;
}
return 0;
}
static int marvell_i2c_address_set(uint8_t chain, int command)
{
uint32_t reg, status;
reg = (chain << I2C_DATA_ADDR_7BIT_OFFS) & I2C_DATA_ADDR_7BIT_MASK;
reg |= command;
mmio_write_32((uintptr_t)&base->data, reg);
udelay(1);
marvell_i2c_interrupt_clear();
if (marvell_i2c_wait_interrupt()) {
ERROR("Start clear bit timeout\n");
return -ETIMEDOUT;
}
/* check the status */
if (marvell_i2c_lost_arbitration(&status)) {
ERROR("%s - %d: Lost arbitration, got status %x\n",
__func__, __LINE__, status);
return -EAGAIN;
}
if (((status != I2C_STATUS_ADDR_R_ACK) && (command == I2C_CMD_READ)) ||
((status != I2C_STATUS_ADDR_W_ACK) && (command == I2C_CMD_WRITE))) {
/* only in debug, since in boot we try to read the SPD
* of both DRAM, and we don't want error messages in cas
* DIMM doesn't exist.
*/
INFO("%s: ERROR - status %x addr in %s mode.\n", __func__,
status, (command == I2C_CMD_WRITE) ? "Write" : "Read");
return -EPERM;
}
return 0;
}
/*
* The I2C module contains a clock divider to generate the SCL clock.
* This function calculates and sets the <N> and <M> fields in the I2C Baud
* Rate Register (t=01) to obtain given 'requested_speed'.
* The requested_speed will be equal to:
* CONFIG_SYS_TCLK / (10 * (M + 1) * (2 << N))
* Where M is the value represented by bits[6:3] and N is the value represented
* by bits[2:0] of "I2C Baud Rate Register".
* Therefore max M which can be set is 16 (2^4) and max N is 8 (2^3). So the
* lowest possible baudrate is:
* CONFIG_SYS_TCLK/(10 * (16 +1) * (2 << 8), which equals to:
* CONFIG_SYS_TCLK/87040. Assuming that CONFIG_SYS_TCLK=250MHz, the lowest
* possible frequency is ~2,872KHz.
*/
static unsigned int marvell_i2c_bus_speed_set(unsigned int requested_speed)
{
unsigned int n, m, freq, margin, min_margin = 0xffffffff;
unsigned int actual_n = 0, actual_m = 0;
int val;
/* Calculate N and M for the TWSI clock baud rate */
for (n = 0; n < 8; n++) {
for (m = 0; m < 16; m++) {
freq = CONFIG_SYS_TCLK / (10 * (m + 1) * (2 << n));
val = requested_speed - freq;
margin = (val > 0) ? val : -val;
if ((freq <= requested_speed) &&
(margin < min_margin)) {
min_margin = margin;
actual_n = n;
actual_m = m;
}
}
}
VERBOSE("%s: actual_n = %u, actual_m = %u\n",
__func__, actual_n, actual_m);
/* Set the baud rate */
mmio_write_32((uintptr_t)&base->baudrate, (actual_m << 3) | actual_n);
return 0;
}
#ifdef DEBUG_I2C
static int marvell_i2c_probe(uint8_t chip)
{
int ret = 0;
ret = marvell_i2c_start_bit_set();
if (ret != 0) {
marvell_i2c_stop_bit_set();
ERROR("%s - %d: %s", __func__, __LINE__,
"marvell_i2c_start_bit_set failed\n");
return -EPERM;
}
ret = marvell_i2c_address_set(chip, I2C_CMD_WRITE);
if (ret != 0) {
marvell_i2c_stop_bit_set();
ERROR("%s - %d: %s", __func__, __LINE__,
"marvell_i2c_address_set failed\n");
return -EPERM;
}
marvell_i2c_stop_bit_set();
VERBOSE("%s: successful I2C probe\n", __func__);
return ret;
}
#endif
/* regular i2c transaction */
static int marvell_i2c_data_receive(uint8_t *p_block, uint32_t block_size)
{
uint32_t reg, status, block_size_read = block_size;
/* Wait for cause interrupt */
if (marvell_i2c_wait_interrupt()) {
ERROR("Start clear bit timeout\n");
return -ETIMEDOUT;
}
while (block_size_read) {
if (block_size_read == 1) {
reg = mmio_read_32((uintptr_t)&base->control);
reg &= ~(I2C_CONTROL_ACK);
mmio_write_32((uintptr_t)&base->control, reg);
}
marvell_i2c_interrupt_clear();
if (marvell_i2c_wait_interrupt()) {
ERROR("Start clear bit timeout\n");
return -ETIMEDOUT;
}
/* check the status */
if (marvell_i2c_lost_arbitration(&status)) {
ERROR("%s - %d: Lost arbitration, got status %x\n",
__func__, __LINE__, status);
return -EAGAIN;
}
if ((status != I2C_STATUS_DATA_R_ACK) &&
(block_size_read != 1)) {
ERROR("Status %x in read transaction\n", status);
return -EPERM;
}
if ((status != I2C_STATUS_DATA_R_NAK) &&
(block_size_read == 1)) {
ERROR("Status %x in Rd Terminate\n", status);
return -EPERM;
}
/* read the data */
*p_block = (uint8_t) mmio_read_32((uintptr_t)&base->data);
VERBOSE("%s: place %d read %x\n", __func__,
block_size - block_size_read, *p_block);
p_block++;
block_size_read--;
}
return 0;
}
static int marvell_i2c_data_transmit(uint8_t *p_block, uint32_t block_size)
{
uint32_t status, block_size_write = block_size;
if (marvell_i2c_wait_interrupt()) {
ERROR("Start clear bit timeout\n");
return -ETIMEDOUT;
}
while (block_size_write) {
/* write the data */
mmio_write_32((uintptr_t)&base->data, (uint32_t) *p_block);
VERBOSE("%s: index = %d, data = %x\n", __func__,
block_size - block_size_write, *p_block);
p_block++;
block_size_write--;
marvell_i2c_interrupt_clear();
if (marvell_i2c_wait_interrupt()) {
ERROR("Start clear bit timeout\n");
return -ETIMEDOUT;
}
/* check the status */
if (marvell_i2c_lost_arbitration(&status)) {
ERROR("%s - %d: Lost arbitration, got status %x\n",
__func__, __LINE__, status);
return -EAGAIN;
}
if (status != I2C_STATUS_DATA_W_ACK) {
ERROR("Status %x in write transaction\n", status);
return -EPERM;
}
}
return 0;
}
static int marvell_i2c_target_offset_set(uint8_t chip, uint32_t addr, int alen)
{
uint8_t off_block[2];
uint32_t off_size;
if (alen == 2) { /* 2-byte addresses support */
off_block[0] = (addr >> 8) & 0xff;
off_block[1] = addr & 0xff;
off_size = 2;
} else { /* 1-byte addresses support */
off_block[0] = addr & 0xff;
off_size = 1;
}
VERBOSE("%s: off_size = %x addr1 = %x addr2 = %x\n", __func__,
off_size, off_block[0], off_block[1]);
return marvell_i2c_data_transmit(off_block, off_size);
}
static int marvell_i2c_unstuck(int ret)
{
uint32_t v;
if (ret != -ETIMEDOUT)
return ret;
VERBOSE("Trying to \"unstuck i2c\"... ");
i2c_init(base);
mmio_write_32((uintptr_t)&base->unstuck, I2C_UNSTUCK_TRIGGER);
do {
v = mmio_read_32((uintptr_t)&base->unstuck);
} while (v & I2C_UNSTUCK_ONGOING);
if (v & I2C_UNSTUCK_ERROR) {
VERBOSE("failed - soft reset i2c\n");
ret = -EPERM;
} else {
VERBOSE("ok\n");
i2c_init(base);
ret = -EAGAIN;
}
return ret;
}
/*
* API Functions
*/
void i2c_init(void *i2c_base)
{
/* For I2C speed and slave address, now we do not set them since
* we just provide the working speed and slave address in mvebu_def.h
* for i2c_init
*/
base = (struct marvell_i2c_regs *)i2c_base;
/* Reset the I2C logic */
mmio_write_32((uintptr_t)&base->soft_reset, 0);
udelay(200);
marvell_i2c_bus_speed_set(CONFIG_SYS_I2C_SPEED);
/* Enable the I2C and slave */
mmio_write_32((uintptr_t)&base->control,
I2C_CONTROL_TWSIEN | I2C_CONTROL_ACK);
/* set the I2C slave address */
mmio_write_32((uintptr_t)&base->xtnd_slave_addr, 0);
mmio_write_32((uintptr_t)&base->slave_address, CONFIG_SYS_I2C_SLAVE);
/* unmask I2C interrupt */
mmio_write_32((uintptr_t)&base->control,
mmio_read_32((uintptr_t)&base->control) |
I2C_CONTROL_INTEN);
udelay(10);
}
/*
* i2c_read: - Read multiple bytes from an i2c device
*
* The higher level routines take into account that this function is only
* called with len < page length of the device (see configuration file)
*
* @chip: address of the chip which is to be read
* @addr: i2c data address within the chip
* @alen: length of the i2c data address (1..2 bytes)
* @buffer: where to write the data
* @len: how much byte do we want to read
* @return: 0 in case of success
*/
int i2c_read(uint8_t chip, uint32_t addr, int alen, uint8_t *buffer, int len)
{
int ret = 0;
uint32_t counter = 0;
#ifdef DEBUG_I2C
marvell_i2c_probe(chip);
#endif
do {
if (ret != -EAGAIN && ret) {
ERROR("i2c transaction failed, after %d retries\n",
counter);
marvell_i2c_stop_bit_set();
return ret;
}
/* wait for 1 us for the interrupt clear to take effect */
if (counter > 0)
udelay(1);
counter++;
ret = marvell_i2c_start_bit_set();
if (ret) {
ret = marvell_i2c_unstuck(ret);
continue;
}
/* if EEPROM device */
if (alen != 0) {
ret = marvell_i2c_address_set(chip, I2C_CMD_WRITE);
if (ret)
continue;
ret = marvell_i2c_target_offset_set(chip, addr, alen);
if (ret)
continue;
ret = marvell_i2c_start_bit_set();
if (ret)
continue;
}
ret = marvell_i2c_address_set(chip, I2C_CMD_READ);
if (ret)
continue;
ret = marvell_i2c_data_receive(buffer, len);
if (ret)
continue;
ret = marvell_i2c_stop_bit_set();
} while ((ret == -EAGAIN) && (counter < I2C_MAX_RETRY_CNT));
if (counter == I2C_MAX_RETRY_CNT) {
ERROR("I2C transactions failed, got EAGAIN %d times\n",
I2C_MAX_RETRY_CNT);
ret = -EPERM;
}
mmio_write_32((uintptr_t)&base->control,
mmio_read_32((uintptr_t)&base->control) |
I2C_CONTROL_ACK);
udelay(1);
return ret;
}
/*
* i2c_write: - Write multiple bytes to an i2c device
*
* The higher level routines take into account that this function is only
* called with len < page length of the device (see configuration file)
*
* @chip: address of the chip which is to be written
* @addr: i2c data address within the chip
* @alen: length of the i2c data address (1..2 bytes)
* @buffer: where to find the data to be written
* @len: how much byte do we want to read
* @return: 0 in case of success
*/
int i2c_write(uint8_t chip, uint32_t addr, int alen, uint8_t *buffer, int len)
{
int ret = 0;
uint32_t counter = 0;
do {
if (ret != -EAGAIN && ret) {
ERROR("i2c transaction failed\n");
marvell_i2c_stop_bit_set();
return ret;
}
/* wait for 1 us for the interrupt clear to take effect */
if (counter > 0)
udelay(1);
counter++;
ret = marvell_i2c_start_bit_set();
if (ret) {
ret = marvell_i2c_unstuck(ret);
continue;
}
ret = marvell_i2c_address_set(chip, I2C_CMD_WRITE);
if (ret)
continue;
/* if EEPROM device */
if (alen != 0) {
ret = marvell_i2c_target_offset_set(chip, addr, alen);
if (ret)
continue;
}
ret = marvell_i2c_data_transmit(buffer, len);
if (ret)
continue;
ret = marvell_i2c_stop_bit_set();
} while ((ret == -EAGAIN) && (counter < I2C_MAX_RETRY_CNT));
if (counter == I2C_MAX_RETRY_CNT) {
ERROR("I2C transactions failed, got EAGAIN %d times\n",
I2C_MAX_RETRY_CNT);
ret = -EPERM;
}
udelay(1);
return ret;
}