#include <config.h>
#include <common.h>
#include <mapmem.h>
#include <asm/io.h>
#include <linux/err.h>
#include "rts_crypto.h"
#include "bspchip.h"

struct rlx_crypto_ctx ctx;

static unsigned int rlx_crypto_read(unsigned int reg)
{
	return readl(0xb8600000UL + reg);
}

static int rlx_crypto_write(unsigned int reg, unsigned int value)
{
	writel(value, 0xb8600000UL + reg);

	return 0;
}

static int rlx_crypto_do(void)
{
	int ret = 0;
	u32 val;

	/* start crypto */
	val = rlx_crypto_read(RLX_REG_CIPHER_CTL);
	val &= ~0x3;
	val |= 0x1;
	rlx_crypto_write(RLX_REG_CIPHER_CTL, val);

	do {
		val = rlx_crypto_read(RLX_REG_CIPHER_INT_FLAG);
		val &= 0xf;
		if (val) {
			rlx_crypto_write(RLX_REG_CIPHER_INT_FLAG, val);
			break;
		}
	} while (1);

	if (val & 0xe) {
		printf("int report error:0x%x, retry\n", val & 0xe);
		ret = -EAGAIN;
		goto do_out;
	}

	/* crypto status */
	val = rlx_crypto_read(RLX_REG_CIPHER_STS);
	if ((val & 0x1) == 0) {
		printf("cipher status error, retry\n");
		ret = -EAGAIN;
		goto do_out;
	}

do_out:
	/* stop crypto */
	val = rlx_crypto_read(RLX_REG_CIPHER_CTL);
	val &= ~0x3;
	val |= 0x2;
	rlx_crypto_write(RLX_REG_CIPHER_CTL, val);

	return ret;
}

static int rlx_crypto(u8 *dst, u8 *src, unsigned int nbytes,
		      unsigned int mask)
{
	u32 val, reg;
	int ret = 0;
	u32 *reg_val;
	u32 *buf;
	unsigned int align_mask;
	unsigned int *table_in_ptr = (unsigned int *)ctx.table_in;
	unsigned int *table_out_ptr = (unsigned int *)ctx.table_out;

	flush_dcache_range((ulong)src, (ulong)src + nbytes);

	set_bit(24, (void *)CRYPTO_CLOCK);
	mdelay(1);

	rlx_crypto_write(RLX_REG_CIPHER_INT_FLAG, 0xf);
	memset(&ctx, 0, sizeof(ctx));
	ctx.keylen = 16;
	ctx.mask = mask;

	val = 0;
	align_mask = 0x7;
	if (mask & FLAGS_AES) {
		val = val | ((u32)0x0 << RLX_ALGORITHM_SEL);
		if (ctx.keylen == 16)
			val = val | ((u32)0x0 << RLX_AES_MODE_SEL);
		else if (ctx.keylen == 24)
			val = val | ((u32)0x1 << RLX_AES_MODE_SEL);
		else if (ctx.keylen == 32)
			val = val | ((u32)0x2 << RLX_AES_MODE_SEL);
		align_mask = 0xf;
	} else if (mask & FLAGS_DES) {
		val = val | ((u32)0x1 << RLX_ALGORITHM_SEL);
	} else if (mask & FLAGS_DES3) {
		val = val | ((u32)0x2 << RLX_ALGORITHM_SEL);
	}

	if (nbytes & align_mask) {
		ret = -EINVAL;
		printf("cryto data not aligned!");
		goto out;
	}

	if (mask & FLAGS_ECB)
		val = val | ((u32)0x0 << RLX_OPERATION_MODE);
	else if (mask & FLAGS_CBC)
		val = val | ((u32)0x1 << RLX_OPERATION_MODE);
	else if (mask & FLAGS_CBCCS1)
		val = val | ((u32)0x2 << RLX_OPERATION_MODE);
	else if (mask & FLAGS_CBCCS2)
		val = val | ((u32)0x3 << RLX_OPERATION_MODE);
	else if (mask & FLAGS_CBCCS3)
		val = val | ((u32)0x4 << RLX_OPERATION_MODE);
	else if (mask & FLAGS_CTR)
		val = val | ((u32)0x5 << RLX_OPERATION_MODE);

	if (mask & FLAGS_ECB)
		ctx.ivflag = 0;
	else
		ctx.ivflag = 1;

	if (mask & FLAGS_AES)
		ctx.ivlen = 16;
	else
		ctx.ivlen = 8;

	if (mask & (FLAGS_ECB | FLAGS_CBC))
		val = val | ((u32)0x1 << RLX_PADDING_SEL);
	else
		val = val | ((u32)0x0 << RLX_PADDING_SEL);

	if (mask & FLAGS_ENCRYPT)
		val = val | ((u32)0x0 << RLX_ENCTYPT_DECTYPT_SEL);
	else if (mask & FLAGS_DECRYPT)
		val = val | ((u32)0x1 << RLX_ENCTYPT_DECTYPT_SEL);

	rlx_crypto_write(RLX_REG_CIPHER_CTL, val);

	/* key */
	reg = efuse_read_byte(27);
	if (!(reg & 0x80)) {
		load_key_from_sd();
		buf = map_sysmem(CONFIG_LOADADDR, ctx.keylen);
		memcpy(ctx.key, buf, ctx.keylen);
		reg_val = (u32 *)ctx.key;
		rlx_crypto_write(RLX_REG_KEY_DATA0, cpu_to_be32(reg_val[0]));
		rlx_crypto_write(RLX_REG_KEY_DATA1, cpu_to_be32(reg_val[1]));
		if (ctx.keylen > 8) {
			rlx_crypto_write(RLX_REG_KEY_DATA2,
					cpu_to_be32(reg_val[2]));
			rlx_crypto_write(RLX_REG_KEY_DATA3,
					cpu_to_be32(reg_val[3]));
		}
		if (ctx.keylen > 16) {
			rlx_crypto_write(RLX_REG_KEY_DATA4,
					cpu_to_be32(reg_val[4]));
			rlx_crypto_write(RLX_REG_KEY_DATA5,
					cpu_to_be32(reg_val[5]));
		}
		if (ctx.keylen > 24) {
			rlx_crypto_write(RLX_REG_KEY_DATA6,
					cpu_to_be32(reg_val[6]));
			rlx_crypto_write(RLX_REG_KEY_DATA7,
					cpu_to_be32(reg_val[7]));
		}
	} else {
		rlx_crypto_write(RLX_REG_CIPHER_KEY, 1);
		efuse_before_load_aes();
		efuse_load_aes();
	}
	/* data in & out */
	rlx_crypto_write(RLX_REG_IN_TABLE_ADDR,
		virt_to_phys(ctx.table_in));
	rlx_crypto_write(RLX_REG_OUT_TABLE_ADDR,
		virt_to_phys(ctx.table_out));

	*(table_in_ptr++) = virt_to_phys(src);
	*(table_in_ptr++) = nbytes;
	flush_dcache_range((ulong)ctx.table_in, (ulong)ctx.table_in + 8);

	rlx_crypto_write(RLX_REG_IN_BUF_NUM, 1);

	*(table_out_ptr++) = virt_to_phys(dst);
	*(table_out_ptr++) = nbytes;
	flush_dcache_range((ulong)ctx.table_out, (ulong)ctx.table_out + 8);

	rlx_crypto_write(RLX_REG_OUT_BUF_NUM, 1);
	rlx_crypto_write(RLX_REG_DATA_IN_LENGTH, nbytes);

	ret = rlx_crypto_do();

	invalidate_dcache_range((ulong)dst, (ulong)dst + nbytes - 1);

out:
	clear_bit(24, (void *)CRYPTO_CLOCK);

	return ret;
}

int crypto_init(void)
{

#ifndef CONFIG_TARGET_FPGA
	rts_sys_mem_up(SYS_MEM_SD_CIPHER);
	mdelay(5);
#endif

	set_bit(4, (void *)0xb8860008);
	clear_bit(4, (void *)0xb8860008);
	udelay(2);
	return 0;
}

/* aes */
int rlx_aes_ecb_encrypt(u8 *dst, u8 *src, unsigned int nbytes)
{
	int ret;

	do {
		ret = rlx_crypto(dst, src, nbytes,
			  FLAGS_ENCRYPT | FLAGS_AES | FLAGS_ECB);
	} while (ret == -EAGAIN);

	return ret;
}

int rlx_aes_ecb_decrypt(u8 *dst, u8 *src, unsigned int nbytes)
{
	int ret;

	do {
		ret = rlx_crypto(dst, src, nbytes,
			  FLAGS_DECRYPT | FLAGS_AES | FLAGS_ECB);
	} while (ret == -EAGAIN);

	return ret;
}

static int strtou32(const char *str, unsigned int base, u32 *result)
{
	char *ep;

	*result = simple_strtoul(str, &ep, base);
	if (ep == str || *ep != '\0')
		return -EINVAL;

	return 0;
}

static int do_keyprog(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
{
	u32 val;
	int i;
	u8 key[16];

	if (argc != 17)
		return CMD_RET_USAGE;

	for (i = 0; i < 16; i++) {
		if (strtou32(argv[i + 1], 16, &val))
			return CMD_RET_USAGE;

		key[i] = val;
	}

	efuse_write(0, (u32 *)key, 16);

	return 0;
}

U_BOOT_CMD(
	keyprog, 64, 0, do_keyprog,
	"keyprog <hexval> <hexval>... - program 16 byte(128 bits) key", ""
);

