// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */

#include <linux/amlogic/aml_kt.h>

#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fcntl.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/amlogic/iomap.h>
#include "aml_seckey_log.h"
#include "aml_kt_dev.h"

#define AML_KT_DEVICE_NAME "aml_kt"
#define DEVICE_INSTANCES 1

#define KT_MODE_READ  (0)
#define KT_MODE_NAGRA (1)
#define KT_MODE_NSK   (2)
#define KT_MODE_HOST  (3)

#define KT_CLEAN_KTE  (1)
#define KT_STS_OK     (0)
#define KT_PENDING    (1)
#define KT_STS_DENIED (3)
#define KT_SUCCESS    (0)
#define KT_ERROR      (-1)

#define KT_IV_SPEC_ALGO	  (0xff)

#define KT_PENDING_WAIT_TIMEOUT (20000)

#define KTE_PENDING_OFFSET  (31)
#define KTE_PENDING_MASK    (1)
#define KTE_STATUS_OFFSET   (29)
#define KTE_STATUS_MASK	    (3)
#define KTE_CLEAN_OFFSET    (28)
#define KTE_CLEAN_MASK	    (1)
#define KTE_MODE_OFFSET	    (26)
#define KTE_MODE_MASK	    (3)
#define KTE_FLAG_OFFSET	    (24)
#define KTE_FLAG_MASK	    (3)
#define KTE_KEYALGO_OFFSET  (20)
#define KTE_KEYALGO_MASK    (0xf)
#define KTE_USERID_OFFSET   (16)
#define KTE_USERID_MASK	    (0xf)
#define KTE_KTE_OFFSET	    (8)
#define KTE_KTE_MASK	    (0xff)

#define HANDLE_TO_ENTRY(h) ((h) & ~(1 << KT_IV_FLAG_OFFSET))

enum KT_ALGO_CAPABILITY {
	KT_CAP_AES = 0x1,
	KT_CAP_DES = 0x2,
	KT_CAP_TDES = 0x4,
	KT_CAP_S17 = 0x8,
	KT_CAP_SM4 = 0x10,
	KT_CAP_NDL = 0x80,
	KT_CAP_ND = 0x100,
	KT_CAP_CSA3 = 0x200,
	KT_CAP_CSA2 = 0x400,
	KT_CAP_HMAC = 0x2000,
};

enum KT_UID_CAPABILITY {
	KT_CAP_M2M_0 = 0x1,
	KT_CAP_M2M_1 = 0x2,
	KT_CAP_M2M_2 = 0x4,
	KT_CAP_M2M_3 = 0x8,
	KT_CAP_M2M_4 = 0x10,
	KT_CAP_M2M_5 = 0x20,
	KT_CAP_M2M_ANY = 0x80,
	KT_CAP_TSD = 0x100,
	KT_CAP_TSN = 0x200,
	KT_CAP_TSE = 0x400,
};

/*bit 0~11: frobenious cycles*/
/*bit 12: variant cryptography
 *        0: variant 5
 *        1: variant 6
 */
 /*variant: 6, frobenious cycles:0x5a5*/
#define S17_CFG_DEFAULT     (0x15a5)

static dev_t aml_kt_devt;
struct aml_kt_dev aml_kt_dev;
EXPORT_SYMBOL(aml_kt_dev);

static struct class *aml_kt_class;
static struct dentry *aml_kt_debug_dent;
u32 kt_log_level = 3;

static int aml_kt_init_dbgfs(void)
{
	if (!aml_kt_debug_dent) {
		aml_kt_debug_dent = debugfs_create_dir("aml_kt", NULL);
		if (!aml_kt_debug_dent) {
			KT_LOGE("can not create debugfs directory\n");
			return -ENOMEM;
		}

		debugfs_create_u32("log_level", 0644, aml_kt_debug_dent, &kt_log_level);
	}
	return 0;
}

static int aml_kt_lock(struct aml_kt_dev *dev)
{
	int cnt = 0;
	int ret = KT_SUCCESS;

	while (ioread32((char *)dev->base_addr + dev->reg.rdy_offset) ==
			KT_STS_OK) {
		if (cnt++ > KT_PENDING_WAIT_TIMEOUT) {
			KT_LOGE("Error: wait KT ready timeout\n");
			ret = KT_ERROR;
		}
	}

	return ret;
}

static void aml_kt_unlock(struct aml_kt_dev *dev)
{
	iowrite32(1, (char *)dev->base_addr + dev->reg.rdy_offset);
}

static bool aml_kt_handle_valid(struct aml_kt_dev *dev, u32 handle)
{
	u32 entry = HANDLE_TO_ENTRY(handle);
	u8 is_iv = handle >> KT_IV_FLAG_OFFSET;

	if (is_iv) {
		if (entry < dev->ive_start || dev->ive_end <= entry) {
			KT_LOGE("Error: Invalid handle (%#x)\n", handle);
			return false;
		}
	} else {
		if (entry < dev->kte_start || dev->kte_end <= entry) {
			KT_LOGE("Error: Invalid handle (%#x)\n", handle);
			return false;
		}
	}

	return true;
}

int aml_kt_handle_to_kte(struct aml_kt_dev *dev, u32 handle, u32 *kte)
{
	if (unlikely(!dev)) {
		KT_LOGE("Empty aml_kt_dev\n");
		return KT_ERROR;
	}

	if (!aml_kt_handle_valid(dev, handle)) {
		KT_LOGE("Invalid handle\n");
		return KT_ERROR;
	}

	if (!kte) {
		KT_LOGE("Invalid kte\n");
		return KT_ERROR;
	}

	*kte = HANDLE_TO_ENTRY(handle);

	return KT_SUCCESS;
}
EXPORT_SYMBOL(aml_kt_handle_to_kte);

static int aml_kt_read_pending(struct aml_kt_dev *dev)
{
	int ret = KT_ERROR;
	int cnt = 0;
	u32 reg_ret = 0;

	do {
		reg_ret = ioread32((char *)dev->base_addr + dev->reg.cfg_offset);
		if (cnt++ > KT_PENDING_WAIT_TIMEOUT) {
			KT_LOGE("Error: wait KT pending done timeout\n");
			ret = KT_ERROR;
			return ret;
		}
	} while (reg_ret & (KT_PENDING << KTE_PENDING_OFFSET));

	reg_ret = (reg_ret >> KTE_STATUS_OFFSET) & KTE_STATUS_MASK;
	if (reg_ret == KT_STS_OK) {
		ret = KT_SUCCESS;
	} else {
		KT_LOGE("Error: KT return error:%#x\n", reg_ret);
		ret = KT_ERROR;
	}

	return ret;
}

/*
 * Determine whether the slot is reserved (chips: A5/C3)
 */
static bool aml_kt_slot_reserved(u32 index)
{
	u32 i;
	bool reserved = false;
	u32 res_size = 0;
	u32 res_list[] = {5, 6, 7, 8, 9, 10, 11, 12};

	res_size = ARRAY_SIZE(res_list);
	for (i = 0; i < res_size; i++) {
		if (index == res_list[i]) {
			reserved = true;
			break;
		}
	}

	return reserved;
}

int aml_kt_get_status(struct aml_kt_dev *dev, u32 handle, u32 *key_sts)
{
	int ret = KT_SUCCESS;
	u32 kte = 0;
	u32 reg_val = 0;
	u32 reg_offset = 0;
	u32 reg_ret = 0;

	if (unlikely(!dev)) {
		KT_LOGE("Empty aml_kt_dev\n");
		return KT_ERROR;
	}

	ret = aml_kt_handle_to_kte(dev, handle, &kte);
	if (ret != KT_SUCCESS) {
		KT_LOGE("kte get error\n");
		return KT_ERROR;
	}

	mutex_lock(&dev->lock);
	if (aml_kt_lock(dev) != KT_SUCCESS) {
		KT_LOGE("lock error\n");
		ret = KT_ERROR;
		goto unlock_mutex;
	}

	reg_offset = dev->reg.cfg_offset;
	reg_val = (KT_PENDING << KTE_PENDING_OFFSET |
			KT_MODE_READ << KTE_MODE_OFFSET |
			kte << KTE_KTE_OFFSET);
	iowrite32(reg_val, (char *)dev->base_addr + reg_offset);

	if (aml_kt_read_pending(dev) != KT_SUCCESS) {
		KT_LOGE("pending error kte[%d]\n", kte);
		ret = KT_ERROR;
		goto unlock_kt;
	}

	reg_ret = ioread32((char *)dev->base_addr + dev->reg.sts_offset);
	if (reg_ret != KT_ERROR) {
		KT_LOGD("kte=%d KT_REE_STS=0x%08x\n", kte, reg_ret);
		memcpy(key_sts, &reg_ret, sizeof(reg_ret));
		ret = KT_SUCCESS;
		goto unlock_kt;
	}

unlock_kt:
	aml_kt_unlock(dev);
unlock_mutex:
	mutex_unlock(&dev->lock);

	return ret;
}
EXPORT_SYMBOL(aml_kt_get_status);

int aml_kt_alloc(struct aml_kt_dev *dev, u32 flag, u32 *handle)
{
	int res = KT_SUCCESS;
	u32 entry = 0;
	u8 is_iv = 0;
	u32 entry_start = 0;
	u32 entry_end = 0;

	if (unlikely(!dev)) {
		KT_LOGE("Empty aml_kt_dev\n");
		return KT_ERROR;
	}

	if (!handle) {
		KT_LOGE("handle is null\n");
		return KT_ERROR;
	}

	if (flag == AML_KT_ALLOC_FLAG_IV) {
		is_iv = 1;
		entry_start = dev->ive_start;
		entry_end = dev->ive_end;
	} else if (flag == AML_KT_ALLOC_FLAG_HOST) {
		entry_start = dev->kte_start;
		entry_end = dev->kte_end;
	} else if (flag == AML_KT_ALLOC_FLAG_NSK_M2M) {
		/* TO-DO */
	} else {
		entry_start = dev->kte_start;
		entry_end = dev->kte_end;
	}

	mutex_lock(&dev->lock);
	for (entry = entry_start; entry < entry_end; entry++) {
		if (is_iv) {
			if (dev->kt_iv_slot[entry])
				continue;

			dev->kt_iv_slot[entry] = kzalloc(sizeof(*dev->kt_iv_slot[entry]),
				GFP_KERNEL);
			if (!dev->kt_iv_slot[entry]) {
				KT_LOGE("Error: KT ive kzalloc failed\n");
				res = KT_ERROR;
				goto exit;
			}
		} else {
			if (dev->kt_slot[entry])
				continue;

			/* Skip reserved key slots */
			if (dev->kt_reserved && aml_kt_slot_reserved(entry))
				continue;

			dev->kt_slot[entry] = kzalloc(sizeof(*dev->kt_slot[entry]),
				GFP_KERNEL);
			if (!dev->kt_slot[entry]) {
				KT_LOGE("Error: KT kte kzalloc failed\n");
				res = KT_ERROR;
				goto exit;
			}
		}
		break;
	}

	if (entry < entry_end) {
		*handle = entry | (is_iv << KT_IV_FLAG_OFFSET);
		KT_LOGI("flag:%#x, is_iv:%d, handle:%#x\n", flag, is_iv, *handle);
		res = KT_SUCCESS;
	} else {
		KT_LOGE("Error: KT alloc return error, no kte/ive available\n");
		res = KT_ERROR;
	}

exit:
	mutex_unlock(&dev->lock);

	return res;
}
EXPORT_SYMBOL(aml_kt_alloc);

int aml_kt_config(struct aml_kt_dev *dev, struct amlkt_cfg_param key_cfg)
{
	int ret = KT_SUCCESS;
	u32 index = 0;
	u8 is_iv = 0;
	u8 is_dsc = 0;

	if (unlikely(!dev)) {
		KT_LOGE("Empty aml_kt_dev\n");
		return KT_ERROR;
	}

	ret = aml_kt_handle_to_kte(dev, key_cfg.handle, &index);
	if (ret != KT_SUCCESS) {
		KT_LOGE("index get error\n");
		return ret;
	}

	is_iv = key_cfg.handle >> KT_IV_FLAG_OFFSET;
	if (is_iv) {
		if (!dev->kt_iv_slot[index]) {
			KT_LOGE("kt_iv_slot is null\n");
			return KT_ERROR;
		}
	} else {
		if (!dev->kt_slot[index]) {
			KT_LOGE("kt_slot is null\n");
			return KT_ERROR;
		}
	}

	KT_LOGD("--------------------------------------------------------------\n");
	KT_LOGD("flag:%d, algo:%d, uid:%d, src:%d\n", key_cfg.key_flag, key_cfg.key_algo,
	     key_cfg.key_userid, key_cfg.key_source);

	/* Conversion T5W key algorithm */
	if (dev->algo_cap == 0x407 && dev->user_cap == 0x600) {
		if (key_cfg.key_algo == AML_KT_ALGO_AES) {
			key_cfg.key_algo = 2;
			KT_LOGD("AES conversion\n");
		} else if (key_cfg.key_algo == AML_KT_ALGO_DES) {
			key_cfg.key_algo = 0;
			KT_LOGD("DES conversion\n");
		} else if (key_cfg.key_algo == AML_KT_ALGO_CSA2) {
			key_cfg.key_algo = 0;
			KT_LOGD("CSA2 conversion\n");
		} else {
			KT_LOGD("No need conversion\n");
		}
	}

	/* Check Invalid key algorithm */
	KT_LOGD("dev->algo_cap:0x%x\n", dev->algo_cap);
	KT_LOGD("dev->user_cap:0x%x\n", dev->user_cap);
	switch (key_cfg.key_algo) {
	case AML_KT_ALGO_AES:
		if (!(dev->algo_cap & KT_CAP_AES))
			goto error_algo;
		break;
	case AML_KT_ALGO_TDES:
		if (!(dev->algo_cap & KT_CAP_TDES))
			goto error_algo;
		break;
	case AML_KT_ALGO_DES:
		if (!(dev->algo_cap & KT_CAP_DES))
			goto error_algo;
		break;
	case AML_KT_ALGO_S17:
		if (!(dev->algo_cap & KT_CAP_S17))
			goto error_algo;
		break;
	case AML_KT_ALGO_SM4:
		if (!(dev->algo_cap & KT_CAP_SM4))
			goto error_algo;
		break;
	case AML_KT_ALGO_NDL:
		if (!(dev->algo_cap & KT_CAP_NDL))
			goto error_algo;
		break;
	case AML_KT_ALGO_ND:
		if (!(dev->algo_cap & KT_CAP_ND))
			goto error_algo;
		break;
	case AML_KT_ALGO_CSA3:
		if (!(dev->algo_cap & KT_CAP_CSA3))
			goto error_algo;
		break;
	case AML_KT_ALGO_CSA2:
		if (!(dev->algo_cap & KT_CAP_CSA2))
			goto error_algo;
		break;
	case AML_KT_ALGO_HMAC:
		if (!(dev->algo_cap & KT_CAP_HMAC))
			goto error_algo;
		break;
	default:
		goto error_algo;
	}

	/* Check Invalid key user id */
	switch (key_cfg.key_userid) {
	case AML_KT_USER_M2M_0:
		if (!(dev->user_cap & KT_CAP_M2M_0))
			goto error_user;
		break;
	case AML_KT_USER_M2M_1:
		if (!(dev->user_cap & KT_CAP_M2M_1))
			goto error_user;
		break;
	case AML_KT_USER_M2M_2:
		if (!(dev->user_cap & KT_CAP_M2M_2))
			goto error_user;
		break;
	case AML_KT_USER_M2M_3:
		if (!(dev->user_cap & KT_CAP_M2M_3))
			goto error_user;
		break;
	case AML_KT_USER_M2M_4:
		if (!(dev->user_cap & KT_CAP_M2M_4))
			goto error_user;
		break;
	case AML_KT_USER_M2M_5:
		if (!(dev->user_cap & KT_CAP_M2M_5))
			goto error_user;
		break;
	case AML_KT_USER_M2M_ANY:
		if (!(dev->user_cap & KT_CAP_M2M_ANY))
			goto error_user;
		break;
	case AML_KT_USER_TSD:
		is_dsc = 1;
		if (!(dev->user_cap & KT_CAP_TSD))
			goto error_user;
		break;
	case AML_KT_USER_TSN:
		is_dsc = 1;
		if (!(dev->user_cap & KT_CAP_TSN))
			goto error_user;
		break;
	case AML_KT_USER_TSE:
		is_dsc = 1;
		if (!(dev->user_cap & KT_CAP_TSE))
			goto error_user;
		break;
	default:
		goto error_user;
	}

	/* Special case for S17 key algorithm.
	 * It is used only for descrambler.
	 */
	if (key_cfg.key_algo == AML_KT_ALGO_S17 && is_dsc) {
		if (key_cfg.ext_value == 0) {
			iowrite32(S17_CFG_DEFAULT,
				(char *)dev->base_addr + dev->reg.s17_cfg_offset);
			KT_LOGD("default frobenius :0x%0x\n", S17_CFG_DEFAULT);
		} else {
			iowrite32(key_cfg.ext_value,
				(char *)dev->base_addr + dev->reg.s17_cfg_offset);
			KT_LOGD("frobenius :0x%0x\n", key_cfg.ext_value);
		}
	}

	if (is_iv)
		memcpy(dev->kt_iv_slot[index], &key_cfg, sizeof(struct amlkt_cfg_param));
	else
		memcpy(dev->kt_slot[index], &key_cfg, sizeof(struct amlkt_cfg_param));
	return ret;

error_algo:
	KT_LOGE("invalid key algorithm:%d\n", key_cfg.key_algo);
	goto exit;

error_user:
	KT_LOGE("invalid key user:%d\n", key_cfg.key_userid);
	goto exit;

exit:
	return KT_ERROR;
}
EXPORT_SYMBOL(aml_kt_config);

static int aml_kt_write_cfg(struct aml_kt_dev *dev,
	u32 handle, u32 key_mode, struct amlkt_cfg_param *key_cfg, u32 func_id)
{
	int ret = KT_SUCCESS;
	u32 kte = 0;
	u32 reg_val = 0;
	u32 reg_offset = 0;
	u8 is_iv = 0;

	ret = aml_kt_handle_to_kte(dev, handle, &kte);
	if (ret != KT_SUCCESS) {
		KT_LOGE("kte get error\n");
		return KT_ERROR;
	}

	is_iv = handle >> KT_IV_FLAG_OFFSET;
	if (key_cfg->key_source == AML_KT_SRC_NSK) {
		// Divide with 3 for getting NSK KTE group ID
		kte = kte / 3;
	}

	reg_offset = dev->reg.cfg_offset;
	reg_val = (KT_PENDING << KTE_PENDING_OFFSET) | (key_mode << KTE_MODE_OFFSET)
			| (key_cfg->key_flag << KTE_FLAG_OFFSET)
			| ((is_iv ? KT_IV_SPEC_ALGO : key_cfg->key_algo) << KTE_KEYALGO_OFFSET)
			| (key_cfg->key_userid << KTE_USERID_OFFSET) | (kte << KTE_KTE_OFFSET)
			| func_id;
	iowrite32(reg_val, (char *)dev->base_addr + reg_offset);

	if (aml_kt_read_pending(dev) != KT_SUCCESS) {
		KT_LOGE("Pending error\n");
		return KT_ERROR;
	}

	return KT_SUCCESS;
}

static void aml_kt_write_key(struct aml_kt_dev *dev, u32 offset, const u8 *key, u32 keylen)
{
	u32 tmp_key = 0;

	memcpy((void *)&tmp_key, key, keylen);
	iowrite32(tmp_key, (char *)dev->base_addr + offset);
}

static int aml_kt_set_inter_key(struct aml_kt_dev *dev, u32 handle, struct amlkt_cfg_param *key_cfg,
				u8 *key, u32 keylen, u32 key_mode)
{
	int ret = KT_SUCCESS;
	u32 func_id = 0;

	if (!key_cfg) {
		KT_LOGE("key_cfg is null\n");
		return KT_ERROR;
	}

	if (!key || keylen == 0) {
		KT_LOGE("key or keylen is null\n");
		return KT_ERROR;
	}

	mutex_lock(&dev->lock);
	if (aml_kt_lock(dev) != KT_SUCCESS) {
		KT_LOGE("lock error\n");
		ret = KT_ERROR;
		goto unlock_mutex;
	}

	KT_LOGD("--------------------------------------------------------------\n");
	KT_LOGD("flag:%d, algo:%d, uid:%d, src:%d\n", key_cfg->key_flag, key_cfg->key_algo,
	     key_cfg->key_userid, key_cfg->key_source);

	if (keylen > 16 && keylen <= 32) {
		if (keylen >= 4)
			aml_kt_write_key(dev, dev->reg.key0_offset, key, 4);

		if (keylen >= 8)
			aml_kt_write_key(dev, dev->reg.key1_offset, key + 4, 4);

		if (keylen >= 12)
			aml_kt_write_key(dev, dev->reg.key2_offset, key + 8, 4);

		if (keylen >= 16)
			aml_kt_write_key(dev, dev->reg.key3_offset, key + 12, 4);

		func_id = 6;
		ret = aml_kt_write_cfg(dev, handle, key_mode, key_cfg, func_id);
		if (ret == KT_ERROR) {
			KT_LOGE("aml_kt_write_cfg error\n");
			goto unlock_kt;
		}

		if (keylen >= 20)
			aml_kt_write_key(dev, dev->reg.key0_offset, key + 16, 4);

		if (keylen >= 24)
			aml_kt_write_key(dev, dev->reg.key1_offset, key + 20, 4);

		if (keylen >= 28)
			aml_kt_write_key(dev, dev->reg.key2_offset, key + 24, 4);

		if (keylen >= 32)
			aml_kt_write_key(dev, dev->reg.key3_offset, key + 28, 4);

		func_id = 7;
		ret = aml_kt_write_cfg(dev, handle, key_mode, key_cfg, func_id);
		if (ret == KT_ERROR) {
			KT_LOGE("aml_kt_write_cfg error\n");
			goto unlock_kt;
		}
	} else if (keylen <= 16) {
		if (keylen >= 4)
			aml_kt_write_key(dev, dev->reg.key0_offset, key, 4);

		if (keylen >= 8)
			aml_kt_write_key(dev, dev->reg.key1_offset, key + 4, 4);

		if (keylen >= 12)
			aml_kt_write_key(dev, dev->reg.key2_offset, key + 8, 4);

		if (keylen >= 16)
			aml_kt_write_key(dev, dev->reg.key3_offset, key + 12, 4);

		ret = aml_kt_write_cfg(dev, handle, key_mode, key_cfg, func_id);
		if (ret == KT_ERROR) {
			KT_LOGE("aml_kt_write_cfg error\n");
			goto unlock_kt;
		}
	} else {
		KT_LOGE("Error: unsupported keylen:%d\n", keylen);
		ret = KT_ERROR;
		goto unlock_kt;
	}

unlock_kt:
	aml_kt_unlock(dev);
unlock_mutex:
	mutex_unlock(&dev->lock);

	return ret;
}

static int aml_kt_set_inter_hw_key(struct aml_kt_dev *dev, u32 handle,
				   struct amlkt_cfg_param *key_cfg, u32 key_mode)
{
	int ret = KT_SUCCESS;
	u32 func_id = 0;

	if (!key_cfg) {
		KT_LOGE("key_cfg is null\n");
		return KT_ERROR;
	}

	mutex_lock(&dev->lock);
	if (aml_kt_lock(dev) != KT_SUCCESS) {
		KT_LOGE("lock error\n");
		ret = KT_ERROR;
		goto unlock_mutex;
	}

	KT_LOGD("--------------------------------------------------------------\n");
	KT_LOGD("flag:%d, algo:%d, uid:%d, src:%d\n", key_cfg->key_flag, key_cfg->key_algo,
	     key_cfg->key_userid, key_cfg->key_source);

	ret = aml_kt_write_cfg(dev, handle, key_mode, key_cfg, func_id);
	if (ret == KT_ERROR) {
		KT_LOGE("aml_kt_write_cfg error\n");
		goto unlock_kt;
	}

unlock_kt:
	aml_kt_unlock(dev);
unlock_mutex:
	mutex_unlock(&dev->lock);

	return ret;
}

int aml_kt_set_host_key(struct aml_kt_dev *dev, struct amlkt_set_key_param *key_param)
{
	int ret = KT_SUCCESS;
	u32 index = 0;
	u32 key_source = 0;
	u8 is_iv = 0;
	u32 key_sts = 0;

	ret = aml_kt_handle_to_kte(dev, key_param->handle, &index);
	if (ret != KT_SUCCESS) {
		KT_LOGE("index get error\n");
		return ret;
	}

	is_iv = key_param->handle >> KT_IV_FLAG_OFFSET;
	if (is_iv) {
		if (!dev->kt_iv_slot[index])
			return KT_ERROR;
		key_source = dev->kt_iv_slot[index]->key_source;
	} else {
		if (!dev->kt_slot[index])
			return KT_ERROR;
		key_source = dev->kt_slot[index]->key_source;
	}

	switch (key_source) {
	case AML_KT_SRC_REE_HOST:
		if (is_iv) {
			ret = aml_kt_set_inter_key(dev, key_param->handle,
							dev->kt_iv_slot[index], key_param->key,
							key_param->key_len, KT_MODE_HOST);
		} else {
			ret = aml_kt_set_inter_key(dev, key_param->handle,
						dev->kt_slot[index], key_param->key,
						key_param->key_len, KT_MODE_HOST);
		}
		break;
	default:
		KT_LOGE("Not support key_source[%d]\n", key_source);
		ret = KT_ERROR;
		return ret;
	}

	if (ret == KT_SUCCESS && kt_log_level <= LOG_DEBUG)
		aml_kt_get_status(dev, key_param->handle, &key_sts);

	return ret;
}
EXPORT_SYMBOL(aml_kt_set_host_key);

int aml_kt_set_hw_key(struct aml_kt_dev *dev, u32 handle)
{
	int ret = KT_SUCCESS;
	u32 index = 0;
	u32 key_source = 0;
	u32 key_sts = 0;
	u8 is_iv = 0;

	ret = aml_kt_handle_to_kte(dev, handle, &index);
	if (ret != KT_SUCCESS) {
		KT_LOGE("index get error\n");
		return KT_ERROR;
	}

	is_iv = handle >> KT_IV_FLAG_OFFSET;
	if (is_iv) {
		if (!dev->kt_iv_slot[index])
			return KT_ERROR;
		key_source = dev->kt_iv_slot[index]->key_source;
	} else {
		if (!dev->kt_slot[index])
			return KT_ERROR;
		key_source = dev->kt_slot[index]->key_source;
	}

	switch (key_source) {
	case AML_KT_SRC_REE_CERT:
	case AML_KT_SRC_REEKL_NAGRA:
		if (is_iv) {
			ret = aml_kt_set_inter_hw_key(dev, handle, dev->kt_iv_slot[index],
								KT_MODE_NAGRA);
		} else {
			ret = aml_kt_set_inter_hw_key(dev, handle, dev->kt_slot[index],
							KT_MODE_NAGRA);
		}
		break;
	case AML_KT_SRC_NSK:
		if (is_iv) {
			ret = aml_kt_set_inter_hw_key(dev, handle, dev->kt_iv_slot[index],
								KT_MODE_NSK);
		} else {
			ret = aml_kt_set_inter_hw_key(dev, handle, dev->kt_slot[index],
								KT_MODE_NSK);
		}
		break;
	default:
		KT_LOGE("Not support key_source[%d]\n", key_source);
		ret = KT_ERROR;
		break;
	}

	if (ret == KT_SUCCESS && kt_log_level <= LOG_DEBUG)
		aml_kt_get_status(dev, handle, &key_sts);

	return ret;
}
EXPORT_SYMBOL(aml_kt_set_hw_key);

static int aml_kt_invalidate(struct aml_kt_dev *dev, u32 handle)
{
	int ret = KT_SUCCESS;
	u32 kte = 0;
	u32 reg_val = 0;
	u32 reg_offset = 0;

	if (unlikely(!dev)) {
		KT_LOGE("Empty aml_kt_dev\n");
		return KT_ERROR;
	}

	ret = aml_kt_handle_to_kte(dev, handle, &kte);
	if (ret != KT_SUCCESS) {
		KT_LOGE("kte get error\n");
		return ret;
	}

	mutex_lock(&dev->lock);
	if (aml_kt_lock(dev) != KT_SUCCESS) {
		ret = KT_ERROR;
		goto unlock_mutex;
	}

	reg_offset = dev->reg.cfg_offset;
	reg_val = (KT_PENDING << KTE_PENDING_OFFSET |
			KT_CLEAN_KTE << KTE_CLEAN_OFFSET |
			KT_MODE_HOST << KTE_MODE_OFFSET |
			kte << KTE_KTE_OFFSET);
	iowrite32(reg_val, (char *)dev->base_addr + reg_offset);

	if (aml_kt_read_pending(dev) != KT_SUCCESS) {
		KT_LOGE("pending error\n");
		ret = KT_ERROR;
		goto unlock_kt;
	} else {
		ret = KT_SUCCESS;
	}

unlock_kt:
	aml_kt_unlock(dev);
unlock_mutex:
	mutex_unlock(&dev->lock);

	return ret;
}

int aml_kt_free(struct aml_kt_dev *dev, u32 handle)
{
	int ret = KT_SUCCESS;
	u32 kte = 0;
	u8 is_iv = 0;

	if (unlikely(!dev)) {
		KT_LOGE("Empty aml_kt_dev\n");
		return KT_ERROR;
	}

	ret = aml_kt_handle_to_kte(dev, handle, &kte);
	if (ret != KT_SUCCESS) {
		KT_LOGE("kte get error\n");
		return ret;
	}

	is_iv = handle >> KT_IV_FLAG_OFFSET;
	ret = aml_kt_invalidate(dev, handle);

	mutex_lock(&dev->lock);
	if (is_iv) {
		kfree(dev->kt_iv_slot[kte]);
		dev->kt_iv_slot[kte] = NULL;
	} else {
		kfree(dev->kt_slot[kte]);
		dev->kt_slot[kte] = NULL;
	}
	mutex_unlock(&dev->lock);

	if (ret != KT_SUCCESS) {
		KT_LOGE("Error: KT invalidate clean kte error\n");
		ret = KT_ERROR;
	} else {
		ret = KT_SUCCESS;
	}

	return ret;
}
EXPORT_SYMBOL(aml_kt_free);

int aml_kt_open(struct inode *inode, struct file *filp)
{
	struct aml_kt_dev *dev;

	dev = container_of(inode->i_cdev, struct aml_kt_dev, cdev);
	filp->private_data = dev;

	return 0;
}

int aml_kt_release(struct inode *inode, struct file *filp)
{
	if (!filp->private_data)
		return 0;

	filp->private_data = NULL;

	return 0;
}

static long aml_kt_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct aml_kt_dev *dev = filp->private_data;
	struct amlkt_alloc_param alloc_param;
	struct amlkt_cfg_param cfg_param;
	struct amlkt_set_key_param key_param;
	u32 handle = 0;
	int ret = 0;

	if (unlikely(!dev)) {
		KT_LOGE("Empty aml_kt_dev\n");
		return -EINVAL;
	}

	if (!dev->base_addr) {
		KT_LOGE("ERROR: iobase is zero\n");
		return -EFAULT;
	}

	switch (cmd) {
	case AML_KT_ALLOC:
		memset(&alloc_param, 0, sizeof(alloc_param));
		if (copy_from_user(&alloc_param, (void __user *)arg, sizeof(alloc_param))) {
			KT_LOGE("aml_kt_alloc copy_from_user error\n");
			return -EFAULT;
		}

		ret = aml_kt_alloc(dev, alloc_param.flag, &alloc_param.handle);
		if (ret != 0) {
			KT_LOGE("aml_kt_alloc failed retval=0x%08x\n", ret);
			return -EFAULT;
		}

		ret = copy_to_user((void __user *)arg, &alloc_param, sizeof(alloc_param));
		if (unlikely(ret)) {
			KT_LOGE("aml_kt_alloc copy_to_user error\n");
			return -EFAULT;
		}
		break;
	case AML_KT_CONFIG:
		memset(&cfg_param, 0, sizeof(cfg_param));
		if (copy_from_user(&cfg_param, (void __user *)arg, sizeof(cfg_param))) {
			KT_LOGE("aml_kt_config copy_from_user error\n");
			return -EFAULT;
		}

		ret = aml_kt_config(dev, cfg_param);
		if (ret != 0) {
			KT_LOGE("aml_kt_config failed retval=0x%08x\n", ret);
			return -EFAULT;
		}
		break;
	case AML_KT_SET:
		memset(&key_param, 0, sizeof(key_param));
		if (copy_from_user(&key_param, (void __user *)arg, sizeof(key_param))) {
			KT_LOGE("aml_kt_set_host_key copy_from_user error\n");
			return -EFAULT;
		}

		ret = aml_kt_set_host_key(dev, &key_param);
		if (ret != 0) {
			KT_LOGE("aml_kt_set failed retval=0x%08x\n", ret);
			return -EFAULT;
		}
		break;
	case AML_KT_HW_SET:
		ret = get_user(handle, (u32 __user *)arg);
		if (unlikely(ret)) {
			KT_LOGE("aml_kt_set_hw_key get_user error\n");
			return ret;
		}

		ret = aml_kt_set_hw_key(dev, handle);
		if (ret != 0) {
			KT_LOGE("aml_kt_hw_set failed retval=0x%08x\n", ret);
			return -EFAULT;
		}
		break;
	case AML_KT_FREE:
		ret = get_user(handle, (u32 __user *)arg);
		if (unlikely(ret)) {
			KT_LOGE("aml_kt_free get_user error\n");
			return ret;
		}

		ret = aml_kt_free(dev, handle);
		if (ret != 0) {
			KT_LOGE("aml_kt_free failed retval=0x%08x\n", ret);
			return -EFAULT;
		}
		break;
	case AML_KT_INVALIDATE:
		ret = get_user(handle, (u32 __user *)arg);
		if (unlikely(ret)) {
			KT_LOGE("aml_kt_invalidate get_user error\n");
			return ret;
		}

		ret = aml_kt_invalidate(dev, handle);
		if (ret != 0) {
			KT_LOGE("aml_kt_invalidate failed retval=0x%08x\n", ret);
			return -EFAULT;
		}
		break;
	default:
		KT_LOGE("Unknown cmd: %d\n", cmd);
		return -EFAULT;
	}

	return 0;
}

static const struct file_operations aml_kt_fops = {
	.owner = THIS_MODULE,
	.open = aml_kt_open,
	.release = aml_kt_release,
	.unlocked_ioctl = aml_kt_ioctl,
	.compat_ioctl = aml_kt_ioctl,
};

static int aml_kt_get_dts_info(struct aml_kt_dev *dev, struct platform_device *pdev)
{
	int ret = 0;
	u32 offset[7];
	u32 cap[6];

	if (unlikely(!dev)) {
		KT_LOGE("Empty aml_kt_dev\n");
		return KT_ERROR;
	}

	/* kt register offset */
	if (of_property_read_u32_array(pdev->dev.of_node, "kt_offset", offset,
			ARRAY_SIZE(offset)) == 0) {
		dev->reg.rdy_offset = offset[0];
		dev->reg.cfg_offset = offset[1];
		dev->reg.sts_offset = offset[2];
		dev->reg.key0_offset = offset[3];
		dev->reg.key1_offset = offset[4];
		dev->reg.key2_offset = offset[5];
		dev->reg.key3_offset = offset[6];
	} else {
		KT_LOGE("%s: not found\n", "kt_offset");
		return KT_ERROR;
	}

	/* kt capability */
	if (of_property_read_u32_array(pdev->dev.of_node, "kt_cap", cap,
			ARRAY_SIZE(cap)) == 0) {
		dev->algo_cap = cap[0];
		dev->user_cap = cap[1];
		dev->kte_start = cap[2];
		dev->kte_end = cap[3];
		dev->ive_start = cap[4];
		dev->ive_end = cap[5];
	} else {
		KT_LOGE("%s: not found\n", "kt_cap");
		return KT_ERROR;
	}

	/* S17 algo cfg */
	if (dev->algo_cap & KT_CAP_S17) {
		ret = of_property_read_u32(pdev->dev.of_node, "s17_cfg_offset",
			&dev->reg.s17_cfg_offset);
		if (ret) {
			KT_LOGE("%s: not found 0x%x\n", "s17_cfg_offset", dev->reg.s17_cfg_offset);
			return KT_ERROR;
		}
	}

	/* Check reserved KTE */
	ret = of_property_read_u32(pdev->dev.of_node, "kt_reserved", &dev->kt_reserved);
	if (ret) {
		KT_LOGE("%s: not found 0x%x\n", "kt_reserved", dev->kt_reserved);
		return KT_ERROR;
	}

	return KT_SUCCESS;
}

int aml_kt_init(struct class *aml_kt_class, struct platform_device *pdev)
{
	int ret = -1;
	struct device *device;
	struct resource *res;

	if (alloc_chrdev_region(&aml_kt_devt, 0, DEVICE_INSTANCES,
				AML_KT_DEVICE_NAME) < 0) {
		KT_LOGE("%s device can't be allocated.\n", AML_KT_DEVICE_NAME);
		return -ENXIO;
	}

	cdev_init(&aml_kt_dev.cdev, &aml_kt_fops);
	aml_kt_dev.cdev.owner = THIS_MODULE;
	ret = cdev_add(&aml_kt_dev.cdev,
		       MKDEV(MAJOR(aml_kt_devt), MINOR(aml_kt_devt)), 1);
	aml_kt_dev.base_addr = NULL;
	if (unlikely(ret < 0))
		goto unregister_chrdev;

	device = device_create(aml_kt_class, NULL, aml_kt_devt, NULL,
			       AML_KT_DEVICE_NAME);
	if (IS_ERR(device)) {
		KT_LOGE("device_create failed\n");
		ret = PTR_ERR(device);
		goto delete_cdev;
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		KT_LOGE("%s: platform_get_resource is failed\n", __func__);
		ret = -ENOMEM;
		goto destroy_device;
	}

	aml_kt_dev.base_addr = devm_ioremap_resource(&pdev->dev, res);
	if (!aml_kt_dev.base_addr) {
		KT_LOGE("%s base addr error\n", __func__);
		ret = -ENOMEM;
		goto destroy_device;
	}

	ret = aml_kt_get_dts_info(&aml_kt_dev, pdev);
	if (ret != KT_SUCCESS) {
		KT_LOGE("%s: cannot find match dts info\n", __func__);
		ret = -EINVAL;
		goto destroy_device;
	}

	mutex_init(&aml_kt_dev.lock);
	memset(aml_kt_dev.kt_slot, 0, sizeof(aml_kt_dev.kt_slot));
	memset(aml_kt_dev.kt_iv_slot, 0, sizeof(aml_kt_dev.kt_iv_slot));
	return ret;

destroy_device:
	device_destroy(aml_kt_class, aml_kt_devt);
delete_cdev:
	cdev_del(&aml_kt_dev.cdev);
unregister_chrdev:
	unregister_chrdev_region(aml_kt_devt, DEVICE_INSTANCES);

	return ret;
}

void aml_kt_exit(struct class *aml_kt_class, struct platform_device *pdev)
{
	int i = 0;

	for (i = aml_kt_dev.kte_start; i < aml_kt_dev.kte_end; i++) {
		if (aml_kt_dev.kt_reserved && aml_kt_slot_reserved(i))
			continue;

		kfree(aml_kt_dev.kt_slot[i]);
		aml_kt_dev.kt_slot[i] = NULL;
	}

	for (i = aml_kt_dev.ive_start; i < aml_kt_dev.ive_end; i++) {
		kfree(aml_kt_dev.kt_iv_slot[i]);
		aml_kt_dev.kt_iv_slot[i] = NULL;
	}

	device_destroy(aml_kt_class, aml_kt_devt);
	cdev_del(&aml_kt_dev.cdev);
	unregister_chrdev_region(MKDEV(MAJOR(aml_kt_devt),
				       MINOR(aml_kt_devt)),
				 DEVICE_INSTANCES);
	mutex_destroy(&aml_kt_dev.lock);
}

static int aml_kt_probe(struct platform_device *pdev)
{
	int ret = 0;

	aml_kt_class = class_create(THIS_MODULE, AML_KT_DEVICE_NAME);
	if (IS_ERR(aml_kt_class)) {
		KT_LOGE("class_create failed\n");
		ret = PTR_ERR(aml_kt_class);
		return ret;
	}

	ret = aml_kt_init(aml_kt_class, pdev);
	if (unlikely(ret < 0)) {
		KT_LOGE("aml_kt_init failed\n");
		goto destroy_class;
	}

	ret = aml_kt_init_dbgfs();
	if (ret) {
		KT_LOGE("aml_kt_init_dbgfs failed\n");
		goto destroy_class;
	}

	return 0;

destroy_class:
	class_destroy(aml_kt_class);
	return ret;
}

static int aml_kt_remove(struct platform_device *pdev)
{
	aml_kt_exit(aml_kt_class, pdev);
	class_destroy(aml_kt_class);
	debugfs_remove_recursive(aml_kt_debug_dent);
	return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id aml_kt_dt_match[] = {
	{
		.compatible = "amlogic,aml_kt"
	},
	{},
};
MODULE_DEVICE_TABLE(of, aml_kt_dt_match);
#else
#define aml_kt_dt_match NULL
#endif

static struct platform_driver aml_kt_drv = {
	.probe = aml_kt_probe,
	.remove = aml_kt_remove,
	.driver = {
		.name = AML_KT_DEVICE_NAME,
		.of_match_table = aml_kt_dt_match,
		.owner = THIS_MODULE,
	},
};

int __init aml_seckey_kt_init(void)
{
	int err = 0;

	err = platform_driver_register(&aml_kt_drv);
	if (err) {
		KT_LOGE("%s: failed to register driver, err %d.\n",
				__func__, err);
		return err;
	}

	return 0;
}

void __exit aml_seckey_kt_exit(void)
{
	platform_driver_unregister(&aml_kt_drv);
}
