/*
 * SPDX-License-Identifier: BSD-3-Clause
 * Copyright(c) 2023 Napatech A/S
 */

#include "nt_util.h"
#include "ntlog.h"

#include "nthw_drv.h"
#include "nthw_register.h"

#include "nthw_iic.h"

#define I2C_TRANSMIT_WR (0x00)
#define I2C_TRANSMIT_RD (0x01)

#define I2C_WAIT_US(x) nt_os_wait_usec(x)

/*
 * Minimum timing values for I2C for a Marvel 88E11111 Phy.
 * This Phy is used in many Trispeed NIMs.
 * In order to access this Phy, the I2C clock speed is needed to be set to 100KHz.
 */
static const uint32_t SUSTA = 4700;	/* ns */
static const uint32_t SUSTO = 4000;	/* ns */
static const uint32_t HDSTA = 4000;	/* ns */
static const uint32_t SUDAT = 250;	/* ns */
static const uint32_t BUF = 4700;	/* ns */
static const uint32_t HIGH = 4000;	/* ns */
static const uint32_t LOW = 4700;	/* ns */
static const uint32_t HDDAT = 300;	/* ns */

static int nthw_iic_reg_control_txfifo_reset(nthw_iic_t *p)
{
	nthw_field_update_register(p->mp_fld_cr_txfifo_reset);

	nthw_field_set_all(p->mp_fld_cr_txfifo_reset);
	nthw_field_flush_register(p->mp_fld_cr_txfifo_reset);

	nthw_field_clr_all(p->mp_fld_cr_txfifo_reset);
	nthw_field_flush_register(p->mp_fld_cr_txfifo_reset);

	return 0;
}

static int nthw_iic_reg_tx_fifo_write(nthw_iic_t *p, uint32_t data, bool start, bool stop)
{
	if (start)
		nthw_field_set_all(p->mp_fld_tx_fifo_start);

	else
		nthw_field_clr_all(p->mp_fld_tx_fifo_start);

	if (stop)
		nthw_field_set_all(p->mp_fld_tx_fifo_stop);

	else
		nthw_field_clr_all(p->mp_fld_tx_fifo_stop);

	nthw_field_set_val32(p->mp_fld_tx_fifo_txdata, data);

	nthw_register_flush(p->mp_reg_tx_fifo, 1);

	return 0;
}

static int nthw_iic_reg_read_i2c_rx_fifo(nthw_iic_t *p, uint8_t *p_data)
{
	assert(p_data);

	*p_data = (uint8_t)nthw_field_get_updated(p->mp_fld_rx_fifo_rxdata);

	return 0;
}

static int nthw_iic_reg_softr(nthw_iic_t *p)
{
	nthw_field_update_register(p->mp_fld_cr_en);
	nthw_field_set_val_flush32(p->mp_fld_softr_rkey, 0x0A);

	return 0;
}

static int nthw_iic_reg_enable(nthw_iic_t *p)
{
	nthw_field_update_register(p->mp_fld_cr_en);
	nthw_field_set_flush(p->mp_fld_cr_en);

	return 0;
}

static int nthw_iic_reg_busbusy(nthw_iic_t *p, bool *pb_flag)
{
	assert(pb_flag);

	*pb_flag = nthw_field_get_updated(p->mp_fld_sr_bb) ? true : false;

	return 0;
}

static int nthw_iic_reg_rxfifo_empty(nthw_iic_t *p, bool *pb_flag)
{
	assert(pb_flag);

	*pb_flag = nthw_field_get_updated(p->mp_fld_sr_rxfifo_empty) ? true : false;

	return 0;
}

/*
 * n_iic_cycle_time is the I2C clock cycle time in ns ie 125MHz = 8ns
 */
static int nthw_iic_reg_set_timing(nthw_iic_t *p, uint32_t n_iic_cycle_time)
{
	uint32_t val;

	val = SUSTA / n_iic_cycle_time;
	nthw_field_set_val_flush(p->mp_fld_tsusta, &val, 1);

	val = SUSTO / n_iic_cycle_time;
	nthw_field_set_val_flush(p->mp_fld_tsusto, &val, 1);

	val = HDSTA / n_iic_cycle_time;
	nthw_field_set_val_flush(p->mp_fld_thdsta, &val, 1);

	val = SUDAT / n_iic_cycle_time;
	nthw_field_set_val_flush(p->mp_fld_tsudat, &val, 1);

	val = BUF / n_iic_cycle_time;
	nthw_field_set_val_flush(p->mp_fld_tbuf, &val, 1);

	val = HIGH / n_iic_cycle_time;
	nthw_field_set_val_flush(p->mp_fld_thigh, &val, 1);

	val = LOW / n_iic_cycle_time;
	nthw_field_set_val_flush(p->mp_fld_tlow, &val, 1);

	val = HDDAT / n_iic_cycle_time;
	nthw_field_set_val_flush(p->mp_fld_thddat, &val, 1);

	return 0;
}

nthw_iic_t *nthw_iic_new(void)
{
	nthw_iic_t *p = malloc(sizeof(nthw_iic_t));

	if (p)
		memset(p, 0, sizeof(nthw_iic_t));

	return p;
}

int nthw_iic_init(nthw_iic_t *p, nthw_fpga_t *p_fpga, int n_iic_instance,
	uint32_t n_iic_cycle_time)
{
	const char *const p_adapter_id_str = p_fpga->p_fpga_info->mp_adapter_id_str;
	nthw_module_t *mod = nthw_fpga_query_module(p_fpga, MOD_IIC, n_iic_instance);

	if (p == NULL)
		return mod == NULL ? -1 : 0;

	if (mod == NULL) {
		NT_LOG(ERR, NTHW, "%s: I2C %d: no such instance", p_adapter_id_str,
			n_iic_instance);
		return -1;
	}

	p->mp_fpga = p_fpga;
	p->mn_iic_instance = n_iic_instance;

	p->mn_iic_cycle_time = n_iic_cycle_time;

	nthw_iic_set_retry_params(p, -1, -1, -1, -1, -1);

	p->mp_mod_iic = mod;

	/* I2C is a primary communication channel - turn off debug by default */
	nthw_module_set_debug_mode(p->mp_mod_iic, 0x00);

	p->mp_reg_tsusta = nthw_module_get_register(p->mp_mod_iic, IIC_TSUSTA);
	p->mp_fld_tsusta = nthw_register_get_field(p->mp_reg_tsusta, IIC_TSUSTA_TSUSTA_VAL);

	p->mp_reg_tsusto = nthw_module_get_register(p->mp_mod_iic, IIC_TSUSTO);
	p->mp_fld_tsusto = nthw_register_get_field(p->mp_reg_tsusto, IIC_TSUSTO_TSUSTO_VAL);

	p->mp_reg_thdsta = nthw_module_get_register(p->mp_mod_iic, IIC_THDSTA);
	p->mp_fld_thdsta = nthw_register_get_field(p->mp_reg_thdsta, IIC_THDSTA_THDSTA_VAL);

	p->mp_reg_tsudat = nthw_module_get_register(p->mp_mod_iic, IIC_TSUDAT);
	p->mp_fld_tsudat = nthw_register_get_field(p->mp_reg_tsudat, IIC_TSUDAT_TSUDAT_VAL);

	p->mp_reg_tbuf = nthw_module_get_register(p->mp_mod_iic, IIC_TBUF);
	p->mp_fld_tbuf = nthw_register_get_field(p->mp_reg_tbuf, IIC_TBUF_TBUF_VAL);

	p->mp_reg_thigh = nthw_module_get_register(p->mp_mod_iic, IIC_THIGH);
	p->mp_fld_thigh = nthw_register_get_field(p->mp_reg_thigh, IIC_THIGH_THIGH_VAL);

	p->mp_reg_tlow = nthw_module_get_register(p->mp_mod_iic, IIC_TLOW);
	p->mp_fld_tlow = nthw_register_get_field(p->mp_reg_tlow, IIC_TLOW_TLOW_VAL);

	p->mp_reg_thddat = nthw_module_get_register(p->mp_mod_iic, IIC_THDDAT);
	p->mp_fld_thddat = nthw_register_get_field(p->mp_reg_thddat, IIC_THDDAT_THDDAT_VAL);

	p->mp_reg_cr = nthw_module_get_register(p->mp_mod_iic, IIC_CR);
	p->mp_fld_cr_en = nthw_register_get_field(p->mp_reg_cr, IIC_CR_EN);
	p->mp_fld_cr_msms = nthw_register_get_field(p->mp_reg_cr, IIC_CR_MSMS);
	p->mp_fld_cr_txfifo_reset = nthw_register_get_field(p->mp_reg_cr, IIC_CR_TXFIFO_RESET);
	p->mp_fld_cr_txak = nthw_register_get_field(p->mp_reg_cr, IIC_CR_TXAK);

	p->mp_reg_sr = nthw_module_get_register(p->mp_mod_iic, IIC_SR);
	p->mp_fld_sr_bb = nthw_register_get_field(p->mp_reg_sr, IIC_SR_BB);
	p->mp_fld_sr_rxfifo_full = nthw_register_get_field(p->mp_reg_sr, IIC_SR_RXFIFO_FULL);
	p->mp_fld_sr_rxfifo_empty = nthw_register_get_field(p->mp_reg_sr, IIC_SR_RXFIFO_EMPTY);
	p->mp_fld_sr_txfifo_full = nthw_register_get_field(p->mp_reg_sr, IIC_SR_TXFIFO_FULL);
	p->mp_fld_sr_txfifo_empty = nthw_register_get_field(p->mp_reg_sr, IIC_SR_TXFIFO_EMPTY);

	p->mp_reg_tx_fifo = nthw_module_get_register(p->mp_mod_iic, IIC_TX_FIFO);
	p->mp_fld_tx_fifo_txdata = nthw_register_get_field(p->mp_reg_tx_fifo, IIC_TX_FIFO_TXDATA);
	p->mp_fld_tx_fifo_start = nthw_register_get_field(p->mp_reg_tx_fifo, IIC_TX_FIFO_START);
	p->mp_fld_tx_fifo_stop = nthw_register_get_field(p->mp_reg_tx_fifo, IIC_TX_FIFO_STOP);

	p->mp_reg_rx_fifo_pirq = nthw_module_get_register(p->mp_mod_iic, IIC_RX_FIFO_PIRQ);
	p->mp_fld_rx_fifo_pirq_cmp_val =
		nthw_register_get_field(p->mp_reg_rx_fifo_pirq, IIC_RX_FIFO_PIRQ_CMP_VAL);

	p->mp_reg_rx_fifo = nthw_module_get_register(p->mp_mod_iic, IIC_RX_FIFO);
	p->mp_fld_rx_fifo_rxdata = nthw_register_get_field(p->mp_reg_rx_fifo, IIC_RX_FIFO_RXDATA);

	p->mp_reg_softr = nthw_module_get_register(p->mp_mod_iic, IIC_SOFTR);
	p->mp_fld_softr_rkey = nthw_register_get_field(p->mp_reg_softr, IIC_SOFTR_RKEY);

	/*
	 * Initialize I2C controller by applying soft reset and enable the controller
	 */
	nthw_iic_reg_softr(p);
	/* Enable the controller */
	nthw_iic_reg_enable(p);

	/* Setup controller timing */
	if (p->mn_iic_cycle_time) {
		NT_LOG(DBG, NTHW, "%s: I2C%d: cycletime=%d", p_adapter_id_str,
			p->mn_iic_instance, p->mn_iic_cycle_time);
		nthw_iic_reg_set_timing(p, p->mn_iic_cycle_time);
	}

	/* Reset TX fifo - must be after enable */
	nthw_iic_reg_control_txfifo_reset(p);
	nthw_iic_reg_tx_fifo_write(p, 0, 0, 0);

	return 0;
}

void nthw_iic_delete(nthw_iic_t *p)
{
	if (p) {
		memset(p, 0, sizeof(nthw_iic_t));
		free(p);
	}
}

int nthw_iic_set_retry_params(nthw_iic_t *p, const int n_poll_delay, const int n_bus_ready_retry,
	const int n_data_ready_retry, const int n_read_data_retry,
	const int n_write_data_retry)
{
	p->mn_poll_delay = n_poll_delay >= 0 ? n_poll_delay : 10;

	p->mn_bus_ready_retry = n_bus_ready_retry >= 0 ? n_bus_ready_retry : 1000;
	p->mn_data_ready_retry = n_data_ready_retry >= 0 ? n_data_ready_retry : 1000;

	p->mn_read_data_retry = n_read_data_retry >= 0 ? n_read_data_retry : 10;
	p->mn_write_data_retry = n_write_data_retry >= 0 ? n_write_data_retry : 10;

	return 0;
}

int nthw_iic_read_data(nthw_iic_t *p, uint8_t dev_addr, uint8_t a_reg_addr, uint8_t data_len,
	void *p_void)
{
	const char *const p_adapter_id_str = p->mp_fpga->p_fpga_info->mp_adapter_id_str;
	const int n_debug_mode = nthw_module_get_debug_mode(p->mp_mod_iic);

	uint8_t *pb = (uint8_t *)p_void;
	int retry = (p->mn_read_data_retry >= 0 ? p->mn_read_data_retry : 10);

	if (n_debug_mode == 0xff) {
		NT_LOG(DBG, NTHW, "%s: adr=0x%2.2x, reg=%d, len=%d", p_adapter_id_str, dev_addr,
			a_reg_addr, data_len);
	}

	while (nthw_iic_readbyte(p, dev_addr, a_reg_addr, data_len, pb) != 0) {
		retry--;

		if (retry <= 0) {
			NT_LOG(ERR, NTHW,
				"%s: I2C%d: Read retry exhausted (dev_addr=%d a_reg_addr=%d)",
				p_adapter_id_str, p->mn_iic_instance, dev_addr, a_reg_addr);
			return -1;

		} else {
			NT_LOG(DBG, NTHW, "%s: I2C%d: Read retry=%d (dev_addr=%d a_reg_addr=%d)",
				p_adapter_id_str, p->mn_iic_instance, retry, dev_addr, a_reg_addr);
		}
	}

	if (n_debug_mode == 0xff) {
		NT_LOG(DBG, NTHW, "%s: adr=0x%2.2x, reg=%d, len=%d, retries remaining: %d",
			p_adapter_id_str, dev_addr, a_reg_addr, data_len, retry);
	}

	return 0;
}

int nthw_iic_readbyte(nthw_iic_t *p, uint8_t dev_addr, uint8_t a_reg_addr, uint8_t data_len,
	uint8_t *p_byte)
{
	const char *const p_adapter_id_str = p->mp_fpga->p_fpga_info->mp_adapter_id_str;

	uint32_t value;
	uint32_t i;

	if (nthw_iic_bus_ready(p)) {
		/* Reset TX fifo */
		nthw_iic_reg_control_txfifo_reset(p);

		/* Write device address to TX_FIFO and set start bit!! */
		value = (dev_addr << 1) | I2C_TRANSMIT_WR;
		nthw_iic_reg_tx_fifo_write(p, value, 1, 0);

		/* Write a_reg_addr to TX FIFO */
		nthw_iic_reg_tx_fifo_write(p, a_reg_addr, 0, 1);

		if (!nthw_iic_bus_ready(p)) {
			NT_LOG_DBGX(ERR, NTHW, "%s: error:", p_adapter_id_str);
			return -1;
		}

		/* Write device address + RD bit to TX_FIFO and set start bit!! */
		value = (dev_addr << 1) | I2C_TRANSMIT_RD;
		nthw_iic_reg_tx_fifo_write(p, value, 1, 0);

		/* Write data_len to TX_FIFO and set stop bit!! */
		nthw_iic_reg_tx_fifo_write(p, data_len, 0, 1);

		for (i = 0; i < data_len; i++) {
			/* Wait for RX FIFO not empty */
			if (!nthw_iic_data_ready(p))
				return -1;

			/* Read data_len bytes from RX_FIFO */
			nthw_iic_reg_read_i2c_rx_fifo(p, p_byte);
			p_byte++;
		}

		return 0;

	} else {
		NT_LOG_DBGX(ERR, NTHW, "%s: error", p_adapter_id_str);
		return -1;
	}

	return 0;
}

int nthw_iic_write_data(nthw_iic_t *p, uint8_t dev_addr, uint8_t a_reg_addr, uint8_t data_len,
	void *p_void)
{
	const char *const p_adapter_id_str = p->mp_fpga->p_fpga_info->mp_adapter_id_str;
	int retry = (p->mn_write_data_retry >= 0 ? p->mn_write_data_retry : 10);
	uint8_t *pb = (uint8_t *)p_void;

	while (nthw_iic_writebyte(p, dev_addr, a_reg_addr, data_len, pb) != 0) {
		retry--;

		if (retry <= 0) {
			NT_LOG(ERR, NTHW,
				"%s: I2C%d: Write retry exhausted (dev_addr=%d a_reg_addr=%d)",
				p_adapter_id_str, p->mn_iic_instance, dev_addr, a_reg_addr);
			return -1;

		} else {
			NT_LOG(DBG, NTHW,
				"%s: I2C%d: Write retry=%d (dev_addr=%d a_reg_addr=%d)",
				p_adapter_id_str, p->mn_iic_instance, retry, dev_addr, a_reg_addr);
		}
	}

	return 0;
}

int nthw_iic_writebyte(nthw_iic_t *p, uint8_t dev_addr, uint8_t a_reg_addr, uint8_t data_len,
	uint8_t *p_byte)
{
	const char *const p_adapter_id_str = p->mp_fpga->p_fpga_info->mp_adapter_id_str;
	uint32_t value;
	int count;
	int i;

	if (data_len == 0)
		return -1;

	count = data_len - 1;

	if (nthw_iic_bus_ready(p)) {
		/* Reset TX fifo */
		nthw_iic_reg_control_txfifo_reset(p);

		/* Write device address to TX_FIFO and set start bit!! */
		value = (dev_addr << 1) | I2C_TRANSMIT_WR;
		nthw_iic_reg_tx_fifo_write(p, value, 1, 0);

		/* Write a_reg_addr to TX FIFO */
		nthw_iic_reg_tx_fifo_write(p, a_reg_addr, 0, 0);

		for (i = 0; i < count; i++) {
			/* Write data byte to TX fifo and set stop bit */
			nthw_iic_reg_tx_fifo_write(p, *p_byte, 0, 0);
			p_byte++;
		}

		/* Write data byte to TX fifo and set stop bit */
		nthw_iic_reg_tx_fifo_write(p, *p_byte, 0, 1);

		if (!nthw_iic_bus_ready(p)) {
			NT_LOG_DBGX(WRN, NTHW, "%s: warn: !busReady", p_adapter_id_str);

			while (true)
				if (nthw_iic_bus_ready(p)) {
					NT_LOG_DBGX(DBG, NTHW, "%s: info: busReady",
					p_adapter_id_str);
					break;
				}
		}

		return 0;

	} else {
		NT_LOG_DBGX(WRN, NTHW, "%s", p_adapter_id_str);
		return -1;
	}
}

/*
 * Support function for read/write functions below. Waits for bus ready.
 */
bool nthw_iic_bus_ready(nthw_iic_t *p)
{
	int count = (p->mn_bus_ready_retry >= 0 ? p->mn_bus_ready_retry : 1000);
	bool b_bus_busy = true;

	while (true) {
		nthw_iic_reg_busbusy(p, &b_bus_busy);

		if (!b_bus_busy)
			break;

		count--;

		if (count <= 0)	/* Test for timeout */
			break;

		if (p->mn_poll_delay != 0)
			I2C_WAIT_US(p->mn_poll_delay);
	}

	if (count == 0)
		return false;

	return true;
}

/*
 * Support function for read function. Waits for data ready.
 */
bool nthw_iic_data_ready(nthw_iic_t *p)
{
	int count = (p->mn_data_ready_retry >= 0 ? p->mn_data_ready_retry : 1000);
	bool b_rx_fifo_empty = true;

	while (true) {
		nthw_iic_reg_rxfifo_empty(p, &b_rx_fifo_empty);

		if (!b_rx_fifo_empty)
			break;

		count--;

		if (count <= 0)	/* Test for timeout */
			break;

		if (p->mn_poll_delay != 0)
			I2C_WAIT_US(p->mn_poll_delay);
	}

	if (count == 0)
		return false;

	return true;
}

int nthw_iic_scan_dev_addr(nthw_iic_t *p, int n_dev_addr, int n_reg_addr)
{
	const char *const p_adapter_id_str = p->mp_fpga->p_fpga_info->mp_adapter_id_str;
	(void)p_adapter_id_str;
	int res;
	uint8_t data_val = -1;
	res = nthw_iic_readbyte(p, (uint8_t)n_dev_addr, (uint8_t)n_reg_addr, 1, &data_val);

	if (res == 0) {
		NT_LOG(DBG, NTHW,
			"%s: I2C%d: devaddr=0x%02X (%03d) regaddr=%02X val=%02X (%03d) res=%d",
			p_adapter_id_str, p->mn_iic_instance, n_dev_addr, n_dev_addr, n_reg_addr,
			data_val, data_val, res);
	}

	return res;
}

int nthw_iic_scan(nthw_iic_t *p)
{
	int i;

	for (i = 0; i < 128; i++)
		(void)nthw_iic_scan_dev_addr(p, i, 0x00);

	return 0;
}
