/* SPDX-License-Identifier: GPL-2.0-only */

/*
 * Derived from Cavium's BSD-3 Clause OCTEONTX-SDK-6.2.0.
 */

#include <console/console.h>
#include <soc/twsi.h>
#include <soc/clock.h>
#include <device/i2c.h>
#include <device/i2c_simple.h>
#include <delay.h>
#include <device/mmio.h>
#include <soc/addressmap.h>
#include <timer.h>

#define TWSI_THP		24

#define TWSI_SW_TWSI		0x1000
#define TWSI_TWSI_SW		0x1008
#define TWSI_INT		0x1010
#define TWSI_SW_TWSI_EXT	0x1018

union twsx_sw_twsi {
	u64 u;
	struct {
		u64 data:32;
		u64 eop_ia:3;
		u64 ia:5;
		u64 addr:10;
		u64 scr:2;
		u64 size:3;
		u64 sovr:1;
		u64 r:1;
		u64 op:4;
		u64 eia:1;
		u64 slonly:1;
		u64 v:1;
	} s;
};

union twsx_sw_twsi_ext {
	u64 u;
	struct {
		u64	data:32;
		u64	ia:8;
		u64	:24;
	} s;
};

union twsx_int {
	u64 u;
	struct {
		u64	st_int:1;	/** TWSX_SW_TWSI register update int */
		u64	ts_int:1;	/** TWSX_TWSI_SW register update int */
		u64	core_int:1;	/** TWSI core interrupt, ignored for HLC */
		u64	:5;		/** Reserved */
		u64	sda_ovr:1;	/** SDA testing override */
		u64	scl_ovr:1;	/** SCL testing override */
		u64	sda:1;		/** SDA signal */
		u64	scl:1;		/** SCL signal */
		u64	:52;		/** Reserved */
	} s;
};

enum {
	TWSI_OP_WRITE	= 0,
	TWSI_OP_READ	= 1,
};

enum {
	TWSI_EOP_SLAVE_ADDR = 0,
	TWSI_EOP_CLK_CTL = 3,
	TWSI_SW_EOP_IA   = 6,
};

enum {
	TWSI_SLAVEADD     = 0,
	TWSI_DATA         = 1,
	TWSI_CTL          = 2,
	TWSI_CLKCTL       = 3,
	TWSI_STAT         = 3,
	TWSI_SLAVEADD_EXT = 4,
	TWSI_RST          = 7,
};

enum {
	TWSI_CTL_AAK	= (1 << 2),
	TWSI_CTL_IFLG	= (1 << 3),
	TWSI_CTL_STP	= (1 << 4),
	TWSI_CTL_STA	= (1 << 5),
	TWSI_CTL_ENAB	= (1 << 6),
	TWSI_CTL_CE	= (1 << 7),
};

enum {
	/** Bus error */
	TWSI_STAT_BUS_ERROR		= 0x00,
	/** Start condition transmitted */
	TWSI_STAT_START			= 0x08,
	/** Repeat start condition transmitted */
	TWSI_STAT_RSTART		= 0x10,
	/** Address + write bit transmitted, ACK received */
	TWSI_STAT_TXADDR_ACK		= 0x18,
	/** Address + write bit transmitted, /ACK received */
	TWSI_STAT_TXADDR_NAK		= 0x20,
	/** Data byte transmitted in master mode, ACK received */
	TWSI_STAT_TXDATA_ACK		= 0x28,
	/** Data byte transmitted in master mode, ACK received */
	TWSI_STAT_TXDATA_NAK		= 0x30,
	/** Arbitration lost in address or data byte */
	TWSI_STAT_TX_ARB_LOST		= 0x38,
	/** Address + read bit transmitted, ACK received */
	TWSI_STAT_RXADDR_ACK		= 0x40,
	/** Address + read bit transmitted, /ACK received */
	TWSI_STAT_RXADDR_NAK		= 0x48,
	/** Data byte received in master mode, ACK transmitted */
	TWSI_STAT_RXDATA_ACK_SENT	= 0x50,
	/** Data byte received, NACK transmitted */
	TWSI_STAT_RXDATA_NAK_SENT	= 0x58,
	/** Slave address received, sent ACK */
	TWSI_STAT_SLAVE_RXADDR_ACK	= 0x60,
	/**
	 * Arbitration lost in address as master, slave address + write bit
	 * received, ACK transmitted
	 */
	TWSI_STAT_TX_ACK_ARB_LOST	= 0x68,
	/** General call address received, ACK transmitted */
	TWSI_STAT_RX_GEN_ADDR_ACK	= 0x70,
	/**
	 * Arbitration lost in address as master, general call address
	 * received, ACK transmitted
	 */
	TWSI_STAT_RX_GEN_ADDR_ARB_LOST	= 0x78,
	/** Data byte received after slave address received, ACK transmitted */
	TWSI_STAT_SLAVE_RXDATA_ACK	= 0x80,
	/** Data byte received after slave address received, /ACK transmitted */
	TWSI_STAT_SLAVE_RXDATA_NAK	= 0x88,
	/**
	 * Data byte received after general call address received, ACK
	 * transmitted
	 */
	TWSI_STAT_GEN_RXADDR_ACK	= 0x90,
	/**
	 * Data byte received after general call address received, /ACK
	 * transmitted
	 */
	TWSI_STAT_GEN_RXADDR_NAK	= 0x98,
	/** STOP or repeated START condition received in slave mode */
	TWSI_STAT_STOP_MULTI_START	= 0xA0,
	/** Slave address + read bit received, ACK transmitted */
	TWSI_STAT_SLAVE_RXADDR2_ACK	= 0xA8,
	/**
	 * Arbitration lost in address as master, slave address + read bit
	 * received, ACK transmitted
	 */
	TWSI_STAT_RXDATA_ACK_ARB_LOST	= 0xB0,
	/** Data byte transmitted in slave mode, ACK received */
	TWSI_STAT_SLAVE_TXDATA_ACK	= 0xB8,
	/** Data byte transmitted in slave mode, /ACK received */
	TWSI_STAT_SLAVE_TXDATA_NAK	= 0xC0,
	/** Last byte transmitted in slave mode, ACK received */
	TWSI_STAT_SLAVE_TXDATA_END_ACK	= 0xC8,
	/** Second address byte + write bit transmitted, ACK received */
	TWSI_STAT_TXADDR2DATA_ACK	= 0xD0,
	/** Second address byte + write bit transmitted, /ACK received */
	TWSI_STAT_TXADDR2DATA_NAK	= 0xD8,
	/** No relevant status information */
	TWSI_STAT_IDLE		= 0xF8
};

/**
 * Returns true if we lost arbitration
 *
 * @param	code		status code
 * @param	final_read	true if this is the final read operation
 *
 * @return	true if arbitration has been lost, false if it hasn't been lost.
 */
static int twsi_i2c_lost_arb(u8 code, int final_read)
{
	switch (code) {
	/* Arbitration lost */
	case TWSI_STAT_TX_ARB_LOST:
	case TWSI_STAT_TX_ACK_ARB_LOST:
	case TWSI_STAT_RX_GEN_ADDR_ARB_LOST:
	case TWSI_STAT_RXDATA_ACK_ARB_LOST:
		return -1;

	/* Being addressed as slave, should back off and listen */
	case TWSI_STAT_SLAVE_RXADDR_ACK:
	case TWSI_STAT_RX_GEN_ADDR_ACK:
	case TWSI_STAT_GEN_RXADDR_ACK:
	case TWSI_STAT_GEN_RXADDR_NAK:
		return -1;

	/* Core busy as slave */
	case TWSI_STAT_SLAVE_RXDATA_ACK:
	case TWSI_STAT_SLAVE_RXDATA_NAK:
	case TWSI_STAT_STOP_MULTI_START:
	case TWSI_STAT_SLAVE_RXADDR2_ACK:
	case TWSI_STAT_SLAVE_TXDATA_ACK:
	case TWSI_STAT_SLAVE_TXDATA_NAK:
	case TWSI_STAT_SLAVE_TXDATA_END_ACK:
		return  -1;

	/* Ack allowed on pre-terminal bytes only */
	case TWSI_STAT_RXDATA_ACK_SENT:
		if (!final_read)
			return 0;
		return -1;

	/* NAK allowed on terminal byte only */
	case TWSI_STAT_RXDATA_NAK_SENT:
		if (!final_read)
			return 0;
		return -1;

	case TWSI_STAT_TXDATA_NAK:
	case TWSI_STAT_TXADDR_NAK:
	case TWSI_STAT_RXADDR_NAK:
	case TWSI_STAT_TXADDR2DATA_NAK:
		return -1;
	}
	return 0;
}

#define RST_BOOT_PNR_MUL(Val)  ((Val >> 33) & 0x1F)

/**
 * Writes to the MIO_TWS(0..5)_SW_TWSI register
 *
 * @param	baseaddr	Base address of i2c registers
 * @param	sw_twsi		value to write
 *
 * @return	0 for success, otherwise error
 */
static u64 twsi_write_sw(void *baseaddr, union twsx_sw_twsi sw_twsi)
{
	unsigned long timeout = 500000;

	sw_twsi.s.r = 0;
	sw_twsi.s.v = 1;

	printk(BIOS_SPEW, "%s(%p, 0x%llx)\n", __func__, baseaddr, sw_twsi.u);
	write64(baseaddr + TWSI_SW_TWSI, sw_twsi.u);
	do {
		sw_twsi.u = read64(baseaddr + TWSI_SW_TWSI);
		timeout--;
	} while (sw_twsi.s.v != 0 && timeout > 0);

	if (sw_twsi.s.v)
		printk(BIOS_ERR, "%s: timed out\n", __func__);
	return sw_twsi.u;
}

/**
 * Reads the MIO_TWS(0..5)_SW_TWSI register
 *
 * @param	baseaddr	Base address of i2c registers
 * @param	sw_twsi		value for eia and op, etc. to read
 *
 * @return	value of the register
 */
static u64 twsi_read_sw(void *baseaddr, union twsx_sw_twsi sw_twsi)
{
	unsigned long timeout = 500000;
	sw_twsi.s.r = 1;
	sw_twsi.s.v = 1;

	printk(BIOS_SPEW, "%s(%p, 0x%llx)\n", __func__, baseaddr, sw_twsi.u);
	write64(baseaddr + TWSI_SW_TWSI, sw_twsi.u);

	do {
		sw_twsi.u = read64(baseaddr + TWSI_SW_TWSI);
		timeout--;
	} while (sw_twsi.s.v != 0 && timeout > 0);

	if (sw_twsi.s.v)
		printk(BIOS_ERR, "%s: Error writing 0x%llx\n", __func__,
		       sw_twsi.u);

	printk(BIOS_SPEW, "%s: Returning 0x%llx\n", __func__, sw_twsi.u);
	return sw_twsi.u;
}

/**
 * Write control register
 *
 * @param	baseaddr	Base address for i2c registers
 * @param	data		data to write
 */
static void twsi_write_ctl(void *baseaddr, const u8 data)
{
	union twsx_sw_twsi twsi_sw;

	printk(BIOS_SPEW, "%s(%p, 0x%x)\n", __func__, baseaddr, data);
	twsi_sw.u = 0;

	twsi_sw.s.op = TWSI_SW_EOP_IA;
	twsi_sw.s.eop_ia = TWSI_CTL;
	twsi_sw.s.data  = data;

	twsi_write_sw(baseaddr, twsi_sw);
}

/**
 * Reads the TWSI Control Register
 *
 * @param[in]	baseaddr	Base address for i2c
 *
 * @return	8-bit TWSI control register
 */
static u32 twsi_read_ctl(void *baseaddr)
{
	union twsx_sw_twsi sw_twsi;

	sw_twsi.u  = 0;
	sw_twsi.s.op  = TWSI_SW_EOP_IA;
	sw_twsi.s.eop_ia = TWSI_CTL;

	sw_twsi.u = twsi_read_sw(baseaddr, sw_twsi);
	printk(BIOS_SPEW, "%s(%p): 0x%x\n", __func__, baseaddr, sw_twsi.s.data);
	return sw_twsi.s.data;
}

/**
 * Read i2c status register
 *
 * @param	baseaddr	Base address of i2c registers
 *
 * @return	value of status register
 */
static u8 twsi_read_status(void *baseaddr)
{
	union twsx_sw_twsi twsi_sw;

	twsi_sw.u = 0;
	twsi_sw.s.op = TWSI_SW_EOP_IA;
	twsi_sw.s.eop_ia = TWSI_STAT;

	return twsi_read_sw(baseaddr, twsi_sw);
}

/**
 * Waits for an i2c operation to complete
 *
 * @param	baseaddr	Base address of registers
 *
 * @return	0 for success, 1 if timeout
 */
static int twsi_wait(void *baseaddr, struct stopwatch *timeout)
{
	u8 twsi_ctl;

	printk(BIOS_SPEW, "%s(%p)\n", __func__, baseaddr);
	do {
		twsi_ctl = twsi_read_ctl(baseaddr);
		twsi_ctl &= TWSI_CTL_IFLG;
	} while (!twsi_ctl && !stopwatch_expired(timeout));

	printk(BIOS_SPEW, "  return: %u\n", !twsi_ctl);
	return !twsi_ctl;
}

/**
 * Sends an i2c stop condition
 *
 * @param	baseaddr	register base address
 *
 * @return	0 for success, -1 if error
 */
static int twsi_stop(void *baseaddr)
{
	u8 stat;
	twsi_write_ctl(baseaddr, TWSI_CTL_STP | TWSI_CTL_ENAB);

	stat = twsi_read_status(baseaddr);
	if (stat != TWSI_STAT_IDLE) {
		printk(BIOS_ERR, "%s: Bad status on bus@%p\n", __func__,
		       baseaddr);
		return -1;
	}
	return 0;
}

/**
 * Manually clear the I2C bus and send a stop
 */
static void twsi_unblock(void *baseaddr)
{
	int i;
	union twsx_int	int_reg;

	int_reg.u = 0;
	for (i = 0; i < 9; i++) {
		int_reg.s.scl_ovr = 0;
		write64(baseaddr + TWSI_INT, int_reg.u);
		udelay(5);
		int_reg.s.scl_ovr = 1;
		write64(baseaddr + TWSI_INT, int_reg.u);
		udelay(5);
	}
	int_reg.s.sda_ovr = 1;
	write64(baseaddr + TWSI_INT, int_reg.u);
	udelay(5);
	int_reg.s.scl_ovr = 0;
	write64(baseaddr + TWSI_INT, int_reg.u);
	udelay(5);
	int_reg.u = 0;
	write64(baseaddr + TWSI_INT, int_reg.u);
	udelay(5);
}

/**
 * Unsticks the i2c bus
 *
 * @param	baseaddr	base address of registers
 */
static int twsi_start_unstick(void *baseaddr)
{
	twsi_stop(baseaddr);

	twsi_unblock(baseaddr);

	return 0;
}

/**
 * Sends an i2c start condition
 *
 * @param	baseaddr	base address of registers
 *
 * @return	0 for success, otherwise error
 */
static int twsi_start(void *baseaddr)
{
	int result;
	u8 stat;
	struct stopwatch timeout;

	printk(BIOS_SPEW, "%s(%p)\n", __func__, baseaddr);
	stopwatch_init_usecs_expire(&timeout, CONFIG_I2C_TRANSFER_TIMEOUT_US);
	twsi_write_ctl(baseaddr, TWSI_CTL_STA | TWSI_CTL_ENAB);
	result = twsi_wait(baseaddr, &timeout);
	if (result) {
		stat = twsi_read_status(baseaddr);
		printk(BIOS_SPEW, "%s: result: 0x%x, status: 0x%x\n", __func__,
		      result, stat);
		switch (stat) {
		case TWSI_STAT_START:
		case TWSI_STAT_RSTART:
			return 0;
		case TWSI_STAT_RXADDR_ACK:
		default:
			return twsi_start_unstick(baseaddr);
		}
	}
	printk(BIOS_SPEW, "%s: success\n", __func__);
	return 0;
}

/**
 * Writes data to the i2c bus
 *
 * @param	baseraddr	register base address
 * @param	slave_addr	address of slave to write to
 * @param	buffer		Pointer to buffer to write
 * @param	length		Number of bytes in buffer to write
 *
 * @return	0 for success, otherwise error
 */
static int twsi_write_data(void *baseaddr, const u8 slave_addr,
			   const u8 *buffer, const unsigned int length)
{
	union twsx_sw_twsi twsi_sw;
	unsigned int curr = 0;
	int result;
	struct stopwatch timeout;

	printk(BIOS_SPEW, "%s(%p, 0x%x, %p, 0x%x)\n", __func__, baseaddr,
	       slave_addr, buffer, length);
	stopwatch_init_usecs_expire(&timeout, CONFIG_I2C_TRANSFER_TIMEOUT_US);
	result = twsi_start(baseaddr);
	if (result) {
		printk(BIOS_ERR, "%s: Could not start BUS transaction\n",
		       __func__);
		return -1;
	}

	result = twsi_wait(baseaddr, &timeout);
	if (result) {
		printk(BIOS_ERR, "%s: wait failed\n", __func__);
		return result;
	}

	twsi_sw.u = 0;
	twsi_sw.s.op = TWSI_SW_EOP_IA;
	twsi_sw.s.eop_ia = TWSI_DATA;
	twsi_sw.s.data = (u32)(slave_addr << 1) | TWSI_OP_WRITE;

	twsi_write_sw(baseaddr, twsi_sw);
	twsi_write_ctl(baseaddr, TWSI_CTL_ENAB);

	printk(BIOS_SPEW, "%s: Waiting\n", __func__);
	result = twsi_wait(baseaddr, &timeout);
	if (result) {
		printk(BIOS_ERR, "%s: Timed out writing slave address 0x%x\n",
		      __func__, slave_addr);
		return result;
	}
	result = twsi_read_status(baseaddr);
	if ((result = twsi_read_status(baseaddr)) != TWSI_STAT_TXADDR_ACK) {
		twsi_stop(baseaddr);
		return twsi_i2c_lost_arb(result, 0);
	}

	while (curr < length) {
		twsi_sw.u = 0;
		twsi_sw.s.op = TWSI_SW_EOP_IA;
		twsi_sw.s.eop_ia = TWSI_DATA;
		twsi_sw.s.data = buffer[curr++];

		twsi_write_sw(baseaddr, twsi_sw);
		twsi_write_ctl(baseaddr, TWSI_CTL_ENAB);

		result = twsi_wait(baseaddr, &timeout);
		if (result) {
			printk(BIOS_ERR, "%s: Timed out writing data to 0x%x\n",
			      __func__, slave_addr);
			return result;
		}
	}

	printk(BIOS_SPEW, "%s: Stopping\n", __func__);
	return twsi_stop(baseaddr);
}

/**
 * Performs a read transaction on the i2c bus
 *
 * @param	baseaddr	Base address of twsi registers
 * @param	slave_addr	i2c bus address to read from
 * @param	buffer		buffer to read into
 * @param	length		number of bytes to read
 *
 * @return	0 for success, otherwise error
 */
static int twsi_read_data(void *baseaddr, const u8 slave_addr,
			  u8 *buffer, const unsigned int length)
{
	union twsx_sw_twsi twsi_sw;
	unsigned int curr = 0;
	int result;
	struct stopwatch timeout;

	printk(BIOS_SPEW, "%s(%p, 0x%x, %p, %u)\n", __func__, baseaddr,
	       slave_addr, buffer, length);
	stopwatch_init_usecs_expire(&timeout, CONFIG_I2C_TRANSFER_TIMEOUT_US);
	result = twsi_start(baseaddr);
	if (result) {
		printk(BIOS_ERR, "%s: start failed\n", __func__);
		return result;
	}

	result = twsi_wait(baseaddr, &timeout);
	if (result) {
		printk(BIOS_ERR, "%s: wait failed\n", __func__);
		return result;
	}

	twsi_sw.u = 0;

	twsi_sw.s.op = TWSI_SW_EOP_IA;
	twsi_sw.s.eop_ia = TWSI_DATA;

	twsi_sw.s.data  = (u32)(slave_addr << 1) | TWSI_OP_READ;

	twsi_write_sw(baseaddr, twsi_sw);
	twsi_write_ctl(baseaddr, TWSI_CTL_ENAB);

	result = twsi_wait(baseaddr, &timeout);
	if (result) {
		printk(BIOS_ERR, "%s: waiting for sending addr failed\n", __func__);
		return result;
	}

	result = twsi_read_status(baseaddr);
	if (result != TWSI_STAT_RXADDR_ACK) {
		twsi_stop(baseaddr);
		return twsi_i2c_lost_arb(result, 0);
	}

	while (curr < length) {
		twsi_write_ctl(baseaddr, TWSI_CTL_ENAB |
				((curr < length - 1) ? TWSI_CTL_AAK : 0));

		result = twsi_wait(baseaddr, &timeout);
		if (result) {
			printk(BIOS_ERR, "%s: waiting for data failed\n",
			       __func__);
			return result;
		}

		twsi_sw.u = twsi_read_sw(baseaddr, twsi_sw);
		buffer[curr++] = twsi_sw.s.data;
	}

	twsi_stop(baseaddr);

	return 0;
}

static int twsi_set_speed(void *baseaddr, const unsigned int speed)
{
	u64 io_clock_hz;
	int n_div;
	int m_div;
	union twsx_sw_twsi sw_twsi;

	io_clock_hz = thunderx_get_io_clock();

	/* Set the TWSI clock to a conservative TWSI_BUS_FREQ.  Compute the
	 * clocks M divider based on the SCLK.
	 * TWSI freq = (core freq) / (20 x (M+1) x (thp+1) x 2^N)
	 * M = ((core freq) / (20 x (TWSI freq) x (thp+1) x 2^N)) - 1
	 */
	for (n_div = 0; n_div < 8; n_div++) {
		m_div = io_clock_hz / (20 * speed * (TWSI_THP + 1));
		m_div /= 1 << n_div;
		m_div -= 1;
		if (m_div < 16)
			break;
	}
	if (m_div >= 16)
		return -1;

	sw_twsi.u = 0;
	sw_twsi.s.v = 1;
	sw_twsi.s.op = 0x6;	/* See EOP field */
	sw_twsi.s.r = 0;	/* Select CLKCTL when R = 0 */
	sw_twsi.s.eop_ia = 3;	/* R=0 selects CLKCTL, R=1 selects STAT */
	sw_twsi.s.data = ((m_div & 0xf) << 3) | ((n_div & 0x7) << 0);

	twsi_write_sw(baseaddr, sw_twsi);
	return 0;
}

int twsi_init(unsigned int bus, enum i2c_speed hz)
{
	void *baseaddr = (void *)MIO_TWSx_PF_BAR0(bus);
	if (!baseaddr)
		return -1;

	if (twsi_set_speed(baseaddr, hz) < 0)
		return -1;

	/* Enable TWSI, HLC disable, STOP, NAK */
	twsi_write_ctl(baseaddr, TWSI_CTL_ENAB);

	return 0;
}

int platform_i2c_transfer(unsigned int bus, struct i2c_msg *segments,
			  int seg_count)
{
	int result;
	void *baseaddr = (void *)MIO_TWSx_PF_BAR0(bus);
	if (!baseaddr)
		return -1;

	printk(BIOS_SPEW, "%s: %d messages\n", __func__, seg_count);
	for (; seg_count > 0; seg_count--, segments++) {
		if (segments->flags & I2C_M_RD) {
			result = twsi_read_data(baseaddr, segments->slave,
						segments->buf, segments->len);
		} else {
			result = twsi_write_data(baseaddr, segments->slave,
						 segments->buf, segments->len);
		}
		if (result) {
			printk(BIOS_ERR, "%s: error transmitting data\n",
			       __func__);
			return -1;
		}
	}

	return 0;
}