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

#include <assert.h>
#include <device/i2c_simple.h>
#include <gpio.h>
#include <inttypes.h>
#include <lib.h>
#include <soc/clock.h>
#include <soc/qcom_qup_se.h>
#include <soc/qupv3_config.h>
#include <soc/qupv3_i2c.h>
#include <stdint.h>
#include <string.h>

static void i2c_clk_configure(unsigned int bus, enum i2c_speed speed)
{
	int clk_div = 0, t_high = 0, t_low = 0, t_cycle = 0;
	struct qup_regs *regs = qup[bus].regs;

	switch (speed) {
	case I2C_SPEED_STANDARD:
		clk_div = 7;
		t_high = 10;
		t_low = 11;
		t_cycle = 26;
		break;
	case I2C_SPEED_FAST:
		clk_div = 2;
		t_high = 5;
		t_low = 12;
		t_cycle = 24;
		break;
	case I2C_SPEED_FAST_PLUS:
		clk_div = 1;
		t_high = 3;
		t_low = 9;
		t_cycle = 18;
		break;
	default:
		die("Unsupported I2C speed");
	}

	write32(&regs->geni_ser_m_clk_cfg, (clk_div << 4) | 1);
	/* Serial clock frequency is 19.2 MHz */
	write32(&regs->i2c_scl_counters, ((t_high << 20) | (t_low << 10)
								| t_cycle));
}

void i2c_init(unsigned int bus, enum i2c_speed speed)
{
	uint32_t proto;
	struct qup_regs *regs = qup[bus].regs;

	qupv3_se_fw_load_and_init(bus, SE_PROTOCOL_I2C, MIXED);
	clock_enable_qup(bus);
	i2c_clk_configure(bus, speed);

	proto = ((read32(&regs->geni_fw_revision_ro) &
					GENI_FW_REVISION_RO_PROTOCOL_MASK) >>
					GENI_FW_REVISION_RO_PROTOCOL_SHIFT);

	assert(proto == 3);

	/* Serial engine IO initialization */
	write32(&regs->geni_cgc_ctrl, DEFAULT_CGC_EN);
	write32(&regs->dma_general_cfg,
		(AHB_SEC_SLV_CLK_CGC_ON | DMA_AHB_SLV_CFG_ON
		| DMA_TX_CLK_CGC_ON | DMA_RX_CLK_CGC_ON));
	write32(&regs->geni_output_ctrl,
			DEFAULT_IO_OUTPUT_CTRL_MSK);
	write32(&regs->geni_force_default_reg, FORCE_DEFAULT);

	/* Serial engine IO set mode */
	write32(&regs->se_irq_en, (GENI_M_IRQ_EN |
			GENI_S_IRQ_EN | DMA_TX_IRQ_EN | DMA_RX_IRQ_EN));
	write32(&regs->se_gsi_event_en, 0);

	/* Set RX and RFR watermark */
	write32(&regs->geni_rx_watermark_reg, 0);
	write32(&regs->geni_rx_rfr_watermark_reg, FIFO_DEPTH - 2);

	/* FIFO PACKING CONFIGURATION */
	write32(&regs->geni_tx_packing_cfg0, PACK_VECTOR0
						| (PACK_VECTOR1 << 10));
	write32(&regs->geni_tx_packing_cfg1, PACK_VECTOR2
						| (PACK_VECTOR3 << 10));
	write32(&regs->geni_rx_packing_cfg0, PACK_VECTOR0
						| (PACK_VECTOR1 << 10));
	write32(&regs->geni_rx_packing_cfg1, PACK_VECTOR2
						| (PACK_VECTOR3 << 10));
	write32(&regs->geni_byte_granularity, (log2(BITS_PER_WORD) - 3));

	/* GPIO Configuration */
	gpio_configure(qup[bus].pin[0], qup[bus].func[0], GPIO_PULL_UP,
				GPIO_2MA, GPIO_OUTPUT_ENABLE);
	gpio_configure(qup[bus].pin[1], qup[bus].func[1], GPIO_PULL_UP,
				GPIO_2MA, GPIO_OUTPUT_ENABLE);

	/* Select and setup FIFO mode */
	write32(&regs->geni_m_irq_clear, 0xFFFFFFFF);
	write32(&regs->geni_s_irq_clear, 0xFFFFFFFF);
	write32(&regs->dma_tx_irq_clr, 0xFFFFFFFF);
	write32(&regs->dma_rx_irq_clr, 0xFFFFFFFF);
	write32(&regs->geni_m_irq_enable, (M_COMMON_GENI_M_IRQ_EN |
			M_CMD_DONE_EN |	M_TX_FIFO_WATERMARK_EN |
			M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN));
	write32(&regs->geni_s_irq_enable, (S_COMMON_GENI_S_IRQ_EN
						| S_CMD_DONE_EN));
	clrbits32(&regs->geni_dma_mode_en, GENI_DMA_MODE_EN);
}

static int i2c_do_xfer(unsigned int bus, struct i2c_msg segment,
				unsigned int prams)
{
	unsigned int cmd = (segment.flags & I2C_M_RD) ? 2 : 1;
	unsigned int master_cmd_reg_val = (cmd << M_OPCODE_SHFT);
	struct qup_regs *regs = qup[bus].regs;
	void *dout = NULL, *din = NULL;

	if (!(segment.flags & I2C_M_RD)) {
		write32(&regs->i2c_tx_trans_len, segment.len);
		write32(&regs->geni_tx_watermark_reg, TX_WATERMARK);
		dout = segment.buf;
	} else {
		write32(&regs->i2c_rx_trans_len, segment.len);
		din = segment.buf;
	}

	master_cmd_reg_val |= (prams & M_PARAMS_MSK);
	write32(&regs->geni_m_cmd0, master_cmd_reg_val);

	return qup_handle_transfer(bus, dout, din, segment.len);
}

int platform_i2c_transfer(unsigned int bus, struct i2c_msg *segments,
			  int seg_count)
{
	struct i2c_msg *seg = segments;
	int ret = 0;

	while (!ret && seg_count--) {
		/* Stretch means end with repeated start, not stop */
		u32 stretch = (seg_count ? 1 : 0);
		u32 m_param = 0;

		m_param |= (stretch << 2);
		m_param |= ((seg->slave & 0x7F) << 9);
		ret = i2c_do_xfer(bus, *seg, m_param);
		seg++;
	}
	return ret;
}
