/*
 * Command for accessing Ethernet efuse.
 *
 * Copyright (C) Realsil Corporation
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */
#include <common.h>
#include <asm/io.h>
#include "bspchip.h"

#define get_val(addr)		REG32(addr)
#define set_val(addr, val)	REG32(addr) = (val)
#define clear_bit(addr, val)	set_val((addr), (get_val(addr) & ~(val)))
#define set_bit(addr, val)	set_val((addr), (get_val(addr) | (val)))
extern void write_mask(u32 reg, u32 bit, u32 mask);

unsigned int efuse_read_word(unsigned int addr)
{
	u32 waddr, val;

	waddr = addr / 32;
	if (waddr > 31)
		waddr = waddr - 32;

	/*enable double bit and ecc*/
	set_val(HW_CTRL, DOUBLE_BIT_ECC);
	/*set read mode*/
	write_mask(HW_CTRL, A_READ << MODE_SELECT_SHIFT, MODE_SELECT_MASK);
	clear_bit(SF_CTRL, RG_PD_SF);
	clear_bit(SF_CTRL, RG_CSB_SF);
	set_bit(SF_CTRL, RG_PGENB_SF);
	set_bit(SF_CTRL, RG_RSB_SF);
	set_bit(SF_CTRL, RG_LOAD_SF);
	set_val(ADDR_CTRL, addr);
	udelay(1);
	set_bit(SF_CTRL, RG_STROBE_SF);
	udelay(1);
	clear_bit(SF_CTRL, RG_STROBE_SF);
	clear_bit(SF_CTRL, RG_LOAD_SF);
	set_bit(SF_CTRL, RG_CSB_SF);
	set_bit(SF_CTRL, RG_PD_SF);
	val = get_val(EFUSE_DATA_0 + waddr * 4);

	return val;
}

int do_efuse_read(u32 addr, u16 offset, u16 length)
{
	u32 *addr_t;
	u32 *efuse_data_addr;
	u8 i;
	u8 row_start;
	u32 mode, temp;

	/*sw or hw operation, default is hw*/
	mode = 2;

	addr_t = (u32 *)addr;
	row_start = offset / 32;
	if (row_start > 31)
		row_start = row_start - 32;
	efuse_data_addr = (u32 *)(EFUSE_DATA_0 + row_start * 4);

	if (mode == 1) {
		set_val(SF_HW_CTRL, SW_ENABLE);
		for (i = 0; i < length/32; i++) {
			temp = efuse_read_word(offset);
			*addr_t = temp;
			offset = offset + 32;
			addr_t++;
		}
	} else {
		/*enable HW control*/
		set_val(SF_HW_CTRL, HW_ENABLE);
		/*enable double bit and ecc*/
		set_val(HW_CTRL, DOUBLE_BIT_ECC);
		/*power on*/
		clear_bit(SF_CTRL, RG_PD_SF);
		/*disable redundancy*/
		set_bit(SF_CTRL, RG_RSB_SF);
		/*set delay counter*/
		write_mask(READ_DELAY_COUNTER,
			PD_SETUP_VALUE << PD_SETUP_DELAY_SHIFT,
			PD_SETUP_DELAY_MASK);
		write_mask(READ_DELAY_COUNTER,
			STROBE_READ_VALUE << STROBE_READ_DELAY_SHIFT,
			STROBE_READ_DELAY_MASK);
		/*set burst length*/
		set_val(BURST_CTRL, length);
		/*set address*/
		set_val(ADDR_CTRL, offset);
		/*set read mode*/
		write_mask(HW_CTRL, A_READ << MODE_SELECT_SHIFT,
			MODE_SELECT_MASK);
		/*start read efuse*/
		set_val(HW_START_REG, HW_START);
		/*wait read complete*/
		while (1) {
			temp = get_val(HW_START_REG);
			if (temp == 0)
				break;
		}
		/*receive word data*/
		for (i = 0; i < (length/32); i++) {
			*addr_t = *efuse_data_addr;
			addr_t++;
			efuse_data_addr++;
		}
	}

	return 0;
}

unsigned int efuse_read_bit(unsigned int addr)
{
	u32 waddr, val;

	waddr = addr % 32;
	/*enable SW control*/
	set_val(SF_HW_CTRL, SW_ENABLE);
	val = efuse_read_word(addr) & (1 << waddr);

	return (val >> waddr);
}

void efuse_load_aes(u16 length)
{
	u16 offset = 0;
	u32 temp;

	/*enable HW control*/
	set_val(SF_HW_CTRL, HW_ENABLE);
	/*enable double bit and ecc*/
	set_val(HW_CTRL, DOUBLE_BIT_ECC);
	/*power on*/
	clear_bit(SF_CTRL, RG_PD_SF);
	/*disable redundancy*/
	set_bit(SF_CTRL, RG_RSB_SF);
	/*set delay counter*/
	write_mask(READ_DELAY_COUNTER,
		PD_SETUP_VALUE << PD_SETUP_DELAY_SHIFT,
		PD_SETUP_DELAY_MASK);
	write_mask(READ_DELAY_COUNTER,
		STROBE_READ_VALUE << STROBE_READ_DELAY_SHIFT,
		STROBE_READ_DELAY_MASK);
	/*set burst length*/
	set_val(BURST_CTRL, length);
	/*set address*/
	set_val(ADDR_CTRL, offset);
	/*set read mode*/
	write_mask(HW_CTRL, A_READ << MODE_SELECT_SHIFT, MODE_SELECT_MASK);
	/*start read efuse*/
	set_val(HW_START_REG, HW_START);
	/*wait read complete*/
	while (1) {
		temp = get_val(HW_START_REG);
		if (temp == 0)
			break;
	}
}

void efuse_write(u32 dst, u32 *data, int len)
{
	u32 addr = 0;
	u32 i, j, k;

	len /= 32;
	dst /= 32;
	set_val(SF_HW_CTRL, SW_ENABLE);
	/*power on*/
	clear_bit(SF_CTRL, RG_PD_SF);
	/*disable redundancy*/
	set_bit(SF_CTRL, RG_RSB_SF);
	/*set write mode*/
	write_mask(HW_CTRL, A_PGM << MODE_SELECT_SHIFT, MODE_SELECT_MASK);

	udelay(1);
	set_bit(SF_CTRL, RG_PS_SF);
	clear_bit(SF_CTRL, RG_CSB_SF | RG_PGENB_SF);

	for (i = 0; i < len; i++) {
		k = 1;
		for (j = 0; j < 32; j++) {
			if (data[i] & k) {
				addr = ((dst + i) << 5) | j;
				set_val(ADDR_CTRL, addr);
				set_bit(SF_CTRL, RG_STROBE_SF);
				udelay(1);
				clear_bit(SF_CTRL, RG_STROBE_SF);
			}
			k = k << 1;
		}
	}
	set_bit(SF_CTRL, RG_CSB_SF | RG_PGENB_SF);
	clear_bit(SF_CTRL, RG_PS_SF);
	set_bit(SF_CTRL, RG_PD_SF);

}

static int do_efuse_write(u32 addr, u16 offset, u16 length)
{

	u32 *addr_t;
	u32 *efuse_data_addr;
	u8 i, temp;

	/*sw or hw operation, default is hw*/
	u8 mode = 2;

	addr_t = (u32 *)addr;
	efuse_data_addr = (u32 *)EFUSE_PROGRAM_DATA;

	set_val(EFUSE_VQPS_EN_REG, EFUSE_VQPS_EN);
	if (mode == 1)
		efuse_write(offset, addr_t, length);
	else {
		/*power on*/
		clear_bit(SF_CTRL, RG_PD_SF);
		/*disable redundancy*/
		set_bit(SF_CTRL, RG_RSB_SF);
		/*set delay counter*/
		write_mask(PROGRAM_DELAY_COUNTER,
		STROBE_WRITE_VALUE << STROBE_WRITE_DELAY_SHIFT,
		STROBE_WRITE_DELAY_MASK);
		write_mask(PROGRAM_DELAY_COUNTER,
		PS_TO_CSB_HOLD_WRITE_VALUE << PS_TO_CSB_HOLD_DELAY_SHIFT,
		PS_TO_CSB_HOLD_DELAY_MASK);
		write_mask(PROGRAM_DELAY_COUNTER,
		PS_TO_CSB_SETUP_WRITE_VALUE << PS_TO_CSB_SETUP_DELAY_SHIFT,
		PS_TO_CSB_SETUP_DELAY_MASK);
		write_mask(PROGRAM_DELAY_COUNTER,
		PD_TO_PS_SETUP_WRITE_VALUE << PD_TO_PS_SETUP_DELAY_SHIFT,
		PD_TO_PS_SETUP_DELAY_MASK);
		/*set write mode*/
		write_mask(HW_CTRL, A_PGM << MODE_SELECT_SHIFT,
		MODE_SELECT_MASK);
		for (i = 0; i < (length/32); i++, addr_t++) {
			/*set burst length*/
			set_val(BURST_CTRL, BURST_LENGTH);
			/*set address*/
			set_val(ADDR_CTRL, offset);
			/*set data*/
			*efuse_data_addr = *addr_t;
			/*start write efuse*/
			set_val(HW_START_REG, HW_START);
			/*wait write complete*/
			while (1) {
				temp = get_val(HW_START_REG);
				if (temp == 0)
					break;
			}
			offset = offset + 32;
		}
		if (length < 32) {
			/*set burst length*/
			set_val(BURST_CTRL, length);
			/*set address*/
			set_val(ADDR_CTRL, offset);
			/*set data*/
			*efuse_data_addr = *addr_t;
			/*start write efuse*/
			set_val(HW_START_REG, HW_START);
			/*wait write complete*/
			while (1) {
				temp = get_val(HW_START_REG);
				if (temp == 0)
					break;
			}
		}
	}

	set_val(EFUSE_VQPS_EN_REG, 0);
	return 0;
}

static int do_efuse(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	int ret = 0;
	char *cmd;
	char *endp;
	u16 offset, length;
	u32 addr;

	/* at least five arguments please */
	if (argc < 5)
		goto usage;

	cmd = argv[1];

	addr = simple_strtoul(argv[2], &endp, 16);
	if (*argv[2] == 0 || *endp != 0)
		return -1;

	offset = simple_strtoul(argv[3], &endp, 16);
	if (*argv[3] == 0 || *endp != 0)
		return -1;

	length = simple_strtoul(argv[4], &endp, 16);
	if (*argv[4] == 0 || *endp != 0)
		return -1;

	if ((offset + length) > 1024) {
		printf("exceeds the maximum of 1024 bits\n");
		return -1;
	}

	if (strcmp(cmd, "read") == 0)
		ret = do_efuse_read(addr, offset, length);
	else if (strcmp(cmd, "write") == 0)
		ret = do_efuse_write(addr, offset, length);
	else
		ret = -1;

	if (ret != -1)
		return ret;

usage:
	return CMD_RET_USAGE;
}

U_BOOT_CMD(
	efuse, 5, 0, do_efuse,
	"read write\n",
	"read addr offset len	- read `len' bytes starting at\n"
	"				          `offset' of efuse to memory at `addr'\n"
	"efuse write addr offset len - write `len' bytes from memory\n"
	"				          at `addr' to efuse at `offset'\n"
	"offset is 0 - 1023, when read unit is word align\n"
	"length is 1 - 1024, when read unit is word align\n"
	"addr, offset and len should be hexadecimal.\n"
);
