marvell: drivers: Add i2c driver

Add i2c driver for A8K SoC family.

Change-Id: I5932b2fce286d84fc3ad5a74c4c456001faa3196
Signed-off-by: Hanna Hawa <hannah@marvell.com>
Signed-off-by: Konstantin Porotchkin <kostap@marvell.com>
diff --git a/drivers/marvell/i2c/a8k_i2c.c b/drivers/marvell/i2c/a8k_i2c.c
new file mode 100644
index 0000000..737dd0a
--- /dev/null
+++ b/drivers/marvell/i2c/a8k_i2c.c
@@ -0,0 +1,613 @@
+/*
+ * 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 */
+	} u;
+	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->u.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->u.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 plat_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;
+}
diff --git a/include/drivers/marvell/a8k_i2c.h b/include/drivers/marvell/a8k_i2c.h
new file mode 100644
index 0000000..8a9abe8
--- /dev/null
+++ b/include/drivers/marvell/a8k_i2c.h
@@ -0,0 +1,38 @@
+/*
+ * 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 */
+
+#ifndef _A8K_I2C_H_
+#define _A8K_I2C_H_
+
+#include <stdint.h>
+
+/*
+ * Initialization, must be called once on start up, may be called
+ * repeatedly to change the speed and slave addresses.
+ */
+void i2c_init(void *i2c_base);
+
+/*
+ * Read/Write interface:
+ *   chip:    I2C chip address, range 0..127
+ *   addr:    Memory (register) address within the chip
+ *   alen:    Number of bytes to use for addr (typically 1, 2 for larger
+ *              memories, 0 for register type devices with only one
+ *              register)
+ *   buffer:  Where to read/write the data
+ *   len:     How many bytes to read/write
+ *
+ *   Returns: 0 on success, not 0 on failure
+ */
+int i2c_read(uint8_t chip,
+	     unsigned int addr, int alen, uint8_t *buffer, int len);
+
+int i2c_write(uint8_t chip,
+	      unsigned int addr, int alen, uint8_t *buffer, int len);
+#endif
diff --git a/include/drivers/marvell/i2c.h b/include/drivers/marvell/i2c.h
new file mode 100644
index 0000000..bd14385
--- /dev/null
+++ b/include/drivers/marvell/i2c.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2018 Marvell International Ltd.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ * https://spdx.org/licenses
+ */
+
+#ifndef _I2C_H_
+#define _I2C_H_
+
+
+void i2c_init(void);
+
+int i2c_read(uint8_t chip,
+	     unsigned int addr, int alen, uint8_t *buffer, int len);
+
+int i2c_write(uint8_t chip,
+	      unsigned int addr, int alen, uint8_t *buffer, int len);
+#endif