// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2021 MediaTek Inc.
 */

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of_device.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/version.h>

#include <linux/errno.h>
#include <linux/list.h>
#include <linux/nospec.h>
#include <linux/spinlock.h>
#include <linux/sched/signal.h>
#include <linux/kthread.h>
#include <media/dvb_ca_en50221.h>
#include <media/dvb_ringbuffer.h>

#include "mdrv_adapter.h"
#include "mtk_scrambler.h"

#include <media/demux.h>
#include <uapi/linux/dvb/dsc_mtk.h>
#include "mtk_dmx_internal_if.h"
#include "../dscmb/mtk_dscmb.h"
#include "mtk_dmx_tee_if.h"

/*--------------------------------------------------*/
/*-----Local Type Definitions---------*/
/*--------------------------------------------------*/

#define DVB_CA_SLOTSTATE_NONE		   0
#define DVB_CA_SLOTSTATE_LINKINIT	   7

#define MTK_SCRAMB_SLOTNUM			3
#define MTK_SCRAMB_RECORD_SLOTNUM		 2
#define MTK_SCRAMB_PID_KEEP             16
#define MTK_SCRAMB_PIDFLT_NUM           256
#define MTK_SCRAMB_IPV6_Len             16

struct scrambler_pid_info {
	bool delay;
	u16 pid;
	struct dmx2_ip_addr ip_addr;
};

/* Information on a CA slot */
struct dvb_ca_slot {
	/* current state of the CAM */
	int slot_state;

	/* mutex used for serializing access to one CI slot */
	struct mutex slot_lock;

	/* Number of CAMCHANGES that have occurred since last processing */
	atomic_t camchange_count;

	/* Type of last CAMCHANGE */
	int camchange_type;

	/* base address of CAM config */
	u32 config_base;

	/* value to write into Config Control register */
	u8 config_option;

	/* if 1, the CAM supports DA IRQs */
	u8 da_irq_supported:1;

	/* size of the buffer to use when talking to the CAM */
	int link_buf_size;

	/* buffer for incoming packets */
	struct dvb_ringbuffer rx_buffer;

	/* timer used during various states of the slot */
	unsigned long timeout;

	/* dmx path id */
	unsigned int source;

	unsigned long long keytoken;

	unsigned int pipeline_id;

	unsigned int purpose;

	/* file mapping 2 slot */
	struct file *file;

	/* ca ctrl */
	u8  delay_pid_num;
	u8  add_pid_num;
	struct scrambler_pid_info pid_info[MTK_SCRAMB_PID_KEEP];
};
static struct dvb_ca_slot *gslot_info = NULL;
/*-------------------------------------------------*/
/*-----Macro Definitions----*/
/*--------------------------------------------------*/

/*--------------------------------------------------*/
/*-----LOCAL VARIABLEs----*/
/*--------------------------------------------------*/

/*---------------------------------------------------*/
/*---Static Variables & Function Prototypes Declarationss----*/
/*---------------------------------------------------*/


/* Private CA-interface information */
struct dvb_ca_private {
	struct kref refcount;

	/* pointer back to the public data structure */
	struct dvb_ca_en50221 *pub;

	/* the DVB device */
	struct dvb_device *dvbdev;

	/* Flags describing the interface (DVB_CA_FLAG_*) */
	u32 flags;

	/* number of slots supported by this CA interface */
	unsigned int slot_count;

	/* set CI or SMC ctrl flow */
	unsigned int flow_ctrl;

	/* information on each slot */
	struct dvb_ca_slot *slot_info;

	/* wait queues for read() and write() operations */
	wait_queue_head_t wait_queue;

	/* PID of the monitoring thread */
	struct task_struct *thread;

	/* Flag indicating if the CA device is open */
	unsigned int open:1;

	/* Flag indicating the thread should wake up now */
	unsigned int wakeup:1;

	/* Delay the main thread should use */
	unsigned long delay;

	/*
	 * Slot to start looking for data to read from in the next user-space
	 * read operation
	 */
	int next_read_slot;

	unsigned int engine_id;
	unsigned int keyslot;

	//dmx path id
	unsigned int ca_source;

	unsigned long long keytoken;
	/* mutex serializing ioctls */
	struct mutex ioctl_mutex;
};
static struct dvb_ca_private *gca = NULL;

static struct mtk_dmx2dev *g_dmx_dev;

/*----------------------------------------------------------------*/
/*-    Function Definitions----*/
/*----------------------------------------------------------------*/

static int _dvb_usercopy(struct file *file,
			 unsigned int cmd, unsigned long arg,
			 int (*func)(struct file *file, unsigned int cmd, void *arg))
{
#define STRING_BUFFER_SIZE (128)
	char sbuf[STRING_BUFFER_SIZE];
	void *mbuf = NULL;
	void *parg = NULL;
	int err = -EINVAL;

	/*  Copy arguments into temp kernel buffer  */
	switch (_IOC_DIR(cmd)) {
	case _IOC_NONE:
		/*
		 * For this command, the pointer is actually an integer
		 * argument.
		 */
		parg = (void *)arg;
		break;
	case _IOC_READ:	/* some v4l ioctls are marked wrong ... */
	case _IOC_WRITE:
	case (_IOC_WRITE | _IOC_READ):
		if (_IOC_SIZE(cmd) <= sizeof(sbuf)) {
			parg = sbuf;
		} else {
			/* too big to allocate from stack */
			mbuf = kmalloc(_IOC_SIZE(cmd), GFP_KERNEL);
			if (mbuf == NULL)
				return -ENOMEM;

			parg = mbuf;
		}

		err = -EFAULT;
		if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd)))
			goto out;
		break;
	}

	/* call driver */
	err = func(file, cmd, parg);
	if (err == -ENOIOCTLCMD)
		err = -ENOTTY;

	if (err < 0)
		goto out;

	/*  Copy results into user buffer  */
	switch (_IOC_DIR(cmd)) {
	case _IOC_READ:
	case (_IOC_WRITE | _IOC_READ):
		if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd)))
			err = -EFAULT;
		break;
	}

out:
	kfree(mbuf);
	return err;
#undef STRING_BUFFER_SIZE
}


static void dvb_ca_private_free(struct dvb_ca_private *ca)
{
#if (KERNEL_VERSION(5, 4, 268) <= LINUX_VERSION_CODE) && \
	(LINUX_VERSION_CODE < KERNEL_VERSION(5, 5, 0)) && \
	!defined(CONFIG_MTK_DVB_CORE)
#else
	dvb_free_device(ca->dvbdev);
#endif

	kfree(ca->slot_info);
	kfree(ca);
	gslot_info = NULL;
	gca = NULL;
}


static void dvb_ca_private_release(struct kref *ref)
{
	struct dvb_ca_private *ca;

	ca = container_of(ref, struct dvb_ca_private, refcount);
	/* mark the CA device as closed */
	ca->open = 0;
	dvb_ca_private_free(ca);
}


static void dvb_ca_private_get(struct dvb_ca_private *ca)
{
	kref_get(&ca->refcount);
}

static void dvb_ca_private_put(struct dvb_ca_private *ca)
{
	kref_put(&ca->refcount, dvb_ca_private_release);
}

/////////////////////////////////////////////////////
// tee_if.c
/////////////////////////////////////////////////////
#include <linux/uuid.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
#include <linux/tee_drv.h>
#else
#include "tee_drv_5_15_13.h"
#endif
#else
#include <linux/tee_drv.h>
#endif
#define TEE_COMMON_ARG_LENGTH	16
#define TZ_CACHE_LINE_SIZE      64

#define TZ_CACHE_LINE_SIZE_MSK  (TZ_CACHE_LINE_SIZE - 1)
#define TZ_CACHE_ALIGN(x)       (((x) + TZ_CACHE_LINE_SIZE_MSK) & ~TZ_CACHE_LINE_SIZE_MSK)
#define SCRM_CLSANDND (8)

typedef enum {
	REC_TA_CMD_GET_PIPELINE_ID,
	REC_TA_CMD_CONFIGURE,
	REC_TA_CMD_OPERATE,
	REC_TA_CMD_RELEASE
} rec_ta_cmd_type_t;

typedef union fixed_size_pointer {
	void *ptr;
	__u32 val;
	__u64 sizer;
} fixed_size_pointer;

typedef struct {
	__u32 ui4Command;
	fixed_size_pointer apvArgument[TEE_COMMON_ARG_LENGTH];
	__u32 ui4Length[TEE_COMMON_ARG_LENGTH];
	bool abWrite[TEE_COMMON_ARG_LENGTH];
	__u8 ui1Size;
} TEE_COMMON_ARG_T;

/**
 * This type contains a Universally Unique Resource Identifier (UUID) type as
 * defined in RFC4122. These UUID values are used to identify Trusted
 * Applications.
 */
struct TEEC_UUID {
	uint32_t timeLow;
	uint16_t timeMid;
	uint16_t timeHiAndVersion;
	uint8_t clockSeqAndNode[SCRM_CLSANDND];
};

/**
 * struct TEEC_Session - Represents a connection between a client application
 * and a trusted application.
 */
struct TEEC_Session {
#ifdef CONFIG_ARM64
	uintptr_t fd;
#else
	int fd;
#endif
};

struct mtk_scrambler_tee_context {
	struct tee_context *ctx;
	struct TEEC_Session rec_ta_session;
	dmx_crypto_type cryption_type;
	bool bInit;
} scrambler_tee_ctx;

static void uuid_to_octets(uint8_t d[TEE_IOCTL_UUID_LEN], const struct TEEC_UUID *s)
{
	d[0] = s->timeLow >> 24;
	d[1] = s->timeLow >> 16;
	d[2] = s->timeLow >> 8;
	d[3] = s->timeLow;
	d[4] = s->timeMid >> 8;
	d[5] = s->timeMid;
	d[6] = s->timeHiAndVersion >> 8;
	d[7] = s->timeHiAndVersion;
	memcpy(d + 8, s->clockSeqAndNode, sizeof(s->clockSeqAndNode));
}

static int _scrambler_optee_match(struct tee_ioctl_version_data *data, const void *vers)
{
	return 1;
}

int _scrambler_tee_open_ctx(void)
{
	struct tee_ioctl_version_data vers = {
		.impl_id = TEE_OPTEE_CAP_TZ,
		.impl_caps = TEE_IMPL_ID_OPTEE,
		.gen_caps = TEE_GEN_CAP_GP,
	};

	if (scrambler_tee_ctx.bInit) {
		SCRAMBLER_ERR("optee context already opened\n");
		return 0;
	}

	scrambler_tee_ctx.ctx = tee_client_open_context(NULL, _scrambler_optee_match, NULL, &vers);

	if (IS_ERR(scrambler_tee_ctx.ctx)) {
		SCRAMBLER_ERR("failed to open context\n");
		return -EFAULT;
	}

	scrambler_tee_ctx.bInit = true;
	return 0;
}

static int __attribute__((unused)) _scrambler_tee_close_ctx(void)
{
	if (!scrambler_tee_ctx.bInit) {
		SCRAMBLER_ERR("optee context already closed\n");
		return 0;
	}

	tee_client_close_context(scrambler_tee_ctx.ctx);
	scrambler_tee_ctx.bInit = false;

	return 0;
}

#define TEEC_CONFIG_PAYLOAD_REF_COUNT	4

#define TEEC_NONE                   0x00000000
#define TEEC_VALUE_INPUT            0x00000001
#define TEEC_VALUE_OUTPUT           0x00000002
#define TEEC_VALUE_INOUT            0x00000003

/**
 * Get the i_th param type from the paramType.
 *
 * @param p The paramType.
 * @param i The i-th parameter to get the type for.
 */
#define TEEC_PARAM_TYPE_GET(p, i) (((p) >> (i * 4)) & 0xF)

struct TempMemoryReference {
	void *buffer;
	size_t size;
};

struct TempValue {
	uint32_t a;
	uint32_t b;
};

struct parameter {
	struct TempValue value;
	struct TempMemoryReference tmpref;
};

struct TEEC_Operation {
	uint32_t paramTypes;
	struct parameter params[TEEC_CONFIG_PAYLOAD_REF_COUNT];
};

static int _scrambler_tee_open_session(__u32 *session_id)
{
	struct tee_ioctl_open_session_arg arg;
	struct tee_param param = { 0 };
	struct TEEC_Operation op = { 0 };
	int rc;
	struct TEEC_UUID uuid = REC_TA_UUID;

	memset(&arg, 0, sizeof(struct tee_ioctl_open_session_arg));
	if (!session_id) {
		SCRAMBLER_ERR("invalid parameter\n");
		return -EINVAL;
	}

	uuid_to_octets(arg.uuid, &uuid);
	arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC;

	op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_OUTPUT, TEEC_NONE, TEEC_NONE, TEEC_NONE);
	param.attr = op.paramTypes;

	SCRAMBLER_DEBUG("UUID = 0x%x\n", *(arg.uuid));
	rc = tee_client_open_session(scrambler_tee_ctx.ctx, &arg, (struct tee_param *)&param);

	if (rc == 0) {
		SCRAMBLER_DEBUG("arg.ret = 0x%x\n", arg.ret);
		SCRAMBLER_DEBUG("arg.session = 0x%x\n", arg.session);
		*session_id = arg.session;
		SCRAMBLER_DEBUG("scrambler_tee_ctx.rec_ta_session.fd=0x%llx\n",
				(u64)(scrambler_tee_ctx.rec_ta_session.fd));
	} else {
		SCRAMBLER_ERR("tee_client_open_session() fail,rc=0x%x\n", rc);
		return -EINVAL;
	}

	return 0;
}

static int _scrambler_tee_close_session(__u32 session_id)
{
	return tee_client_close_session(scrambler_tee_ctx.ctx, session_id);
}

static inline __u32 _scrambler_tee_get_rec_ta_session_id(void)
{
	__u32 session_id;

	/*down_read(&tee_rwsem); */
	session_id = scrambler_tee_ctx.rec_ta_session.fd;
	/*up_read(&tee_rwsem); */
	return session_id;
}

struct TEEC_SharedMemory_2 {
	void *buffer;
	size_t size;
	uint32_t flags;
	/*
	 * Implementation-Defined
	 */
	int id;
	size_t alloced_size;
	void *shadow_buffer;
};

static int teec_allocatesharedmemory(struct tee_context *ctx, struct TEEC_SharedMemory_2 *shm)
{
#define SCRM_SHARE_MEM (8)
	struct tee_shm *_shm = NULL;
	void *shm_va = NULL;
	size_t len;
	struct tee_context *context = ctx;

	shm->shadow_buffer = shm->buffer = NULL;
	len = shm->size;
	if (!len)
		len = SCRM_SHARE_MEM;

#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
	_shm = tee_shm_alloc(context, len, TEE_SHM_MAPPED | TEE_SHM_DMA_BUF);
#else
	_shm = tee_shm_alloc_kernel_buf(context, len);
#endif
	if (IS_ERR(_shm)) {
		SCRAMBLER_ERR("tee_shm_alloc fail\n");
		return -ENOMEM;
	}

	shm_va = tee_shm_get_va(_shm, 0);
	if (IS_ERR(shm_va)) {
		SCRAMBLER_ERR("tee_shm_get_va fail\n");
		return -ENOMEM;
	}
	shm->buffer = shm_va;
	shm->shadow_buffer = (void *)_shm;
	shm->alloced_size = len;

	return 0;
#undef SCRM_SHARE_MEM
}

static int teec_pre_process_tmpref(struct tee_context *ctx,
				   uint32_t param_type, struct TempMemoryReference *tmpref,
				   struct tee_param *param, struct TEEC_SharedMemory_2 *shm)
{
	int res;

	param->attr = param_type;
	shm->size = tmpref->size;

	res = teec_allocatesharedmemory(ctx, shm);
	if (res != 0)
		return res;

	memcpy(shm->buffer, tmpref->buffer, tmpref->size);
	param->u.memref.size = tmpref->size;
	//Workaround, shm->shadow_buffer is shm object.
	param->u.memref.shm = (struct tee_shm *)shm->shadow_buffer;
	return 0;
}

static int teec_pre_process_operation(struct tee_context *ctx,
				      struct TEEC_Operation *operation,
				      struct tee_param *params, struct TEEC_SharedMemory_2 *shms)
{
	int res;
	size_t n;
	uint32_t param_type;

	memset(shms, 0, sizeof(*shms) * TEEC_CONFIG_PAYLOAD_REF_COUNT);
	if (!operation) {
		memset(params, 0, sizeof(*params) * TEEC_CONFIG_PAYLOAD_REF_COUNT);
		return 0;
	}

	for (n = 0; n < TEEC_CONFIG_PAYLOAD_REF_COUNT; n++) {
		param_type = TEEC_PARAM_TYPE_GET(operation->paramTypes, n);
		switch (param_type) {
		case TEE_IOCTL_PARAM_ATTR_TYPE_NONE:
			params[n].attr = param_type;
			break;
		case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT:
		case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT:
		case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT:
			params[n].attr = param_type;
			params[n].u.value.a = operation->params[n].value.a;
			params[n].u.value.b = operation->params[n].value.b;
			break;
		case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT:
		case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT:
		case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT:
			res = teec_pre_process_tmpref(ctx, param_type,
						      &operation->params[n].tmpref,
						      params + n, shms + n);
			if (res != 0)
				return res;
			break;
		default:
			SCRAMBLER_ERR("param_type not support!\n");
			return -EINVAL;
		}
	}

	return 0;
}

static void teec_post_process_tmpref(uint32_t param_type,
				     struct TempMemoryReference *tmpref,
				     struct tee_param *param, struct TEEC_SharedMemory_2 *shm)
{
	if (param_type != TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT) {
		if (param->u.memref.size <= tmpref->size && tmpref->buffer)
			memcpy(tmpref->buffer, shm->buffer, param->u.memref.size);

		tmpref->size = param->u.memref.size;
	}
}

static void teec_post_process_operation(struct TEEC_Operation *operation,
					struct tee_param *params, struct TEEC_SharedMemory_2 *shms)
{
	size_t n;

	if (!operation)
		return;

	for (n = 0; n < TEEC_CONFIG_PAYLOAD_REF_COUNT; n++) {
		uint32_t param_type;

		param_type = TEEC_PARAM_TYPE_GET(operation->paramTypes, n);
		switch (param_type) {
		case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT:
			break;
		case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT:
		case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT:
			operation->params[n].value.a = params[n].u.value.a;
			operation->params[n].value.b = params[n].u.value.b;
			break;
		case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT:
		case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT:
		case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT:
			teec_post_process_tmpref(param_type,
						 &operation->params[n].tmpref,
						 params + n, shms + n);
			break;
		default:
			break;
		}
	}
}

static void teec_releasesharedmemory(struct TEEC_SharedMemory_2 *shm)
{
	if (shm->shadow_buffer)
		tee_shm_free((struct tee_shm *)shm->shadow_buffer);
	shm->shadow_buffer = NULL;
	shm->buffer = NULL;
}

static void teec_free_temp_refs(struct TEEC_Operation *operation, struct TEEC_SharedMemory_2 *shms)
{
	size_t n;

	if (!operation)
		return;

	for (n = 0; n < TEEC_CONFIG_PAYLOAD_REF_COUNT; n++) {
		switch (TEEC_PARAM_TYPE_GET(operation->paramTypes, n)) {
		case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT:
		case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT:
		case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT:
			teec_releasesharedmemory(shms + n);
			break;
		default:
			break;
		}
	}
}

static int _scrambler_tee_invoke_cmd(__u32 session_id, __u32 cmd, void *param, size_t size)
{
#define ERRORIG_PRCSS (1)
#define ERRORIG_INVOKE (2)
	struct TEEC_Operation op = { 0 };
	struct tee_ioctl_invoke_arg arg = { 0 };
	struct tee_param params[TEEC_CONFIG_PAYLOAD_REF_COUNT];
	struct TEEC_SharedMemory_2 shm[TEEC_CONFIG_PAYLOAD_REF_COUNT];
	int res, rc;

	memset(params, 0, sizeof(struct tee_param) * TEEC_CONFIG_PAYLOAD_REF_COUNT);
	memset(shm, 0, sizeof(struct TEEC_SharedMemory_2) * TEEC_CONFIG_PAYLOAD_REF_COUNT);
	if (size) {
		op.params[0].tmpref.buffer = param;
		op.params[0].tmpref.size = size;
		op.paramTypes = TEEC_PARAM_TYPES(TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT,
						 TEE_IOCTL_PARAM_ATTR_TYPE_NONE,
						 TEE_IOCTL_PARAM_ATTR_TYPE_NONE,
						 TEE_IOCTL_PARAM_ATTR_TYPE_NONE);
	} else {
		op.paramTypes = TEEC_PARAM_TYPES(TEE_IOCTL_PARAM_ATTR_TYPE_NONE,
						 TEE_IOCTL_PARAM_ATTR_TYPE_NONE,
						 TEE_IOCTL_PARAM_ATTR_TYPE_NONE,
						 TEE_IOCTL_PARAM_ATTR_TYPE_NONE);
	}

	arg.num_params = TEEC_CONFIG_PAYLOAD_REF_COUNT;
	arg.session = session_id;
	arg.func = cmd;

	res = teec_pre_process_operation(scrambler_tee_ctx.ctx, &op, params, shm);
	if (res != 0)
		goto tee_out_free_temp_refs;

	rc = tee_client_invoke_func(scrambler_tee_ctx.ctx, &arg, params);

	if (rc) {
		res = rc;
		goto tee_out_free_temp_refs;
	}

	res = arg.ret;
	teec_post_process_operation(&op, params, shm);

tee_out_free_temp_refs:
	teec_free_temp_refs(&op, shm);
	//tee_client_close_session(scrambler_tee_ctx.ctx, session_id);

#undef ERRORIG_PRCSS
#undef ERRORIG_INVOKE
	return res;
}

/////////////////////////////////////////////////////
// for allocate keytoken from xtest ta
/////////////////////////////////////////////////////
#define MTK_XTEST_TA_UUID {0xc49b5c01, 0xf966, 0x4c5e, \
	 { 0xb1, 0x72, 0x39, 0x72, 0x3e, 0xac, 0x38, 0x8c}}
#define TA_XTEST_KEYTOKEN_CMD_FREE_TOKEN  (932)
#define TA_XTEST_KEYTOKEN_CMD_ALLOC_TOKEN (929)
#define TA_XTEST_KEYTOKEN_CMD_FREE_TOKEN_3_22  (0x20003)
#define TA_XTEST_KEYTOKEN_CMD_ALLOC_TOKEN_3_22 (0x20000)
#define TEE_KEY_TOKEN_USAGE_USAGE_RECORD		  0x02
#define TEE_KEY_TOKEN_TYPE_AES					0x00
#define EN_KEY_TOKEN_SECURITY_LEVEL_SVP			 0x1
#define TEE_OP_PARA_INDEX3				  (3)
#define SHIFTBIT_LOW4BYTES2HIGH			 (32)
#define SHIFTBIT_HIGH4BYTES2LOW			 (32)

static struct tee_context *ctx = NULL;
static struct TEEC_Session xtest_ta_session;
static bool xtest_bInit = false;
static int ta_keytoken_cmd_allocate_token(struct tee_context *ctx, TEEC_Session *sess, uint8_t usage, uint8_t type, int32_t securityLevel, uint64_t *u64Token)
{
	int res;
	struct TEEC_Operation op = { 0 };
	struct tee_ioctl_invoke_arg arg = { 0 };
	struct tee_param params[TEEC_CONFIG_PAYLOAD_REF_COUNT];
	struct TEEC_SharedMemory_2 shm[TEEC_CONFIG_PAYLOAD_REF_COUNT];

	memset(params, 0, sizeof(struct tee_param) * TEEC_CONFIG_PAYLOAD_REF_COUNT);
	memset(shm, 0, sizeof(struct TEEC_SharedMemory_2) * TEEC_CONFIG_PAYLOAD_REF_COUNT);
	op.params[0].value.a = usage; //TEE_KEY_TOKEN_USAGE_USAGE_PLAYBACK
	op.params[0].value.b = securityLevel; // EN_KEY_TOKEN_SECURITY_LEVEL_SVP
	op.params[TEE_OP_PARA_INDEX3].value.a = type; // TEE_KEY_TOKEN_TYPE_AES

	op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_INPUT, TEEC_VALUE_OUTPUT, TEEC_VALUE_OUTPUT, TEEC_VALUE_INPUT);

	{
		arg.num_params = TEEC_CONFIG_PAYLOAD_REF_COUNT;
		arg.session = sess->fd;
		arg.func = TA_XTEST_KEYTOKEN_CMD_ALLOC_TOKEN;

		SCRAMBLER_DEBUG("pre operation");
		res = teec_pre_process_operation(ctx, &op, params, shm);
		if (res != 0) {
			goto free_temp_refs;
		}
		SCRAMBLER_DEBUG("client invoke cmd");
		res = tee_client_invoke_func(ctx, &arg, params);
		if ((res) || (arg.ret)) {
			SCRAMBLER_ERR("cmd fail! try to use 3.22 cmd!");
			arg.func = TA_XTEST_KEYTOKEN_CMD_ALLOC_TOKEN_3_22;
			res = tee_client_invoke_func(ctx, &arg, params);
			if (res)
				goto free_temp_refs;
			else if (arg.ret) {
				res = arg.ret;
				goto free_temp_refs;
			}
		}

		res = arg.ret;
		SCRAMBLER_DEBUG("post operation");
		teec_post_process_operation(&op, params, shm);
	}

	SCRAMBLER_DEBUG("op.params[1].value.a = 0x%x", op.params[1].value.a);
	SCRAMBLER_DEBUG("op.params[1].value.b = 0x%x", op.params[1].value.b);
	*u64Token = ((uint64_t)(op.params[1].value.a) << SHIFTBIT_LOW4BYTES2HIGH) | op.params[1].value.b;

free_temp_refs:
	teec_free_temp_refs(&op, shm);
	return res;
}

static int _scrambler_test_tee_allocate_keytoken(unsigned long long *pkeyToken)
{
	TEEC_Result res;
	struct tee_param param = { 0 };

	// init ctx
	SCRAMBLER_DEBUG("init ctx");
	{
		struct tee_ioctl_version_data vers = {
			.impl_id = TEE_OPTEE_CAP_TZ,
			.impl_caps = TEE_IMPL_ID_OPTEE,
			.gen_caps = TEE_GEN_CAP_GP,
		};
		ctx = tee_client_open_context(NULL, _scrambler_optee_match, NULL, &vers);
		if (IS_ERR(ctx)) {
			SCRAMBLER_ERR("failed to open context\n");
			return -EFAULT;
		}
		xtest_bInit = true;
	}

	// open session
	SCRAMBLER_DEBUG("open session");
	{
		struct tee_ioctl_open_session_arg arg;
		struct TEEC_Operation op = { 0 };
		int rc;
		struct TEEC_UUID uuid = MTK_XTEST_TA_UUID;

		memset(&arg, 0, sizeof(struct tee_ioctl_open_session_arg));
		uuid_to_octets(arg.uuid, &uuid);
		arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC;

		op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_OUTPUT, TEEC_NONE, TEEC_NONE, TEEC_NONE);
		param.attr = op.paramTypes;

		SCRAMBLER_DEBUG("UUID = 0x%x\n", *(arg.uuid));
		rc = tee_client_open_session(ctx, &arg, (struct tee_param *)&param);

		if (rc == 0) {
			SCRAMBLER_DEBUG("arg.ret = 0x%x\n", arg.ret);
			SCRAMBLER_DEBUG("arg.session = 0x%x\n", arg.session);
			xtest_ta_session.fd = arg.session;
			SCRAMBLER_DEBUG("xtest session.fd=0x%llx\n",
					(u64)(xtest_ta_session.fd));
		} else {
			SCRAMBLER_ERR("tee_client_open_session() fail,rc=0x%x\n", rc);
			res = TEEC_ERROR_GENERIC;
			goto close_ctx;
		}
	}

	// invoke cmd
	SCRAMBLER_DEBUG("invoke cmd");
	{
		TEEC_Session *sess = (TEEC_Session *)&xtest_ta_session;
		res = ta_keytoken_cmd_allocate_token(ctx,
				sess,
				TEE_KEY_TOKEN_USAGE_USAGE_RECORD,
				TEE_KEY_TOKEN_TYPE_AES,
				EN_KEY_TOKEN_SECURITY_LEVEL_SVP,
				pkeyToken);
		if (res != TEEC_SUCCESS) {
			SCRAMBLER_ERR("ta_keytoken_cmd_allocate_token fail!\n");
			goto close_session;
		}
	}
	return 0;

	// close
close_session:
		SCRAMBLER_DEBUG("close_session");
		tee_client_close_session(ctx, xtest_ta_session.fd);
close_ctx:
		SCRAMBLER_DEBUG("close_ctx");
		tee_client_close_context(ctx);
		xtest_bInit = false;
		return res;
}

static int ta_keytoken_cmd_free_token(struct tee_context *ctx, TEEC_Session *sess, uint64_t u64Token)
{
	int res;
	struct TEEC_Operation op = { 0 };
	struct tee_ioctl_invoke_arg arg = { 0 };
	struct tee_param params[TEEC_CONFIG_PAYLOAD_REF_COUNT];
	struct TEEC_SharedMemory_2 shm[TEEC_CONFIG_PAYLOAD_REF_COUNT];

	memset(params, 0, sizeof(struct tee_param) * TEEC_CONFIG_PAYLOAD_REF_COUNT);
	memset(shm, 0, sizeof(struct TEEC_SharedMemory_2) * TEEC_CONFIG_PAYLOAD_REF_COUNT);
	op.params[0].value.a = (uint32_t)(u64Token >> SHIFTBIT_HIGH4BYTES2LOW);
	op.params[0].value.b = (uint32_t)u64Token;

	op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_INPUT, TEEC_NONE, TEEC_NONE, TEEC_NONE);

	{
		arg.num_params = TEEC_CONFIG_PAYLOAD_REF_COUNT;
		arg.session = sess->fd;
		arg.func = TA_XTEST_KEYTOKEN_CMD_FREE_TOKEN;

		SCRAMBLER_DEBUG("pre operation");
		res = teec_pre_process_operation(ctx, &op, params, shm);
		if (res != 0) {
			goto free_temp_refs;
		}
		SCRAMBLER_DEBUG("client invoke cmd");
		res = tee_client_invoke_func(ctx, &arg, params);
		if ((res) || (arg.ret)) {
			SCRAMBLER_ERR("cmd fail! try to use 3.22 cmd!");
			arg.func = TA_XTEST_KEYTOKEN_CMD_FREE_TOKEN_3_22;
			res = tee_client_invoke_func(ctx, &arg, params);
			if (res)
				goto free_temp_refs;
			else if (arg.ret) {
				res = arg.ret;
				goto free_temp_refs;
			}
		}

		res = arg.ret;
		SCRAMBLER_DEBUG("post operation");
		teec_post_process_operation(&op, params, shm);
	}

free_temp_refs:
	teec_free_temp_refs(&op, shm);
	return res;
}

static int _scrambler_test_tee_free_keytoken(unsigned long long keyToken)
{
	TEEC_Result res;
	TEEC_Session *sess = (TEEC_Session *)&xtest_ta_session;

	if ((xtest_bInit != true) && (xtest_ta_session.fd == 0x0)) {
		SCRAMBLER_ERR("Hasn't allocate keytoken for ltp test!");
		return -EFAULT;
	}

	// invoke cmd
	SCRAMBLER_DEBUG("invoke cmd");
	res = ta_keytoken_cmd_free_token(ctx,
			sess,
			keyToken);
	if (res != TEEC_SUCCESS) {
		SCRAMBLER_ERR("ta_keytoken_cmd_free_token fail!\n");
		return res;
	}

	// close
	SCRAMBLER_DEBUG("close");
	tee_client_close_session(ctx, xtest_ta_session.fd);
	tee_client_close_context(ctx);
	xtest_bInit = false;
	return 0;
}

static int _scrambler_tee_get_pvr_pipeline_id(struct dvb_ca_slot *slot, struct dmx_tee_info *tee_info)
{
#define PIPELINE_ID_UILSIZE (4)
#define PIPELINE_ID_INDEX (2)
#define CRYTION_TYPE_INDEX (3)
	int ret;
	__u32 pipeline_id = slot->pipeline_id;

	if (!pipeline_id) {
		TEE_COMMON_ARG_T *rec_ta_param;
		__u32 rec_ta_session_id;
		__u8 *serialized_buf = NULL;
		__u32 serialized_buf_size = 0;
		__u32 serialized_buf_offset = TZ_CACHE_ALIGN(sizeof(TEE_COMMON_ARG_T));

		rec_ta_session_id = _scrambler_tee_get_rec_ta_session_id();
		if (!rec_ta_session_id) {
			SCRAMBLER_ERR("rec ta session id invalid\n");
			return -EFAULT;
		}

		serialized_buf_size = TZ_CACHE_ALIGN(sizeof(TEE_COMMON_ARG_T));
		serialized_buf_size += TZ_CACHE_ALIGN(sizeof(u32));	// result
		serialized_buf_size += TZ_CACHE_ALIGN(sizeof(u64));	// token
		serialized_buf_size += TZ_CACHE_ALIGN(sizeof(u32));	// pipeline
		serialized_buf_size += TZ_CACHE_ALIGN(sizeof(u32));	// cryption_type

		serialized_buf = kmalloc(serialized_buf_size, GFP_KERNEL);
		if (!serialized_buf) {
			SCRAMBLER_ERR("out-of-memory\n");
			return -ENOMEM;
		}

		memset(serialized_buf, 0, serialized_buf_size);

		rec_ta_param = (TEE_COMMON_ARG_T *) serialized_buf;
		rec_ta_param->ui4Command = REC_TA_CMD_GET_PIPELINE_ID;
		rec_ta_param->ui1Size = PIPELINE_ID_UILSIZE;

		rec_ta_param->apvArgument[0].ptr = (u8 *) (serialized_buf + serialized_buf_offset);
		rec_ta_param->ui4Length[0] = sizeof(u32);
		rec_ta_param->abWrite[0] = 1;
		serialized_buf_offset += TZ_CACHE_ALIGN(sizeof(u32));

		rec_ta_param->apvArgument[1].ptr = (u8 *) (serialized_buf + serialized_buf_offset);
		rec_ta_param->ui4Length[1] = sizeof(u64);
		rec_ta_param->abWrite[1] = 0;
		memcpy(rec_ta_param->apvArgument[1].ptr, &slot->keytoken, sizeof(u64));
		serialized_buf_offset += TZ_CACHE_ALIGN(sizeof(u64));

		rec_ta_param->apvArgument[PIPELINE_ID_INDEX].ptr =
		    (u8 *) (serialized_buf + serialized_buf_offset);
		rec_ta_param->ui4Length[PIPELINE_ID_INDEX] = sizeof(u32);
		rec_ta_param->abWrite[PIPELINE_ID_INDEX] = 1;
		serialized_buf_offset += TZ_CACHE_ALIGN(sizeof(u32));

		rec_ta_param->apvArgument[CRYTION_TYPE_INDEX].ptr =
		    (u8 *) (serialized_buf + serialized_buf_offset);
		rec_ta_param->ui4Length[CRYTION_TYPE_INDEX] = sizeof(u32);
		rec_ta_param->abWrite[CRYTION_TYPE_INDEX] = 1;

		ret = _scrambler_tee_invoke_cmd(rec_ta_session_id,
						0, serialized_buf, serialized_buf_size);

		SCRAMBLER_DEBUG("return value   (rec_ta_param->apvArgument[0].ptr))  = %d\n", *((u32 *) (rec_ta_param->apvArgument[0].ptr)));
		SCRAMBLER_DEBUG("keytoken value (rec_ta_param->apvArgument[1].ptr))  = 0x%llx\n", (unsigned long long)*((u32 *) (rec_ta_param->apvArgument[1].ptr)));
		SCRAMBLER_DEBUG("pipeline ID	(rec_ta_param->apvArgument[%d].ptr)) = 0x%x\n", PIPELINE_ID_INDEX, *((u32 *) (rec_ta_param->apvArgument[PIPELINE_ID_INDEX].ptr)));
		SCRAMBLER_DEBUG("cryption type  (rec_ta_param->apvArgument[%d].ptr)) = 0x%x\n", CRYTION_TYPE_INDEX, *((u32 *) (rec_ta_param->apvArgument[CRYTION_TYPE_INDEX].ptr)));

		if (ret < 0) {
			SCRAMBLER_ERR("invoke cmd fail\n");
			kfree(serialized_buf);
			return -EFAULT;
		}

		tee_info->pipeline_id = *((u32 *) (rec_ta_param->apvArgument[PIPELINE_ID_INDEX].ptr));
		tee_info->cryption_type = *((dmx_crypto_type *) (rec_ta_param->apvArgument[CRYTION_TYPE_INDEX].ptr));

		kfree(serialized_buf);
		SCRAMBLER_DEBUG("pipeline id = 0x%x, cryption_type = %d\n", tee_info->pipeline_id, tee_info->cryption_type);
	}else{
        tee_info->pipeline_id = pipeline_id;
        tee_info->cryption_type = scrambler_tee_ctx.cryption_type;
	}

	return 0;
#undef PIPELINE_ID_UILSIZE
#undef PIPELINE_ID_INDEX
#undef CRYTION_TYPE_INDEX
}

static int _scrambler_tee_release_pipeline(unsigned long long keyToken)
{
#define PIPELINE_ID_RELEASE_UILSIZE (2)
	TEE_COMMON_ARG_T *rec_ta_param;
	__u32 rec_ta_session_id;
	__u8 *serialized_buf = NULL;
	__u32 serialized_buf_size = 0;
	__u32 serialized_buf_offset = TZ_CACHE_ALIGN(sizeof(TEE_COMMON_ARG_T));
	int ret;

	rec_ta_session_id = _scrambler_tee_get_rec_ta_session_id();
	if (!rec_ta_session_id) {
		SCRAMBLER_ERR("rec ta session id invalid\n");
		return -EFAULT;
	}

	serialized_buf_size = TZ_CACHE_ALIGN(sizeof(TEE_COMMON_ARG_T));
	serialized_buf_size += TZ_CACHE_ALIGN(sizeof(u32));	// result
	serialized_buf_size += TZ_CACHE_ALIGN(sizeof(u64));	// token

	serialized_buf = kmalloc(serialized_buf_size, GFP_KERNEL);
	if (!serialized_buf) {
		SCRAMBLER_ERR("out-of-memory\n");
		return -ENOMEM;
	}

	memset(serialized_buf, 0, serialized_buf_size);

	rec_ta_param = (TEE_COMMON_ARG_T *) serialized_buf;
	rec_ta_param->ui4Command = REC_TA_CMD_RELEASE;
	rec_ta_param->ui1Size = PIPELINE_ID_RELEASE_UILSIZE;

	rec_ta_param->apvArgument[0].ptr = (u8 *) (serialized_buf + serialized_buf_offset);
	rec_ta_param->ui4Length[0] = sizeof(u32);
	rec_ta_param->abWrite[0] = 1;
	serialized_buf_offset += TZ_CACHE_ALIGN(sizeof(u32));

	rec_ta_param->apvArgument[1].ptr = (u8 *) (serialized_buf + serialized_buf_offset);
	rec_ta_param->ui4Length[1] = sizeof(u64);
	rec_ta_param->abWrite[1] = 0;
	memcpy(rec_ta_param->apvArgument[1].ptr, &keyToken, sizeof(u64));
	serialized_buf_offset += TZ_CACHE_ALIGN(sizeof(u64));

	ret = _scrambler_tee_invoke_cmd(rec_ta_session_id, 0, serialized_buf, serialized_buf_size);

	if (ret < 0) {
		SCRAMBLER_ERR("invoke cmd fail\n");
		kfree(serialized_buf);
		return -EFAULT;
	}

	kfree(serialized_buf);
	return 0;
#undef PIPELINE_ID_RELEASE_UILSIZE
}

static int _scrambler_get_empty_slot(struct dvb_ca_slot *sl, int slot_num)
{
	int i;
	if (sl == NULL)
		return -EFAULT;

	for (i=0; i<slot_num; i++)
	{
		struct dvb_ca_slot *psl = (struct dvb_ca_slot *)(sl+i);
		if (psl->slot_state == DVB_CA_SLOTSTATE_NONE)
			return i;
	}
	return -EFAULT;
}

static int _scrambler_query_slot(struct dvb_ca_slot *sl, struct file *target_file, int slot_num)
{
	int i;
	if ((sl == NULL) || (target_file == NULL))
		return -EFAULT;

	for (i=0; i<slot_num; i++)
	{
		struct dvb_ca_slot *psl = (struct dvb_ca_slot *)(sl+i);
		if (psl->file == target_file)
			return i;
	}
	return -EFAULT;
}

static int _scrambler_query_record_slotnum(struct dvb_ca_slot *sl, int slot_num)
{
	int i, num = 0;
	if (sl == NULL)
		return -EFAULT;

	for (i=0 ; i<slot_num; i++)
	{
		struct dvb_ca_slot *psl = (struct dvb_ca_slot *)(sl+i);
		if (psl->purpose == KEY_RECORD)
			num++;
	}
	return num;
}

static __u8 _scrambler_tlv_savepid(
	struct dvb_ca_slot *psl, struct dscmb_tlvpid *tlv_pid,
	struct dmx2_ip_addr ip_addr, int *ret)
{
	__u8   i, j;
	struct dmx2_ip_addr *pip_addr = NULL;

	for (i = 0; i < MTK_SCRAMB_PID_KEEP; i++) {
		if (psl->pid_info[i].pid == 0) {
			psl->pid_info[i].pid = tlv_pid->pid;
			pip_addr = &(psl->pid_info[i].ip_addr);
			for (j = 0; j < MTK_SCRAMB_IPV6_Len; j++) {
				pip_addr->src_ip_addr.v6[j] = ip_addr.src_ip_addr.v6[j];
				pip_addr->dst_ip_addr.v6[j] = ip_addr.dst_ip_addr.v6[j];
			}
			psl->add_pid_num++;
			return i;
		}
	}
	return i;
}

static int mdrv_scrambler_ext_ioctl(struct file *file, unsigned int cmd, void *parg)
{
	struct dvb_device *dvbdev = file->private_data;
	struct dvb_ca_private *ca = dvbdev->priv;
	struct dvb_ca_slot *psl = NULL;
	int sl;
	int ret = 0;

	SCRAMBLER_DEBUG("%s\n", __func__);
	SCRAMBLER_DEBUG("file point 0x%lx\n", (unsigned long)file);
	if (mutex_lock_interruptible(&ca->ioctl_mutex))
		return -ERESTARTSYS;

	SCRAMBLER_DEBUG("cmd = 0x%x:\n", cmd);
	switch (cmd) {
	case CA_SET_SOURCE:
		{
			unsigned int *source = (unsigned int *)parg;
			int ret;

			/* Query for slot */
			sl = _scrambler_get_empty_slot(ca->slot_info, ca->slot_count);
			if (sl<0) {
				SCRAMBLER_ERR("No any empty slot!!\n");
				goto unlock;
			}

			/* Set source to slot */
			psl = (struct dvb_ca_slot *)((ca->slot_info)+sl);
			psl->source = *source;
			psl->slot_state = DVB_CA_SLOTSTATE_LINKINIT;
			psl->file = file;
			SCRAMBLER_DEBUG("CA DD SCRAMBLER CA_SET_SOURCE\n");
			SCRAMBLER_DEBUG("ca:*keytoken=0x%llx, source = %d\n", psl->keytoken, psl->source);

			ret = _scrambler_tee_open_ctx();
			if (ret < 0) {
				SCRAMBLER_ERR("open tee ctx fail\n");
				ret = -EFAULT;
				goto unlock;
			}

			ret = _scrambler_tee_open_session((__u32 *)
							  &scrambler_tee_ctx.rec_ta_session.fd);
			if (ret < 0) {
				SCRAMBLER_ERR("open tee session fail\n");
				ret = -EFAULT;
				goto unlock;
			}

			SCRAMBLER_DEBUG("SCRAMBLER_tee_ctx.rec_ta_session.fd=0x%llx\n",
					(u64)(scrambler_tee_ctx.rec_ta_session.fd));
		}
		break;

	case CA_SET_KEYTOKEN:
		{
			unsigned long long *keytoken = (unsigned long long *)parg;
			struct dmx_tee_info tee_info;
			struct dmx_crypto_info crypto_info;
			__u32 keytoken_purpose = 0;
			unsigned int source = 0;

			memset(&tee_info, 0, sizeof(tee_info));
			memset(&crypto_info, 0, sizeof(crypto_info));

			/* Query for slot */
			sl = _scrambler_query_slot(ca->slot_info, file, ca->slot_count);
			SCRAMBLER_DEBUG("Query slot %d\n", sl);
			if (sl<0) {
				SCRAMBLER_ERR("Wrong Slot index %d, maybe not set source!\n", sl);
				ca->keytoken = *keytoken;
				keytoken_purpose = (ca->keytoken & 0xF);
				goto set_keytoken4not_setsource;
			}

			/* Set Keytoken to slot */
			psl = (struct dvb_ca_slot *)((ca->slot_info)+sl);
			psl->keytoken = *keytoken;
			keytoken_purpose = (psl->keytoken & 0xF);
			psl->purpose = keytoken_purpose;
			source = psl->source;
			psl->pipeline_id = 0;
			SCRAMBLER_INFO("ca: *keytoken=0x%llx, purpose=%d, source=%d\n",
					psl->keytoken, keytoken_purpose, psl->source);

set_keytoken4not_setsource:
			ca->flags = CA_SCRB_USED;

			if (keytoken_purpose == KEY_RECORD) {
				/* check for record slot only support one record */
				if (_scrambler_query_record_slotnum(ca->slot_info, ca->slot_count) > MTK_SCRAMB_RECORD_SLOTNUM) {
					SCRAMBLER_ERR("Not availible to support more than one record path!\n");
					ret = -EINVAL;
					goto unlock;
				}

				SCRAMBLER_DEBUG("Current purpose %d, only %d need to set crypto type\n",
						keytoken_purpose, KEY_PLAYBACK);
				goto unlock;
			}
			else if (keytoken_purpose == KEY_PLAYBACK) {
				struct dmx_filein_secure_info filein_secure_info;
				if (mtk_dmx_tee_if_play_getPipelineId(*keytoken, &tee_info.pipeline_id, &tee_info.cryption_type)) {
					SCRAMBLER_ERR("Error dmx tee_get_pvr_pipeline_id FAIL!\n");
					ret = -EINVAL;
					goto unlock;
				}
				scrambler_tee_ctx.cryption_type = tee_info.cryption_type;
				psl->pipeline_id = tee_info.pipeline_id;
				SCRAMBLER_DEBUG("tee_info.cryption_type = %d\n", tee_info.cryption_type);

				if (tee_info.cryption_type == CRYPTO_TYPE_BLOCK) {
					crypto_info.path_id = source;
					crypto_info.type = tee_info.cryption_type;
					ret = mtk_dmx_set_filein_crypto_type(crypto_info);
					if (ret < 0)
						SCRAMBLER_ERR("dmx set_crypto_type fail ret = 0x%x\n", ret);
					filein_secure_info.path_id = source;
					filein_secure_info.key_token = *keytoken;
					filein_secure_info.pipeline_id = tee_info.pipeline_id;
					ret = mtk_dmx_set_filein_secure_mode(filein_secure_info);
					if (ret < 0)
						SCRAMBLER_ERR("dmx set_filein_secure_mode fail ret = 0x%x\n", ret);

				} else {
					SCRAMBLER_ERR("Error Get cryption type(%d) FAIL!\n", tee_info.cryption_type);
					ret = -EINVAL;
				}
			}
			else {
				SCRAMBLER_ERR("Error purpose (%d) FAIL!\n", keytoken_purpose);
				ret = -EINVAL;
			}
		}
		break;

	case CA_ADD_PID:
		{
			unsigned short *pid = (unsigned short *)parg;
			struct dmx_query_info query_info;
			struct dmx_tee_info tee_info;
			struct dmx_secure_info secure_info;
			__u32 dmx_flt_id;
			__u32 keytoken_purpose = 0;
			int i;

			memset(&query_info, 0, sizeof(query_info));
			memset(&tee_info, 0, sizeof(tee_info));
			memset(&secure_info, 0, sizeof(secure_info));

			/* Query for slot */
			sl = _scrambler_query_slot(ca->slot_info, file, ca->slot_count);
			SCRAMBLER_INFO("Query slot %d\n", sl);
			if (sl<0) {
				SCRAMBLER_ERR("Wrong Slot index %d\n", sl);
				ret = -EINVAL;
				goto unlock;
			}

			/* Set Keytoken to slot */
			psl = (struct dvb_ca_slot *)((ca->slot_info)+sl);
			SCRAMBLER_INFO("ca->slot[0] ptr 0x%lx, move slot[%d] ptr 0x%lx\n",
					(unsigned long)ca->slot_info, (int)sl, (unsigned long)psl);
			SCRAMBLER_DEBUG(" *pid  = %d\n", *pid);
			SCRAMBLER_INFO("ca: *pid %d *keytoken=0x%llx, source = %d\n",
					*pid, psl->keytoken, psl->source);
			keytoken_purpose = psl->purpose;
			query_info.path_id = psl->source;
			query_info.pid = *pid;

			if (keytoken_purpose == KEY_RECORD)
				query_info.purpose = PURPOSE_RECORD;
			else {
				SCRAMBLER_ERR("Not support purpose %d\n", keytoken_purpose);
				ret = -EINVAL;
				goto unlock;
			}

			SCRAMBLER_DEBUG("query:.purpose  = %d  path_id= %d  pid = %d\n",
					query_info.purpose, query_info.path_id, query_info.pid);

			/* keep pid */
			for (i = 0; i < MTK_SCRAMB_PID_KEEP; i++) {
				if (psl->pid_info[i].pid == 0) {
					psl->pid_info[i].pid = *pid;
					psl->add_pid_num++;
					break;
				}
			}
			SCRAMBLER_INFO("Store PID %d to Slot 0x%lx, index %d\n",
				*pid, (unsigned long)psl, i);
			if (i == MTK_SCRAMB_PID_KEEP) {
				SCRAMBLER_ERR(
					"Numbers of pid is bigger than MTK_SCRAMB_PID_KEEP %d\n",
					MTK_SCRAMB_PID_KEEP);
				ret = -EINVAL;
				goto unlock;
			}

			dmx_flt_id = mtk_dmx_get_filter_id(&query_info);
			SCRAMBLER_DEBUG("CA_ADD_PID :dmx_flt_id = 0x%x *pid =%d\n", dmx_flt_id, *pid);

			if (dmx_flt_id >= MTK_SCRAMB_PIDFLT_NUM) {
				SCRAMBLER_ERR("Error dmx flt id = %d! Wait pid start\n",
					dmx_flt_id);
				psl->delay_pid_num++;
				psl->pid_info[i].delay = true;
				goto unlock;
			}

			if(_scrambler_tee_get_pvr_pipeline_id(psl, &tee_info) != 0) {
				SCRAMBLER_ERR("Error dmx tee_get_pvr_pipeline_id FAIL!\n");
				ret = -EINVAL;
				goto unlock;
			}
			psl->pipeline_id = tee_info.pipeline_id;

			if (tee_info.pipeline_id != 0) {
				SCRAMBLER_DEBUG("pipe_id = 0x%x\n", tee_info.pipeline_id);
				secure_info.enable = true;
				secure_info.pipeline_id = tee_info.pipeline_id;
				secure_info.filter_id = dmx_flt_id;
				secure_info.key_token = psl->keytoken;
				ret = mtk_dmx_set_secure_mode(
					secure_info,
					query_info.purpose);
				if (ret < 0)
					SCRAMBLER_ERR("dmx set_secure_mode fail ret = 0x%x\n", ret);
			} else {
				SCRAMBLER_ERR("Error Get pipe id(%d) FAIL!\n", tee_info.pipeline_id);
				ret = -EINVAL;
			}
		}
		break;

	case CA_ADD_TLVPID:
		{
			struct dscmb_tlvpid *tlv_pid = (struct dscmb_tlvpid *)parg;
			struct dmx_query_info query_info;
			struct dmx_tee_info tee_info;
			struct dmx_secure_info secure_info;
			struct dmx2_ip_addr ip_addr;
			__u32  dmx_flt_id;
			__u32  keytoken_purpose = 0;
			__u8   i;

			ip_addr.src_port = tlv_pid->ip_addr.src_port;
			ip_addr.dst_port = tlv_pid->ip_addr.dst_port;

			for (i = 0; i < IPV6_Len; i++) {
				ip_addr.src_ip_addr.v6[i] = tlv_pid->ip_addr.src_ip_addr.v6[i];
				ip_addr.dst_ip_addr.v6[i] = tlv_pid->ip_addr.dst_ip_addr.v6[i];
			}

			memset(&query_info, 0, sizeof(query_info));
			memset(&tee_info, 0, sizeof(tee_info));
			memset(&secure_info, 0, sizeof(secure_info));

			/* Query for slot */
			sl = _scrambler_query_slot(ca->slot_info, file, ca->slot_count);
			SCRAMBLER_DEBUG("Query slot %d\n", sl);
			if (sl<0) {
				SCRAMBLER_ERR("Wrong Slot index %d\n", sl);
				ret = -EINVAL;
				goto unlock;
			}

			/* Set Keytoken to slot */
			psl = (struct dvb_ca_slot *)((ca->slot_info)+sl);
			SCRAMBLER_DEBUG(" *pid  = 0x%x\n", (__u16)tlv_pid->pid);
			SCRAMBLER_DEBUG("ca: *pid %d *keytoken=0x%llx, source = %d\n",
					(__u16)tlv_pid->pid, psl->keytoken, psl->source);
			keytoken_purpose = psl->purpose;
			query_info.path_id = psl->source;
			query_info.pid = tlv_pid->pid;
			query_info.ip_addr = &ip_addr;

			if (keytoken_purpose == KEY_RECORD)
				query_info.purpose = PURPOSE_RECORD;
			else {
				SCRAMBLER_ERR("Not support purpose %d\n", keytoken_purpose);
				ret = -EINVAL;
				goto unlock;
			}
			SCRAMBLER_DEBUG("query:.purpose  = %d  path_id= %d  pid = 0x%x\n",
					query_info.purpose, query_info.path_id, query_info.pid);

			/* keep pid */
			i = _scrambler_tlv_savepid(psl, tlv_pid, ip_addr, &ret);
			if (i == MTK_SCRAMB_PID_KEEP) {
				SCRAMBLER_ERR(
					"Numbers of pid is bigger than MTK_SCRAMB_PID_KEEP %d\n",
					MTK_SCRAMB_PID_KEEP);
				ret = -EINVAL;
				goto unlock;
			}
			SCRAMBLER_INFO("Store PID %d to Slot 0x%lx, index %d\n",
				tlv_pid->pid, (unsigned long)psl, i);

			dmx_flt_id = mtk_dmx_get_filter_id(&query_info);
			SCRAMBLER_DEBUG("CA_ADD_TLVPID :dmx_flt_id = %u  pid = 0x%x\n",
				dmx_flt_id, query_info.pid);
			if (dmx_flt_id >= MTK_SCRAMB_PIDFLT_NUM) {
				SCRAMBLER_ERR("Error dmx flt id = %d! Wait pid start\n",
					dmx_flt_id);
				psl->delay_pid_num++;
				psl->pid_info[i].delay = true;
				goto unlock;
			}

			if(_scrambler_tee_get_pvr_pipeline_id(psl, &tee_info) != 0) {
				SCRAMBLER_ERR("Error dmx tee_get_pvr_pipeline_id FAIL!\n");
				ret = -EINVAL;
				goto unlock;
			}
			psl->pipeline_id = tee_info.pipeline_id;

			if (tee_info.pipeline_id != 0) {
				SCRAMBLER_DEBUG("pipe_id = 0x%x\n", tee_info.pipeline_id);
				secure_info.enable = true;
				secure_info.pipeline_id = tee_info.pipeline_id;
				secure_info.filter_id = dmx_flt_id;
				secure_info.key_token = psl->keytoken;
				ret = mtk_dmx_set_secure_mode(
					secure_info,
					query_info.purpose);
				if (ret < 0)
					SCRAMBLER_ERR("dmx set_secure_mode fail ret = 0x%x\n", ret);
			} else {
				SCRAMBLER_ERR("Error Get pipe id(%d) FAIL!\n", tee_info.pipeline_id);
				ret = -EINVAL;
			}
		}
		break;

	case CA_REMOVE_PID:
		{
			unsigned short *pid = (unsigned short *)parg;
			struct dmx_query_info query_info;
			struct dmx_tee_info tee_info;
			struct dmx_secure_info secure_info;
			__u32 dmx_flt_id;
			__u32 keytoken_purpose = 0;
			int i;

			memset(&query_info, 0, sizeof(query_info));
			memset(&tee_info, 0, sizeof(tee_info));
			memset(&secure_info, 0, sizeof(secure_info));

			SCRAMBLER_DEBUG(" *pid  = %d\n", *pid);
			/* Query for slot */
			sl = _scrambler_query_slot(ca->slot_info, file, ca->slot_count);
			SCRAMBLER_DEBUG("Query slot %d\n", sl);
			if (sl <0) {
				SCRAMBLER_ERR("Wrong Slot index %d\n", sl);
				ret = -EINVAL;
				goto unlock;
			}

			/* Set Keytoken to slot */
			psl = (struct dvb_ca_slot *)((ca->slot_info)+sl);
			keytoken_purpose = psl->purpose;
			query_info.path_id = psl->source;
			query_info.pid = *pid;
			SCRAMBLER_INFO("ca: *pid %d *keytoken=0x%llx, source = %d\n",
					*pid, psl->keytoken, psl->source);

			if (keytoken_purpose == KEY_RECORD)
				query_info.purpose = PURPOSE_RECORD;
			else {
				SCRAMBLER_ERR("Not support purpose %d\n", keytoken_purpose);
				ret = -EINVAL;
				goto unlock;
			}
			SCRAMBLER_INFO("query:.purpose  = %d  path_id= %d  pid = %d\n",
					query_info.purpose, query_info.path_id, query_info.pid);

			/* query whether pid is in the slot*/
			if (psl->add_pid_num > 0) {
				for (i = 0; i < MTK_SCRAMB_PID_KEEP; i++) {
					if (psl->pid_info[i].pid == *pid) {
						SCRAMBLER_DEBUG("found PID %d in Slot\n",
							(int)*pid);
						break;
					}
				}
				if (i == MTK_SCRAMB_PID_KEEP) {
					SCRAMBLER_INFO(
						"Not found PID %d in Slot, filter is already stopped!\n",
						(int)*pid);
					goto unlock;
				}
			} else if (psl->add_pid_num == 0) {
				SCRAMBLER_DEBUG("No more pid can be removed!\n");
				goto unlock;
			}

			dmx_flt_id = mtk_dmx_get_filter_id(&query_info);
			SCRAMBLER_DEBUG("CA_REMOVE_PID :dmx_flt_id = 0x%x *pid =%d\n", dmx_flt_id, *pid);

			if (dmx_flt_id > MTK_SCRAMB_PIDFLT_NUM) {
				SCRAMBLER_ERR("Error dmx flt id = 0x%x, *pid =%d!\n",
					dmx_flt_id, *pid);
				ret = -EINVAL;
				goto unlock;
			}

			if(_scrambler_tee_get_pvr_pipeline_id(psl, &tee_info) != 0) {
				SCRAMBLER_ERR("Error dmx tee_get_pvr_pipeline_id FAIL!\n");
				ret = -EINVAL;
				goto unlock;
			}
			psl->pipeline_id = tee_info.pipeline_id;

			if (tee_info.pipeline_id != 0) {
				SCRAMBLER_DEBUG("pipe_id = 0x%x\n", tee_info.pipeline_id);
				secure_info.enable = false;
				secure_info.pipeline_id = tee_info.pipeline_id;
				secure_info.filter_id = dmx_flt_id;
				secure_info.key_token = psl->keytoken;
				ret = mtk_dmx_set_secure_mode(
					secure_info,
					query_info.purpose);
				if (ret < 0)
					SCRAMBLER_ERR("dmx set_secure_mode fail ret = 0x%x\n", ret);
			} else {
				SCRAMBLER_ERR("Error Get pipe id(%d) FAIL!\n", tee_info.pipeline_id);
				ret = -EINVAL;
			}

			/* reset pid in the slot */
			if (psl->add_pid_num > 0) {
				psl->pid_info[i].pid = 0;
				psl->pid_info[i].delay = false;
				psl->add_pid_num--;
				memset((void *)&psl->pid_info[i].ip_addr,
					0x0, sizeof(struct dmx2_ip_addr));
			}
			SCRAMBLER_INFO("Remove pid %d in slot%d!", (int)*pid, i);
		}
		break;

	case CA_REMOVE_TLVPID:
		{
			struct dscmb_tlvpid *tlv_pid = (struct dscmb_tlvpid *)parg;
			struct dmx_query_info query_info;
			struct dmx_tee_info tee_info;
			struct dmx_secure_info secure_info;
			struct dmx2_ip_addr ip_addr;
			__u32 dmx_flt_id;
			__u32 keytoken_purpose = 0;
			__u8	 i;

			ip_addr.src_port = tlv_pid->ip_addr.src_port;
			ip_addr.dst_port = tlv_pid->ip_addr.dst_port;

			for (i = 0; i < IPV6_Len; i++) {
				ip_addr.src_ip_addr.v6[i] = tlv_pid->ip_addr.src_ip_addr.v6[i];
				ip_addr.dst_ip_addr.v6[i] = tlv_pid->ip_addr.dst_ip_addr.v6[i];
			}

			memset(&query_info, 0, sizeof(query_info));
			memset(&tee_info, 0, sizeof(tee_info));
			memset(&secure_info, 0, sizeof(secure_info));

			/* Query for slot */
			sl = _scrambler_query_slot(ca->slot_info, file, ca->slot_count);
			SCRAMBLER_DEBUG("Query slot %d\n", sl);
			if (sl <0) {
				SCRAMBLER_ERR("Wrong Slot index %d\n", sl);
				ret = -EINVAL;
				goto unlock;
			}

			/* Set Keytoken to slot */
			psl = (struct dvb_ca_slot *)((ca->slot_info)+sl);
			keytoken_purpose = psl->purpose;
			query_info.path_id = psl->source;
			query_info.pid = tlv_pid->pid;
			query_info.ip_addr = &ip_addr;

			SCRAMBLER_DEBUG(" *pid  = 0x%x\n", (__u16)tlv_pid->pid);
			SCRAMBLER_DEBUG("ca: pid 0x%x *keytoken=0x%llx, source = %d\n",
					(__u16)tlv_pid->pid, psl->keytoken, psl->source);

			if (keytoken_purpose == KEY_RECORD)
				query_info.purpose = PURPOSE_RECORD;
			else {
				SCRAMBLER_ERR("Not support purpose %d\n", keytoken_purpose);
				return -EINVAL;
			}
			SCRAMBLER_DEBUG("query:.purpose  = %d  path_id= %d  pid = 0x%x\n",
					query_info.purpose, query_info.path_id, query_info.pid);

			/* query whether pid is in the slot*/
			if (psl->add_pid_num > 0) {
				for (i = 0; i < MTK_SCRAMB_PID_KEEP; i++) {
					if (psl->pid_info[i].pid == tlv_pid->pid) {
						SCRAMBLER_DEBUG("found PID %d in Slot\n",
							(int)tlv_pid->pid);
						break;
					}
				}
				if (i == MTK_SCRAMB_PID_KEEP) {
					SCRAMBLER_DEBUG(
						"Not found PID %d in Slot, filter is already stopped!\n",
						(int)tlv_pid->pid);
					goto unlock;
				}
			} else if (psl->add_pid_num == 0) {
				SCRAMBLER_DEBUG("No more pid can be removed!\n");
				goto unlock;
			}

			dmx_flt_id = mtk_dmx_get_filter_id(&query_info);
			SCRAMBLER_DEBUG("CA_REMOVE_TLVPID :dmx_flt_id = %u  pid = 0x%x\n",
				dmx_flt_id, query_info.pid);

			if (dmx_flt_id > MTK_SCRAMB_PIDFLT_NUM) {
				SCRAMBLER_ERR("Error dmx flt id = %u!\n", dmx_flt_id);
				ret = -EINVAL;
				goto unlock;
			}

			if(_scrambler_tee_get_pvr_pipeline_id(psl, &tee_info) != 0) {
				SCRAMBLER_ERR("Error dmx tee_get_pvr_pipeline_id FAIL!\n");
				ret = -EINVAL;
				goto unlock;
			}
			psl->pipeline_id = tee_info.pipeline_id;

			if (tee_info.pipeline_id != 0) {
				SCRAMBLER_DEBUG("pipe_id = 0x%x\n", tee_info.pipeline_id);
				secure_info.enable = false;
				secure_info.pipeline_id = tee_info.pipeline_id;
				secure_info.filter_id = dmx_flt_id;
				secure_info.key_token = psl->keytoken;
				ret = mtk_dmx_set_secure_mode(
					secure_info,
					query_info.purpose);
				if (ret < 0)
					SCRAMBLER_ERR("dmx set_secure_mode fail ret = 0x%x\n", ret);
			} else {
				SCRAMBLER_ERR("Error Get pipe id(%d) FAIL!\n", tee_info.pipeline_id);
				ret = -EINVAL;
			}

			/* reset pid in the slot */
			psl->pid_info[i].pid = 0;
			psl->pid_info[i].delay = false;
			psl->add_pid_num--;
			memset((void *)&psl->pid_info[i].ip_addr, 0x0,
				sizeof(struct dmx2_ip_addr));
		}
		break;

	case CA_GET_SLOT_INFO:
		{
			struct ca_slot_info *info = parg;
			SCRAMBLER_DEBUG("CA_GET_SLOT_INFO\n");

			info->type = CA_SCRB;
			info->flags = ca->flags;
		}
		break;

	case CA_TEST_ALLOCATE_KEYTOKEN:
	{
		unsigned long long *keytoken = (unsigned long long *)parg;

		SCRAMBLER_DEBUG("CA_TEST_ALLOCATE_KEYTOKEN\n");
		SCRAMBLER_DEBUG("keytoken point = %p", keytoken);
		ret = _scrambler_test_tee_allocate_keytoken(keytoken);
		SCRAMBLER_DEBUG("keytoken: 0x%llx\n", *keytoken);
		break;
	}

	case CA_TEST_FREE_KEYTOKEN:
	{
		unsigned long long keytoken = *(unsigned long long *)parg;

		SCRAMBLER_DEBUG("CA_TEST_FREE_KEYTOKEN\n");
		SCRAMBLER_DEBUG("keytoken: 0x%llx\n", keytoken);
		ret = _scrambler_test_tee_free_keytoken(keytoken);
		break;
	}

	default:
		SCRAMBLER_ERR("cmd =%d not support.\n", (int)cmd);
		ret = -EINVAL;
		break;
	}

unlock:
	mutex_unlock(&ca->ioctl_mutex);
	return ret;
}

static long dvb_ca_scrambler_io_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	long ret;

	FH_READ_LOCK(&g_dmx_dev->tm.fh_lock);
	ret = _dvb_usercopy(file, cmd, arg, mdrv_scrambler_ext_ioctl);
	FH_READ_UNLOCK(&g_dmx_dev->tm.fh_lock);

	return ret;
}

static int dvb_ca_scrambler_io_open(struct inode *inode, struct file *file)
{
	struct dvb_device *dvbdev = file->private_data;
	struct dvb_ca_private *ca = dvbdev->priv;
	int err;

	SCRAMBLER_DEBUG("minor 0x%x, major 0x%x\n", 
					MINOR(inode->i_rdev), MAJOR(inode->i_rdev));
	SCRAMBLER_DEBUG("file point 0x%lx\n", (unsigned long)file);

	if (!try_module_get(ca->pub->owner)) {
		SCRAMBLER_ERR("try_module_get\n");
		return -EIO;
	}
	err = dvb_generic_open(inode, file);

	if (err < 0) {
		SCRAMBLER_ERR("open err\n");
		module_put(ca->pub->owner);
		return err;
	}

	ca->open = 1;
	dvb_ca_private_get(ca);

	SCRAMBLER_DEBUG("leave\n");
	return 0;
}

static int dvb_ca_scrambler_io_release(struct inode *inode, struct file *file)
{
	struct dvb_device *dvbdev = file->private_data;
	struct dvb_ca_private *ca = dvbdev->priv;
	struct dvb_ca_slot *psl = NULL;
	int err, sl;

	SCRAMBLER_DEBUG("\n");
	SCRAMBLER_DEBUG("file point 0x%lx\n", (unsigned long)file);

	/* Query for slot */
	sl = _scrambler_query_slot(ca->slot_info, file, ca->slot_count);
	SCRAMBLER_DEBUG("Query slot %d\n", sl);
	if (sl<0) {
		SCRAMBLER_ERR("Wrong Slot index %d\n", sl);
		if(xtest_bInit == true) {
			_scrambler_test_tee_free_keytoken(ca->keytoken);
		}
		ca->keytoken = 0;
		goto release_ca;
	}

	/* Set Keytoken to slot */
	psl = (struct dvb_ca_slot *)((ca->slot_info)+sl);

	/* Test keytoken from xtest */
	if(xtest_bInit == true) {
		_scrambler_test_tee_free_keytoken(psl->keytoken);
	}

	/* release pipeline and keytoken */
	if (psl->purpose == KEY_RECORD) {
		if(psl->keytoken != 0) {
			_scrambler_tee_release_pipeline(psl->keytoken);
		}
	}

	/* mark the CA device as closed */
	SCRAMBLER_INFO("Release Slot %d, key purpose %d\n", sl, psl->purpose);
	psl->delay_pid_num = 0;
	psl->add_pid_num = 0;
	memset(&psl->pid_info, 0, sizeof(struct scrambler_pid_info)*MTK_SCRAMB_PID_KEEP);
	psl->keytoken = 0;
	psl->slot_state = DVB_CA_SLOTSTATE_NONE;
	psl->file = NULL;
	psl->purpose = KEY_DTV;

	/* release ta session */
	if(_scrambler_query_record_slotnum(ca->slot_info, ca->slot_count) == 0) {
		if(_scrambler_tee_get_rec_ta_session_id()) {
			_scrambler_tee_close_session(_scrambler_tee_get_rec_ta_session_id());
		}
	}
release_ca:
	err = dvb_generic_release(inode, file);

	module_put(ca->pub->owner);

	dvb_ca_private_put(ca);

	return err;
}

static const struct file_operations dvb_ca_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = dvb_ca_scrambler_io_ioctl,
	.open = dvb_ca_scrambler_io_open,
	.release = dvb_ca_scrambler_io_release,
	.llseek = noop_llseek,
#ifdef CONFIG_COMPAT
	.compat_ioctl = dvb_ca_scrambler_io_ioctl,
#endif
};

static const struct dvb_device dvbdev_ca = {
	.priv = NULL,
	.users = MTK_SCRAMB_SLOTNUM,
	.readers = MTK_SCRAMB_SLOTNUM,
	.writers = MTK_SCRAMB_SLOTNUM,
#if defined(CONFIG_MEDIA_CONTROLLER_DVB) || defined(CONFIG_MTK_MEDIA_CONTROLLER_DVB)
	.name = "dvb-ca-scrambler",
#endif
	.fops = &dvb_ca_fops,
};

int mdrv_scrambler_dev_init(struct platform_device *pdev, struct mtk_dmx2dev *mtkdev)
{
	struct mtk_dvb_scrambler *dvb = NULL;
	struct dvb_ca_private *ca = NULL;
	int ret = 0;
	int i;

	SCRAMBLER_DEBUG("dev_init\n");
	dev_info(&pdev->dev, "enable\n");

	g_dmx_dev = mtkdev;

	/* initialise the system data */
	dvb = devm_kzalloc(&pdev->dev, sizeof(struct mtk_dvb_scrambler), GFP_KERNEL);
	if (!dvb) {
		SCRAMBLER_ERR("devm_kzalloc error.\n");
		return -ENOMEM;
	}

	dvb->pdev = pdev;

	// no use interrupt
	// no need to init clock

	dvb->adapter = mdrv_get_dvb_adapter();
	if (!dvb->adapter) {
		SCRAMBLER_ERR("mdrv_get_dvb_adapter error.\n");
		ret = -ENODEV;
		goto free_dvb;
	}

	dvb->ca.data = dvb;
	dvb->ca.owner = THIS_MODULE;

	/* initialise the system data */
	gca = kzalloc(sizeof(*gca), GFP_KERNEL);
	if (!gca) {
		ret = -ENOMEM;
		goto free_dvb;
	}
	ca = gca;
	dvb->ca.private = ca;

	kref_init(&ca->refcount);
	ca->pub = &dvb->ca;
	ca->flags = 0;
	ca->slot_count = MTK_SCRAMB_SLOTNUM;

	gslot_info = kcalloc(ca->slot_count, sizeof(struct dvb_ca_slot),
						GFP_KERNEL);
	if (!gslot_info) {
		ret = -ENOMEM;
		SCRAMBLER_ERR("\n");
		goto free_ca;
	}
	ca->slot_info = gslot_info;
	ca->next_read_slot = 0;

	/* register the DVB device */
	SCRAMBLER_DEBUG("dvb_register_devic\n");
	ret = dvb_register_device(dvb->adapter, &ca->dvbdev, &dvbdev_ca, ca, DVB_DEVICE_CA, 0);
	if (ret == 0) {
		SCRAMBLER_DEBUG("dvb_register_device OK\n");
		SCRAMBLER_DEBUG("dvb_register_device %p.\n", ca->dvbdev);
	} else {
		SCRAMBLER_ERR("dvb_register_device error.\n");
		ret = -ENONET;
		goto free_slot_info;
	}

	/* now initialise each slot */
	for (i = 0; i < ca->slot_count; i++) {
		struct dvb_ca_slot *sl = &ca->slot_info[i];
		memset(sl, 0, sizeof(struct dvb_ca_slot));
		sl->slot_state = DVB_CA_SLOTSTATE_NONE;
	}

	/* NO NEED cuze no use HW reg */
	mutex_init(&ca->ioctl_mutex);

	if (signal_pending(current)) {
		ret = -EINTR;
		goto unregister_device;
	}

	platform_set_drvdata(pdev, dvb);
	SCRAMBLER_DEBUG("done\n");
	return 0;

unregister_device:
	dvb_unregister_device(ca->dvbdev);
free_slot_info:
	kfree(ca->slot_info);
free_ca:
	kfree(ca);
free_dvb:
	kfree(dvb);
	return ret;
}

static void mdrv_scrambler_release(struct dvb_ca_en50221 *pubca)
{
	struct dvb_ca_private *ca = pubca->private;

	dvb_remove_device(ca->dvbdev);
	dvb_ca_private_put(ca);
	pubca->private = NULL;
}

int mdrv_scrambler_dev_exit(struct platform_device *pdev)
{
	struct mtk_dvb_scrambler *dvb = platform_get_drvdata(pdev);

	SCRAMBLER_DEBUG("in dev_exit\n");
	mdrv_scrambler_release(&dvb->ca);

	return 0;
}

void mdrv_scrambler_disconnect_pid(struct scrambler_connect_info disconnect_info)
{
	struct dmx_tee_info tee_info;
	struct dmx_secure_info secure_info;
	struct dvb_ca_slot *psl = NULL;
	int i, j, ret;

	SCRAMBLER_INFO("pathid=%d, fltid=%d, pid = 0x%x, purpose = %d\n",
		disconnect_info.path_id, disconnect_info.pid_flt_id,
		disconnect_info.pid,  disconnect_info.purpose);

	for (j = 0; j < MTK_SCRAMB_SLOTNUM; j++) {
		psl = (struct dvb_ca_slot *)(gslot_info+j);

		SCRAMBLER_INFO("Get Slot 0x%lx, index %d\n", (unsigned long)psl, j);
		SCRAMBLER_INFO("Query PID in slot, pid num %d, purpose %d\n",
			(int)psl->add_pid_num, (int)psl->purpose);

		/* sort for path id */
		if (psl->source != disconnect_info.path_id) {
			SCRAMBLER_INFO("Unmatch Slot[%d] source %d\n", j, psl->source);
			continue;
		}
		SCRAMBLER_INFO("Find match Slot[%d] source %d\n", j, psl->source);

		for (i = 0; i < MTK_SCRAMB_PID_KEEP; i++) {
			SCRAMBLER_INFO("psl->pid_info[%d].pid = %d\n",
				i, (int)psl->pid_info[i].pid);
			if (psl->pid_info[i].pid == disconnect_info.pid) {
				SCRAMBLER_INFO("Find Match PID\n");

				if (_scrambler_tee_get_pvr_pipeline_id(psl, &tee_info) != 0) {
					SCRAMBLER_ERR("Error dmx tee_get_pvr_pipeline_id FAIL!\n");
					return;
				}
				psl->pipeline_id = tee_info.pipeline_id;

				if (psl->pipeline_id != 0) {
					SCRAMBLER_INFO("pipe_id = 0x%x\n", psl->pipeline_id);
					secure_info.enable = false;
					secure_info.pipeline_id = psl->pipeline_id;
					secure_info.filter_id = disconnect_info.pid_flt_id;
					secure_info.key_token = psl->keytoken;
					ret = mtk_dmx_set_secure_mode(
						secure_info,
						disconnect_info.purpose);
					if (ret < 0) {
						SCRAMBLER_ERR(
							"dmx set_secure_mode fail ret = 0x%x\n",
							ret);
					}
				} else {
					SCRAMBLER_ERR("Error Get pipe id(%d) FAIL!\n",
						psl->pipeline_id);
				}

				SCRAMBLER_INFO("Reset PID info in slot %d\n", i);
				psl->pid_info[i].pid = 0;
				psl->pid_info[i].delay = false;
				psl->add_pid_num--;
				memset((void *)&psl->pid_info[i].ip_addr, 0x0,
						sizeof(struct dmx2_ip_addr));
				return;
			}
		}
	}

	if (j >= MTK_SCRAMB_SLOTNUM) {
		SCRAMBLER_WARN("This PID %d isn't added in all slots!\n",
			(int)disconnect_info.pid);
		return;
	}
	SCRAMBLER_WARN("PID numbers %d of Slot %d\n", (int)psl->add_pid_num, j);
}

static int _scrambler_connect_setsecuremode(
	struct dvb_ca_slot *psl,
	u16 pid_flt_id,
	dmx_purpose_t purpose)
{
	struct dmx_tee_info tee_info;
	struct dmx_secure_info secure_info;
	int ret;

	if (_scrambler_tee_get_pvr_pipeline_id(psl, &tee_info) != 0) {
		SCRAMBLER_ERR("Error dmx tee_get_pvr_pipeline_id FAIL!\n");
		return -1;
	}
	psl->pipeline_id = tee_info.pipeline_id;

	if (tee_info.pipeline_id != 0) {
		SCRAMBLER_INFO("pipe_id = 0x%x\n", tee_info.pipeline_id);
		secure_info.enable = true;
		secure_info.pipeline_id = tee_info.pipeline_id;
		secure_info.filter_id = pid_flt_id;
		secure_info.key_token = psl->keytoken;
		ret = mtk_dmx_set_secure_mode(
			secure_info,
			purpose);
		if (ret < 0)
			SCRAMBLER_ERR("dmx set_secure_mode fail ret = 0x%x\n", ret);
	} else {
		SCRAMBLER_ERR("Error Get pipe id(%d) FAIL!\n", tee_info.pipeline_id);
		return -1;
	}
	return 0;
}

void mdrv_scrambler_connect_pid(struct scrambler_connect_info connect_info)
{
	struct dvb_ca_slot *psl = NULL;
	int i, j;

	SCRAMBLER_INFO("pathid=%d, fltid=%d, pid = 0x%x, purpose = %d\n",
	connect_info.path_id, connect_info.pid_flt_id, connect_info.pid,  connect_info.purpose);

	for (j = 0; j < MTK_SCRAMB_SLOTNUM; j++) {
		psl = (struct dvb_ca_slot *)(gslot_info+j);

		/* sort for path id */
		if (psl->source != connect_info.path_id) {
			SCRAMBLER_INFO("Unmatch Slot[%d] source %d\n", j, psl->source);
			continue;
		}

		SCRAMBLER_INFO("Find match Slot[%d] source %d\n", j, psl->source);
		if (psl->delay_pid_num > 0) {
			SCRAMBLER_INFO("Get Slot 0x%lx, index %d\n", (unsigned long)psl, j);
			SCRAMBLER_INFO("slot purpose %d", psl->purpose);

			for (i = 0; i < MTK_SCRAMB_PID_KEEP; i++) {
				if ((psl->pid_info[i].pid == connect_info.pid) &&
						(psl->pid_info[i].delay == true)) {
					SCRAMBLER_INFO("Find Match delay PID\n");

					// set decure mode
					if (_scrambler_connect_setsecuremode(
						psl,
						connect_info.pid_flt_id,
						connect_info.purpose) < 0)
						return;

					psl->delay_pid_num--;
					return;
				}
			}
		}
	}
	SCRAMBLER_DEBUG("No any delay pid in all slot!\n");
}

u64 mdrv_scrambler_get_keytoken(u16 path_id, unsigned int purpose)
{
	struct dvb_ca_slot *psl = NULL;
	int j;

	if (!gslot_info) {
		SCRAMBLER_ERR("gslot_info is NULL");
		return 0;
	}

	for (j = 0; j < MTK_SCRAMB_SLOTNUM; j++) {
		psl = (struct dvb_ca_slot *)(gslot_info+j);

		if (psl->source == path_id &&
			psl->purpose == purpose)
			return psl->keytoken;
	}

	return 0;
}

#ifdef MTK_SCRAMBLER_MODULE
static int mdrv_scrambler_remove(struct platform_device *pdev)
{
	struct mtk_dvb_scrambler *dvb = platform_get_drvdata(pdev);

	mdrv_scrambler_release(&dvb->ca);

	return 0;
}

static int mtk_scrambler_suspend(struct device *pdev)
{
	return 0;
}

static int mtk_scrambler_resume(struct device *pdev)
{
	return 0;
}

static const struct dev_pm_ops mtk_scrambler_pm_ops = {
	.suspend = mtk_scrambler_suspend,
	.resume = mtk_scrambler_resume,
};

static struct platform_device mtk_scrambler_device = {
	.name = "scrambler",
	.id = -1,
};

static struct platform_device *dvb_devices[] __initdata = {
	&mtk_scrambler_device,
};

static const struct of_device_id mtk_scrambler_dt_match[] = {
	{.compatible = DRVAES_NAME},
	{},
};

MODULE_DEVICE_TABLE(of, mtk_scrambler_dt_match);
#endif

MODULE_DESCRIPTION("Mediatek SCRAMBLER driver");
MODULE_AUTHOR("WinniePooh Wu <WinniePooh.Wu@mediatek.com");
MODULE_LICENSE("GPL");
