/*
 * Copyright (C) Marvell International Ltd. and its affiliates
 *
 * SPDX-License-Identifier:	GPL-2.0
 */

#include <common.h>
#include <spl.h>
#include <asm/io.h>
#include <asm/arch/cpu.h>
#include <asm/arch/soc.h>

#include "seq_exec.h"
#include "high_speed_env_spec.h"

#include "../../../drivers/ddr/marvell/a38x/ddr3_init.h"

#if defined(MV_DEBUG_INIT_FULL) || defined(MV_DEBUG)
#define DB(x)	x
#else
#define DB(x)
#endif

/* Array for mapping the operation (write, poll or delay) functions */
op_execute_func_ptr op_execute_func_arr[] = {
	write_op_execute,
	delay_op_execute,
	poll_op_execute
};

int write_op_execute(u32 serdes_num, struct op_params *params, u32 data_arr_idx)
{
	u32 unit_base_reg, unit_offset, data, mask, reg_data, reg_addr;

	/* Getting write op params from the input parameter */
	data = params->data[data_arr_idx];
	mask = params->mask;

	/* an empty operation */
	if (data == NO_DATA)
		return MV_OK;

	/* get updated base address since it can be different between Serdes */
	CHECK_STATUS(hws_get_ext_base_addr(serdes_num, params->unit_base_reg,
					   params->unit_offset,
					   &unit_base_reg, &unit_offset));

	/* Address calculation */
	reg_addr = unit_base_reg + unit_offset * serdes_num;

#ifdef SEQ_DEBUG
	printf("Write: 0x%x: 0x%x (mask 0x%x) - ", reg_addr, data, mask);
#endif
	/* Reading old value */
	reg_data = reg_read(reg_addr);
	reg_data &= (~mask);

	/* Writing new data */
	data &= mask;
	reg_data |= data;
	reg_write(reg_addr, reg_data);

#ifdef SEQ_DEBUG
	printf(" - 0x%x\n", reg_data);
#endif

	return MV_OK;
}

int delay_op_execute(u32 serdes_num, struct op_params *params, u32 data_arr_idx)
{
	u32 delay;

	/* Getting delay op params from the input parameter */
	delay = params->wait_time;
#ifdef SEQ_DEBUG
	printf("Delay: %d\n", delay);
#endif
	mdelay(delay);

	return MV_OK;
}

int poll_op_execute(u32 serdes_num, struct op_params *params, u32 data_arr_idx)
{
	u32 unit_base_reg, unit_offset, data, mask, num_of_loops, wait_time;
	u32 poll_counter = 0;
	u32 reg_addr, reg_data;

	/* Getting poll op params from the input parameter */
	data = params->data[data_arr_idx];
	mask = params->mask;
	num_of_loops = params->num_of_loops;
	wait_time = params->wait_time;

	/* an empty operation */
	if (data == NO_DATA)
		return MV_OK;

	/* get updated base address since it can be different between Serdes */
	CHECK_STATUS(hws_get_ext_base_addr(serdes_num, params->unit_base_reg,
					   params->unit_offset,
					   &unit_base_reg, &unit_offset));

	/* Address calculation */
	reg_addr = unit_base_reg + unit_offset * serdes_num;

	/* Polling */
#ifdef SEQ_DEBUG
	printf("Poll:  0x%x: 0x%x (mask 0x%x)\n", reg_addr, data, mask);
#endif

	do {
		reg_data = reg_read(reg_addr) & mask;
		poll_counter++;
		udelay(wait_time);
	} while ((reg_data != data) && (poll_counter < num_of_loops));

	if ((poll_counter >= num_of_loops) && (reg_data != data)) {
		DEBUG_INIT_S("poll_op_execute: TIMEOUT\n");
		return MV_TIMEOUT;
	}

	return MV_OK;
}

enum mv_op get_cfg_seq_op(struct op_params *params)
{
	if (params->wait_time == 0)
		return WRITE_OP;
	else if (params->num_of_loops == 0)
		return DELAY_OP;

	return POLL_OP;
}

int mv_seq_exec(u32 serdes_num, u32 seq_id)
{
	u32 seq_idx;
	struct op_params *seq_arr;
	u32 seq_size;
	u32 data_arr_idx;
	enum mv_op curr_op;

	DB(printf("\n### mv_seq_exec ###\n"));
	DB(printf("seq id: %d\n", seq_id));

	if (hws_is_serdes_active(serdes_num) != 1) {
		printf("mv_seq_exec_ext:Error: SerDes lane %d is not valid\n",
		       serdes_num);
		return MV_BAD_PARAM;
	}

	seq_arr = serdes_seq_db[seq_id].op_params_ptr;
	seq_size = serdes_seq_db[seq_id].cfg_seq_size;
	data_arr_idx = serdes_seq_db[seq_id].data_arr_idx;

	DB(printf("seq_size: %d\n", seq_size));
	DB(printf("data_arr_idx: %d\n", data_arr_idx));

	/* Executing the sequence operations */
	for (seq_idx = 0; seq_idx < seq_size; seq_idx++) {
		curr_op = get_cfg_seq_op(&seq_arr[seq_idx]);
		op_execute_func_arr[curr_op](serdes_num, &seq_arr[seq_idx],
					     data_arr_idx);
	}

	return MV_OK;
}