Orange Pi5 kernel

Deprecated Linux kernel 5.10.110 for OrangePi 5/5B/5+ boards

3 Commits   0 Branches   0 Tags
/*
 * Copyright (C) 2013 ROCKCHIP, Inc.
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/kthread.h>
#include <linux/poll.h>
#include <linux/dma-mapping.h>
#include <linux/fb.h>
#include <linux/wakelock.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/rockchip/cpu.h>
#include <linux/iommu.h>
#include <asm/cacheflush.h>
#include "iep_drv.h"
#include "hw_iep_reg.h"
#include "iep_iommu_ops.h"

#define IEP_MAJOR		255
#define IEP_CLK_ENABLE
/*#define IEP_TEST_CASE*/

static int debug;
module_param(debug, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug,
		 "Debug level - higher value produces more verbose messages");

#define RK_IEP_SIZE		0x1000
#define IEP_TIMEOUT_DELAY	2*HZ
#define IEP_POWER_OFF_DELAY	4*HZ

struct iep_drvdata {
	struct miscdevice miscdev;
	void *iep_base;
	int irq0;

	struct clk *aclk_iep;
	struct clk *hclk_iep;
	struct clk *pd_iep;
	struct clk *aclk_vio1;

	struct mutex mutex;

	/* direct path interface mode. true: enable, false: disable */
	bool dpi_mode;

	struct delayed_work power_off_work;

	/* clk enable or disable */
	bool enable;
	struct wake_lock wake_lock;

	atomic_t iep_int;
	atomic_t mmu_page_fault;
	atomic_t mmu_bus_error;

	/* capability for this iep device */
	struct IEP_CAP cap;
	struct device *dev;
};

struct iep_drvdata *iep_drvdata1 = NULL;
iep_service_info iep_service;

static void iep_reg_deinit(struct iep_reg *reg)
{
	struct iep_mem_region *mem_region = NULL, *n;
	/* release memory region attach to this registers table.*/
	if (iep_service.iommu_dev) {
		list_for_each_entry_safe(mem_region, n, &reg->mem_region_list,
					 reg_lnk) {
			iep_iommu_unmap_iommu(iep_service.iommu_info,
					      reg->session, mem_region->hdl);
			iep_iommu_free(iep_service.iommu_info,
				       reg->session, mem_region->hdl);
			list_del_init(&mem_region->reg_lnk);
			kfree(mem_region);
		}
	}

	list_del_init(&reg->session_link);
	list_del_init(&reg->status_link);
	kfree(reg);
}

static void iep_reg_from_wait_to_ready(struct iep_reg *reg)
{
	list_del_init(&reg->status_link);
	list_add_tail(&reg->status_link, &iep_service.ready);

	list_del_init(&reg->session_link);
	list_add_tail(&reg->session_link, &reg->session->ready);
}

static void iep_reg_from_ready_to_running(struct iep_reg *reg)
{
	list_del_init(&reg->status_link);
	list_add_tail(&reg->status_link, &iep_service.running);

	list_del_init(&reg->session_link);
	list_add_tail(&reg->session_link, &reg->session->running);
}

static void iep_del_running_list(void)
{
	struct iep_reg *reg;
	int cnt = 0;

	mutex_lock(&iep_service.lock);

	while (!list_empty(&iep_service.running)) {
		BUG_ON(cnt != 0);
		reg = list_entry(iep_service.running.next,
				 struct iep_reg, status_link);

		atomic_dec(&reg->session->task_running);
		atomic_dec(&iep_service.total_running);

		if (list_empty(&reg->session->waiting)) {
			atomic_set(&reg->session->done, 1);
			atomic_inc(&reg->session->num_done);
			wake_up(&reg->session->wait);
		}

		iep_reg_deinit(reg);
		cnt++;
	}

	mutex_unlock(&iep_service.lock);
}

static void iep_dump(void)
{
	struct iep_status sts;

	sts = iep_get_status(iep_drvdata1->iep_base);

	IEP_INFO("scl_sts: %u, dil_sts %u, wyuv_sts %u, ryuv_sts %u, wrgb_sts %u, rrgb_sts %u, voi_sts %u\n",
		sts.scl_sts, sts.dil_sts, sts.wyuv_sts, sts.ryuv_sts, sts.wrgb_sts, sts.rrgb_sts, sts.voi_sts); {
		int *reg = (int *)iep_drvdata1->iep_base;
		int i;

		/* could not read validate data from address after base+0x40 */
		for (i = 0; i < 0x40; i++) {
			IEP_INFO("%08x ", reg[i]);

			if ((i + 1) % 4 == 0) {
				IEP_INFO("\n");
			}
		}

		IEP_INFO("\n");
	}
}

/* Caller must hold iep_service.lock */
static void iep_del_running_list_timeout(void)
{
	struct iep_reg *reg;

	mutex_lock(&iep_service.lock);

	while (!list_empty(&iep_service.running)) {
		reg = list_entry(iep_service.running.next, struct iep_reg, status_link);

		atomic_dec(&reg->session->task_running);
		atomic_dec(&iep_service.total_running);

		/* iep_soft_rst(iep_drvdata1->iep_base); */

		iep_dump();

		if (list_empty(&reg->session->waiting)) {
			atomic_set(&reg->session->done, 1);
			wake_up(&reg->session->wait);
		}

		iep_reg_deinit(reg);
	}

	mutex_unlock(&iep_service.lock);
}

static inline void iep_queue_power_off_work(void)
{
	queue_delayed_work(system_wq, &iep_drvdata1->power_off_work, IEP_POWER_OFF_DELAY);
}

static void iep_power_on(void)
{
	static ktime_t last;
	ktime_t now = ktime_get();
	if (ktime_to_ns(ktime_sub(now, last)) > NSEC_PER_SEC) {
		cancel_delayed_work_sync(&iep_drvdata1->power_off_work);
		iep_queue_power_off_work();
		last = now;
	}

	if (iep_service.enable)
		return;

	IEP_INFO("IEP Power ON\n");

	/* iep_soft_rst(iep_drvdata1->iep_base); */

#ifdef IEP_CLK_ENABLE
	pm_runtime_get_sync(iep_drvdata1->dev);
	if (iep_drvdata1->pd_iep)
		clk_prepare_enable(iep_drvdata1->pd_iep);
	clk_prepare_enable(iep_drvdata1->aclk_iep);
	clk_prepare_enable(iep_drvdata1->hclk_iep);
#endif

	wake_lock(&iep_drvdata1->wake_lock);

	iep_iommu_attach(iep_service.iommu_info);

	iep_service.enable = true;
}

static void iep_power_off(void)
{
	int total_running;

	if (!iep_service.enable) {
		return;
	}

	IEP_INFO("IEP Power OFF\n");

	total_running = atomic_read(&iep_service.total_running);
	if (total_running) {
		IEP_WARNING("power off when %d task running!!\n", total_running);
		mdelay(50);
		IEP_WARNING("delay 50 ms for running task\n");
		iep_dump();
	}

	if (iep_service.iommu_dev) {
		iep_iommu_detach(iep_service.iommu_info);
	}

#ifdef IEP_CLK_ENABLE
	clk_disable_unprepare(iep_drvdata1->aclk_iep);
	clk_disable_unprepare(iep_drvdata1->hclk_iep);
	if (iep_drvdata1->pd_iep)
		clk_disable_unprepare(iep_drvdata1->pd_iep);
	pm_runtime_put(iep_drvdata1->dev);
#endif

	wake_unlock(&iep_drvdata1->wake_lock);
	iep_service.enable = false;
}

static void iep_power_off_work(struct work_struct *work)
{
	if (mutex_trylock(&iep_service.lock)) {
		if (!iep_drvdata1->dpi_mode) {
			IEP_INFO("iep dpi mode inactivity\n");
			iep_power_off();
		}
		mutex_unlock(&iep_service.lock);
	} else {
		/* Come back later if the device is busy... */
		iep_queue_power_off_work();
	}
}

#ifdef CONFIG_FB_ROCKCHIP
extern void rk_direct_fb_show(struct fb_info *fbi);
extern struct fb_info* rk_get_fb(int fb_id);
extern bool rk_fb_poll_wait_frame_complete(void);
extern int rk_fb_dpi_open(bool open);
extern int rk_fb_dpi_win_sel(int layer_id);

static void iep_config_lcdc(struct iep_reg *reg)
{
	struct fb_info *fb;
	int fbi = 0;
	int fmt = 0;

	fbi = reg->layer == 0 ? 0 : 1;

	rk_fb_dpi_win_sel(fbi);

	fb = rk_get_fb(fbi);
#if 1
	switch (reg->format) {
	case IEP_FORMAT_ARGB_8888:
	case IEP_FORMAT_ABGR_8888:
		fmt = HAL_PIXEL_FORMAT_RGBA_8888;
		fb->var.bits_per_pixel = 32;

		fb->var.red.length = 8;
		fb->var.red.offset = 16;
		fb->var.red.msb_right = 0;

		fb->var.green.length = 8;
		fb->var.green.offset = 8;
		fb->var.green.msb_right = 0;

		fb->var.blue.length = 8;
		fb->var.blue.offset = 0;
		fb->var.blue.msb_right = 0;

		fb->var.transp.length = 8;
		fb->var.transp.offset = 24;
		fb->var.transp.msb_right = 0;

		break;
	case IEP_FORMAT_BGRA_8888:
		fmt = HAL_PIXEL_FORMAT_BGRA_8888;
		fb->var.bits_per_pixel = 32;
		break;
	case IEP_FORMAT_RGB_565:
		fmt = HAL_PIXEL_FORMAT_RGB_565;
		fb->var.bits_per_pixel = 16;

		fb->var.red.length = 5;
		fb->var.red.offset = 11;
		fb->var.red.msb_right = 0;

		fb->var.green.length = 6;
		fb->var.green.offset = 5;
		fb->var.green.msb_right = 0;

		fb->var.blue.length = 5;
		fb->var.blue.offset = 0;
		fb->var.blue.msb_right = 0;

		break;
	case IEP_FORMAT_YCbCr_422_SP:
		fmt = HAL_PIXEL_FORMAT_YCbCr_422_SP;
		fb->var.bits_per_pixel = 16;
		break;
	case IEP_FORMAT_YCbCr_420_SP:
		fmt = HAL_PIXEL_FORMAT_YCrCb_NV12;
		fb->var.bits_per_pixel = 16;
		break;
	case IEP_FORMAT_YCbCr_422_P:
	case IEP_FORMAT_YCrCb_422_SP:
	case IEP_FORMAT_YCrCb_422_P:
	case IEP_FORMAT_YCrCb_420_SP:
	case IEP_FORMAT_YCbCr_420_P:
	case IEP_FORMAT_YCrCb_420_P:
	case IEP_FORMAT_RGBA_8888:
	case IEP_FORMAT_BGR_565:
		/* unsupported format */
		IEP_ERR("unsupported format %d\n", reg->format);
		break;
	default:
		;
	}

	fb->var.xoffset = 0;
	fb->var.yoffset = 0;
	fb->var.xres = reg->act_width;
	fb->var.yres = reg->act_height;
	fb->var.xres_virtual = reg->act_width;
	fb->var.yres_virtual = reg->act_height;
	fb->var.nonstd = ((reg->off_y & 0xFFF) << 20) +
		((reg->off_x & 0xFFF) << 8) + (fmt & 0xFF);
	fb->var.grayscale =
		((reg->vir_height & 0xFFF) << 20) +
		((reg->vir_width & 0xFFF) << 8) + 0;/*win0 xsize & ysize*/
#endif
	rk_direct_fb_show(fb);
}

static int iep_switch_dpi(struct iep_reg *reg)
{
	if (reg->dpi_en) {
		if (!iep_drvdata1->dpi_mode) {
			/* Turn on dpi */
			rk_fb_dpi_open(true);
			iep_drvdata1->dpi_mode = true;
		}
		iep_config_lcdc(reg);
	} else {
		if (iep_drvdata1->dpi_mode) {
			/* Turn off dpi */
			/* wait_lcdc_dpi_close(); */
			bool status;
			rk_fb_dpi_open(false);
			status = rk_fb_poll_wait_frame_complete();
			iep_drvdata1->dpi_mode = false;
			IEP_INFO("%s %d, iep dpi inactivated\n",
				 __func__, __LINE__);
		}
	}

	return 0;
}
#endif

static void iep_reg_copy_to_hw(struct iep_reg *reg)
{
	int i;

	u32 *pbase = (u32 *)iep_drvdata1->iep_base;

	/* config registers */
	for (i = 0; i < IEP_CNF_REG_LEN; i++)
		pbase[IEP_CNF_REG_BASE + i] = reg->reg[IEP_CNF_REG_BASE + i];

	/* command registers */
	for (i = 0; i < IEP_CMD_REG_LEN; i++)
		pbase[IEP_CMD_REG_BASE + i] = reg->reg[IEP_CMD_REG_BASE + i];

	/* address registers */
	for (i = 0; i < IEP_ADD_REG_LEN; i++)
		pbase[IEP_ADD_REG_BASE + i] = reg->reg[IEP_ADD_REG_BASE + i];

	/* dmac_flush_range(&pbase[0], &pbase[IEP_REG_LEN]); */
	/* outer_flush_range(virt_to_phys(&pbase[0]),virt_to_phys(&pbase[IEP_REG_LEN])); */

	dsb(sy);
}

/** switch fields order before the next lcdc frame start
 *  coming */
static void iep_switch_fields_order(void)
{
	void *pbase = (void *)iep_drvdata1->iep_base;
	int mode = iep_get_deinterlace_mode(pbase);
#ifdef CONFIG_FB_ROCKCHIP
	struct fb_info *fb;
#endif
	switch (mode) {
	case dein_mode_I4O1B:
		iep_set_deinterlace_mode(dein_mode_I4O1T, pbase);
		break;
	case dein_mode_I4O1T:
		iep_set_deinterlace_mode(dein_mode_I4O1B, pbase);
		break;
	case dein_mode_I2O1B:
		iep_set_deinterlace_mode(dein_mode_I2O1T, pbase);
		break;
	case dein_mode_I2O1T:
		iep_set_deinterlace_mode(dein_mode_I2O1B, pbase);
		break;
	default:
		;
	}
#ifdef CONFIG_FB_ROCKCHIP
	fb = rk_get_fb(1);
	rk_direct_fb_show(fb);
#endif
	/*iep_switch_input_address(pbase);*/
}

/* Caller must hold iep_service.lock */
static void iep_try_set_reg(void)
{
	struct iep_reg *reg;

	mutex_lock(&iep_service.lock);

	if (list_empty(&iep_service.ready)) {
		if (!list_empty(&iep_service.waiting)) {
			reg = list_entry(iep_service.waiting.next, struct iep_reg, status_link);

			iep_power_on();
			udelay(1);

			iep_reg_from_wait_to_ready(reg);
			atomic_dec(&iep_service.waitcnt);

			/*iep_soft_rst(iep_drvdata1->iep_base);*/

			iep_reg_copy_to_hw(reg);
		}
	} else {
		if (iep_drvdata1->dpi_mode)
			iep_switch_fields_order();
	}

	mutex_unlock(&iep_service.lock);
}

static void iep_try_start_frm(void)
{
	struct iep_reg *reg;

	mutex_lock(&iep_service.lock);

	if (list_empty(&iep_service.running)) {
		if (!list_empty(&iep_service.ready)) {
			reg = list_entry(iep_service.ready.next, struct iep_reg, status_link);
#ifdef CONFIG_FB_ROCKCHIP
			iep_switch_dpi(reg);
#endif
			iep_reg_from_ready_to_running(reg);
			iep_config_frame_end_int_en(iep_drvdata1->iep_base);
			iep_config_done(iep_drvdata1->iep_base);

			/* Start proc */
			atomic_inc(&reg->session->task_running);
			atomic_inc(&iep_service.total_running);
			iep_config_frm_start(iep_drvdata1->iep_base);
		}
	}

	mutex_unlock(&iep_service.lock);
}

static irqreturn_t iep_isr(int irq, void *dev_id)
{
	if (atomic_read(&iep_drvdata1->iep_int) > 0) {
		if (iep_service.enable) {
			if (list_empty(&iep_service.waiting)) {
				if (iep_drvdata1->dpi_mode) {
					iep_switch_fields_order();
				}
			}
			iep_del_running_list();
		}

		iep_try_set_reg();
		iep_try_start_frm();

		atomic_dec(&iep_drvdata1->iep_int);
	}

	return IRQ_HANDLED;
}

static irqreturn_t iep_irq(int irq,  void *dev_id)
{
	/*clear INT */
	void *pbase = (void *)iep_drvdata1->iep_base;

	if (iep_probe_int(pbase)) {
		iep_config_frame_end_int_clr(pbase);
		atomic_inc(&iep_drvdata1->iep_int);
	}

	return IRQ_WAKE_THREAD;
}

static void iep_service_session_clear(iep_session *session)
{
	struct iep_reg *reg, *n;

	list_for_each_entry_safe(reg, n, &session->waiting, session_link) {
		iep_reg_deinit(reg);
	}

	list_for_each_entry_safe(reg, n, &session->ready, session_link) {
		iep_reg_deinit(reg);
	}

	list_for_each_entry_safe(reg, n, &session->running, session_link) {
		iep_reg_deinit(reg);
	}
}

static int iep_open(struct inode *inode, struct file *filp)
{
	//DECLARE_WAITQUEUE(wait, current);
	iep_session *session = kzalloc(sizeof(*session), GFP_KERNEL);
	if (NULL == session) {
		IEP_ERR("unable to allocate memory for iep_session.\n");
		return -ENOMEM;
	}

	session->pid = current->pid;
	INIT_LIST_HEAD(&session->waiting);
	INIT_LIST_HEAD(&session->ready);
	INIT_LIST_HEAD(&session->running);
	INIT_LIST_HEAD(&session->list_session);
	init_waitqueue_head(&session->wait);
	/*add_wait_queue(&session->wait, wait);*/
	/* no need to protect */
	mutex_lock(&iep_service.lock);
	list_add_tail(&session->list_session, &iep_service.session);
	mutex_unlock(&iep_service.lock);
	atomic_set(&session->task_running, 0);
	atomic_set(&session->num_done, 0);

	filp->private_data = (void *)session;

	return nonseekable_open(inode, filp);
}

static int iep_release(struct inode *inode, struct file *filp)
{
	int task_running;
	iep_session *session = (iep_session *)filp->private_data;

	if (NULL == session)
		return -EINVAL;

	task_running = atomic_read(&session->task_running);

	if (task_running) {
		IEP_ERR("iep_service session %d still "
			"has %d task running when closing\n",
			session->pid, task_running);
		msleep(100);
		/*synchronization*/
	}

	wake_up(&session->wait);
	iep_power_on();
	mutex_lock(&iep_service.lock);
	list_del(&session->list_session);
	iep_service_session_clear(session);
	iep_iommu_clear(iep_service.iommu_info, session);
	kfree(session);
	mutex_unlock(&iep_service.lock);

	return 0;
}

static unsigned int iep_poll(struct file *filp, poll_table *wait)
{
	int mask = 0;
	iep_session *session = (iep_session *)filp->private_data;
	if (NULL == session)
		return POLL_ERR;
	poll_wait(filp, &session->wait, wait);
	if (atomic_read(&session->done))
		mask |= POLL_IN | POLLRDNORM;

	return mask;
}

static int iep_get_result_sync(iep_session *session)
{
	int ret = 0;

	iep_try_start_frm();

	ret = wait_event_timeout(session->wait,
		atomic_read(&session->done), IEP_TIMEOUT_DELAY);

	if (unlikely(ret < 0)) {
		IEP_ERR("sync pid %d wait task ret %d\n", session->pid, ret);
		iep_del_running_list();
	} else if (0 == ret) {
		IEP_ERR("sync pid %d wait %d task done timeout\n",
			session->pid, atomic_read(&session->task_running));
		iep_del_running_list_timeout();
		iep_try_set_reg();
		iep_try_start_frm();
		ret = -ETIMEDOUT;
	}

	return ret;
}

static void iep_get_result_async(iep_session *session)
{
	iep_try_start_frm();
	return;
}

static long iep_ioctl(struct file *filp, uint32_t cmd, unsigned long arg)
{
	int ret = 0;
	iep_session *session = (iep_session *)filp->private_data;

	if (NULL == session) {
		IEP_ERR("%s [%d] iep thread session is null\n",
			__FUNCTION__, __LINE__);
		return -EINVAL;
	}

	mutex_lock(&iep_service.mutex);

	switch (cmd) {
	case IEP_SET_PARAMETER:
		{
			struct IEP_MSG *msg;
			msg = kzalloc(sizeof(*msg), GFP_KERNEL);
			if (msg) {
				if (copy_from_user(msg, (struct IEP_MSG *)arg,
						sizeof(struct IEP_MSG))) {
					IEP_ERR("copy_from_user failure\n");
					ret = -EFAULT;
				}
			}

			if (ret == 0) {
				if (atomic_read(&iep_service.waitcnt) < 10) {
					iep_power_on();
					iep_config(session, msg);
					atomic_inc(&iep_service.waitcnt);
				} else {
					IEP_ERR("iep task queue full\n");
					ret = -EFAULT;
				}
			}

			/** REGISTER CONFIG must accord to Timing When DPI mode
			 *  enable */
			if (!iep_drvdata1->dpi_mode)
				iep_try_set_reg();
			kfree(msg);
		}
		break;
	case IEP_GET_RESULT_SYNC:
		if (0 > iep_get_result_sync(session)) {
			ret = -ETIMEDOUT;
		}
		break;
	case IEP_GET_RESULT_ASYNC:
		iep_get_result_async(session);
		break;
	case IEP_RELEASE_CURRENT_TASK:
		iep_del_running_list_timeout();
		iep_try_set_reg();
		iep_try_start_frm();
		break;
	case IEP_GET_IOMMU_STATE:
		{
			int iommu_enable = 0;

			iommu_enable = iep_service.iommu_dev ? 1 : 0;

			if (copy_to_user((void __user *)arg, &iommu_enable,
				sizeof(int))) {
				IEP_ERR("error: copy_to_user failed\n");
				ret = -EFAULT;
			}
		}
		break;
	case IEP_QUERY_CAP:
		if (copy_to_user((void __user *)arg, &iep_drvdata1->cap,
			sizeof(struct IEP_CAP))) {
			IEP_ERR("error: copy_to_user failed\n");
			ret = -EFAULT;
		}
		break;
	default:
		IEP_ERR("unknown ioctl cmd!\n");
		ret = -EINVAL;
	}
	mutex_unlock(&iep_service.mutex);

	return ret;
}

#ifdef CONFIG_COMPAT
static long compat_iep_ioctl(struct file *filp, uint32_t cmd,
			     unsigned long arg)
{
	int ret = 0;
	iep_session *session = (iep_session *)filp->private_data;

	if (NULL == session) {
		IEP_ERR("%s [%d] iep thread session is null\n",
			__func__, __LINE__);
		return -EINVAL;
	}

	mutex_lock(&iep_service.mutex);

	switch (cmd) {
	case COMPAT_IEP_SET_PARAMETER:
		{
			struct IEP_MSG *msg;

			msg = kzalloc(sizeof(*msg), GFP_KERNEL);

			if (msg) {
				if (copy_from_user
				    (msg, compat_ptr((compat_uptr_t)arg),
				     sizeof(struct IEP_MSG))) {
					IEP_ERR("copy_from_user failure\n");
					ret = -EFAULT;
				}
			}

			if (ret == 0) {
				if (atomic_read(&iep_service.waitcnt) < 10) {
					iep_power_on();
					iep_config(session, msg);
					atomic_inc(&iep_service.waitcnt);
				} else {
					IEP_ERR("iep task queue full\n");
					ret = -EFAULT;
				}
			}

			/** REGISTER CONFIG must accord to Timing When DPI mode
			 *  enable */
			if (!iep_drvdata1->dpi_mode)
				iep_try_set_reg();
			kfree(msg);
		}
		break;
	case COMPAT_IEP_GET_RESULT_SYNC:
		if (0 > iep_get_result_sync(session))
			ret = -ETIMEDOUT;
		break;
	case COMPAT_IEP_GET_RESULT_ASYNC:
		iep_get_result_async(session);
		break;
	case COMPAT_IEP_RELEASE_CURRENT_TASK:
		iep_del_running_list_timeout();
		iep_try_set_reg();
		iep_try_start_frm();
		break;
	case COMPAT_IEP_GET_IOMMU_STATE:
		{
			int iommu_enable = 0;

			iommu_enable = iep_service.iommu_dev ? 1 : 0;

			if (copy_to_user((void __user *)arg, &iommu_enable,
				sizeof(int))) {
				IEP_ERR("error: copy_to_user failed\n");
				ret = -EFAULT;
			}
		}
		break;
	case COMPAT_IEP_QUERY_CAP:
		if (copy_to_user((void __user *)arg, &iep_drvdata1->cap,
			sizeof(struct IEP_CAP))) {
			IEP_ERR("error: copy_to_user failed\n");
			ret = -EFAULT;
		}
		break;
	default:
		IEP_ERR("unknown ioctl cmd!\n");
		ret = -EINVAL;
	}
	mutex_unlock(&iep_service.mutex);

	return ret;
}
#endif

struct file_operations iep_fops = {
	.owner		= THIS_MODULE,
	.open		= iep_open,
	.release	= iep_release,
	.poll		= iep_poll,
	.unlocked_ioctl	= iep_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= compat_iep_ioctl,
#endif
};

static struct miscdevice iep_dev = {
	.minor = IEP_MAJOR,
	.name  = "iep",
	.fops  = &iep_fops,
};

static int iep_sysmmu_fault_handler(struct iommu_domain *domain,
				    struct device *iommu_dev,
				    unsigned long iova, int status, void *arg)
{
	struct iep_reg *reg = list_entry(iep_service.running.next,
		struct iep_reg, status_link);
	if (reg != NULL) {
		struct iep_mem_region *mem, *n;
		int i = 0;
		pr_info("iep, fault addr 0x%08x\n", (u32)iova);
		list_for_each_entry_safe(mem, n,
			&reg->mem_region_list,
			reg_lnk) {
			pr_info("iep, mem region [%02d] 0x%08x %ld\n",
				i, (u32)mem->iova, mem->len);
			i++;
		}

		pr_alert("iep, page fault occur\n");

		iep_del_running_list();
	}

	return 0;
}

static int iep_drv_probe(struct platform_device *pdev)
{
	struct iep_drvdata *data;
	int ret = 0;
	struct resource *res = NULL;
	u32 version;
	struct device_node *np = pdev->dev.of_node;
	struct platform_device *sub_dev = NULL;
	struct device_node *sub_np = NULL;
	u32 iommu_en = 0;
	struct iommu_domain *domain;

	of_property_read_u32(np, "iommu_enabled", &iommu_en);

	data = devm_kzalloc(&pdev->dev, sizeof(*data),
			    GFP_KERNEL);
	if (NULL == data) {
		IEP_ERR("failed to allocate driver data.\n");
		return  -ENOMEM;
	}

	iep_drvdata1 = data;

	INIT_LIST_HEAD(&iep_service.waiting);
	INIT_LIST_HEAD(&iep_service.ready);
	INIT_LIST_HEAD(&iep_service.running);
	INIT_LIST_HEAD(&iep_service.done);
	INIT_LIST_HEAD(&iep_service.session);
	atomic_set(&iep_service.waitcnt, 0);
	mutex_init(&iep_service.lock);
	atomic_set(&iep_service.total_running, 0);
	iep_service.enable = false;

#ifdef IEP_CLK_ENABLE
	data->pd_iep = devm_clk_get(&pdev->dev, "pd_iep");
	if (IS_ERR(data->pd_iep)) {
		IEP_ERR("failed to find iep power down clock source.\n");
		data->pd_iep = NULL;
	}

	data->aclk_iep = devm_clk_get(&pdev->dev, "aclk_iep");
	if (IS_ERR(data->aclk_iep)) {
		IEP_ERR("failed to find iep axi clock source.\n");
		ret = -ENOENT;
		goto err_clock;
	}

	data->hclk_iep = devm_clk_get(&pdev->dev, "hclk_iep");
	if (IS_ERR(data->hclk_iep)) {
		IEP_ERR("failed to find iep ahb clock source.\n");
		ret = -ENOENT;
		goto err_clock;
	}
#endif

	iep_service.enable = false;
	INIT_DELAYED_WORK(&data->power_off_work, iep_power_off_work);
	wake_lock_init(&data->wake_lock, WAKE_LOCK_SUSPEND, "iep");

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

	data->iep_base = (void *)devm_ioremap_resource(&pdev->dev, res);
	if (data->iep_base == NULL) {
		IEP_ERR("iep ioremap failed\n");
		ret = -ENOENT;
		goto err_ioremap;
	}

	atomic_set(&data->iep_int, 0);
	atomic_set(&data->mmu_page_fault, 0);
	atomic_set(&data->mmu_bus_error, 0);

	/* get the IRQ */
	data->irq0 = platform_get_irq(pdev, 0);
	if (data->irq0 <= 0) {
		IEP_ERR("failed to get iep irq resource (%d).\n", data->irq0);
		ret = data->irq0;
		goto err_irq;
	}

	/* request the IRQ */
	ret = devm_request_threaded_irq(&pdev->dev, data->irq0, iep_irq,
		iep_isr, IRQF_SHARED, dev_name(&pdev->dev), pdev);
	if (ret) {
		IEP_ERR("iep request_irq failed (%d).\n", ret);
		goto err_irq;
	}

	mutex_init(&iep_service.mutex);

	if (of_property_read_u32(np, "version", &version)) {
		version = 0;
	}

	data->cap.scaling_supported = 0;
	data->cap.i4_deinterlace_supported = 1;
	data->cap.i2_deinterlace_supported = 1;
	data->cap.compression_noise_reduction_supported = 1;
	data->cap.sampling_noise_reduction_supported = 1;
	data->cap.hsb_enhancement_supported = 1;
	data->cap.cg_enhancement_supported = 1;
	data->cap.direct_path_supported = 1;
	data->cap.max_dynamic_width = 1920;
	data->cap.max_dynamic_height = 1088;
	data->cap.max_static_width = 8192;
	data->cap.max_static_height = 8192;
	data->cap.max_enhance_radius = 3;

	switch (version) {
	case 0:
		data->cap.scaling_supported = 1;
		break;
	case 1:
		data->cap.compression_noise_reduction_supported = 0;
		data->cap.sampling_noise_reduction_supported = 0;
		if (soc_is_rk3126b() || soc_is_rk3126c()) {
			data->cap.i4_deinterlace_supported = 0;
			data->cap.hsb_enhancement_supported = 0;
			data->cap.cg_enhancement_supported = 0;
		}
		break;
	case 2:
		data->cap.max_dynamic_width = 4096;
		data->cap.max_dynamic_height = 2340;
		data->cap.max_enhance_radius = 2;
		break;
	default:
		;
	}

	platform_set_drvdata(pdev, data);

	ret = misc_register(&iep_dev);
	if (ret) {
		IEP_ERR("cannot register miscdev (%d)\n", ret);
		goto err_misc_register;
	}

	data->dev = &pdev->dev;
#ifdef IEP_CLK_ENABLE
	pm_runtime_enable(data->dev);
#endif

	iep_service.iommu_dev = NULL;
	sub_np = of_parse_phandle(np, "iommus", 0);
	if (sub_np) {
		sub_dev = of_find_device_by_node(sub_np);
		iep_service.iommu_dev = &sub_dev->dev;
		domain = iommu_get_domain_for_dev(&pdev->dev);
		iommu_set_fault_handler(domain, iep_sysmmu_fault_handler, data);
	}

	of_property_read_u32(np, "allocator", (u32 *)&iep_service.alloc_type);
	iep_power_on();
	iep_service.iommu_info = iep_iommu_info_create(data->dev,
						       iep_service.iommu_dev,
						       iep_service.alloc_type);
	iep_power_off();

	IEP_INFO("IEP Driver loaded succesfully\n");

	return 0;

err_misc_register:
	free_irq(data->irq0, pdev);
err_irq:
err_ioremap:
	wake_lock_destroy(&data->wake_lock);
#ifdef IEP_CLK_ENABLE
err_clock:
#endif
	return ret;
}

static int iep_drv_remove(struct platform_device *pdev)
{
	struct iep_drvdata *data = platform_get_drvdata(pdev);

	iep_iommu_info_destroy(iep_service.iommu_info);
	iep_service.iommu_info = NULL;

	wake_lock_destroy(&data->wake_lock);

	misc_deregister(&(data->miscdev));
	free_irq(data->irq0, &data->miscdev);

#ifdef IEP_CLK_ENABLE
	pm_runtime_disable(data->dev);
#endif

	return 0;
}

#if defined(CONFIG_OF)
static const struct of_device_id iep_dt_ids[] = {
	{ .compatible = "rockchip,iep", },
	{ },
};
#endif

static struct platform_driver iep_driver = {
	.probe		= iep_drv_probe,
	.remove		= iep_drv_remove,
	.driver		= {
		.name	= "iep",
#if defined(CONFIG_OF)
		.of_match_table = of_match_ptr(iep_dt_ids),
#endif
	},
};

#ifdef CONFIG_PROC_FS
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

static int proc_iep_show(struct seq_file *s, void *v)
{
	struct iep_status sts;
	//mutex_lock(&iep_service.mutex);
	iep_power_on();
	seq_printf(s, "\nIEP Modules Status:\n");
	sts = iep_get_status(iep_drvdata1->iep_base);
	seq_printf(s, "scl_sts: %u, dil_sts %u, wyuv_sts %u, "
		      "ryuv_sts %u, wrgb_sts %u, rrgb_sts %u, voi_sts %u\n",
		sts.scl_sts, sts.dil_sts, sts.wyuv_sts, sts.ryuv_sts,
		sts.wrgb_sts, sts.rrgb_sts, sts.voi_sts); {
		int *reg = (int *)iep_drvdata1->iep_base;
		int i;

		/* could not read validate data from address after base+0x40 */
		for (i = 0; i < 0x40; i++) {
			seq_printf(s, "%08x ", reg[i]);

			if ((i + 1) % 4 == 0)
				seq_printf(s, "\n");
		}

		seq_printf(s, "\n");
	}

	//mutex_unlock(&iep_service.mutex);

	return 0;
}

static int proc_iep_open(struct inode *inode, struct file *file)
{
	return single_open(file, proc_iep_show, NULL);
}

static const struct proc_ops proc_iep_fops = {
	.proc_open	= proc_iep_open,
	.proc_read	= seq_read,
	.proc_lseek	= seq_lseek,
	.proc_release	= single_release,
};

static int __init iep_proc_init(void)
{
	proc_create("iep", 0, NULL, &proc_iep_fops);
	return 0;
}

static void __exit iep_proc_release(void)
{
	remove_proc_entry("iep", NULL);
}
#endif

#ifdef IEP_TEST_CASE
void iep_test_case0(void);
#endif

static int __init iep_init(void)
{
	int ret;

	if ((ret = platform_driver_register(&iep_driver)) != 0) {
		IEP_ERR("Platform device register failed (%d).\n", ret);
		return ret;
	}

#ifdef CONFIG_PROC_FS
	iep_proc_init();
#endif

	IEP_INFO("Module initialized.\n");

#ifdef IEP_TEST_CASE
	iep_test_case0();
#endif

	return 0;
}

static void __exit iep_exit(void)
{
	IEP_ERR("%s IN\n", __func__);
#ifdef CONFIG_PROC_FS
	iep_proc_release();
#endif

	iep_power_off();
	platform_driver_unregister(&iep_driver);
}

module_init(iep_init);
module_exit(iep_exit);

/* Module information */
MODULE_AUTHOR("ljf@rock-chips.com");
MODULE_DESCRIPTION("Driver for iep device");
MODULE_LICENSE("GPL");

#ifdef IEP_TEST_CASE

/*this test just test for iep , not test iep's iommu
 *so dts need cancel iommus handle
 */

#include "yuv420sp_480x480_interlaced.h"
#include "yuv420sp_480x480_deinterlaced_i2o1.h"

//unsigned char tmp_buf[480*480*3/2];

void iep_test_case0(void)
{
	struct IEP_MSG msg;
	iep_session session;
	unsigned int phy_src, phy_tmp;
	int i;
	int ret = 0;
	unsigned char *tmp_buf;

	tmp_buf = kmalloc(480 * 480 * 3 / 2, GFP_KERNEL);

	session.pid	= current->pid;
	INIT_LIST_HEAD(&session.waiting);
	INIT_LIST_HEAD(&session.ready);
	INIT_LIST_HEAD(&session.running);
	INIT_LIST_HEAD(&session.list_session);
	init_waitqueue_head(&session.wait);
	list_add_tail(&session.list_session, &iep_service.session);
	atomic_set(&session.task_running, 0);
	atomic_set(&session.num_done, 0);

	memset(&msg, 0, sizeof(struct IEP_MSG));
	memset(tmp_buf, 0xCC, 480 * 480 * 3 / 2);

#ifdef CONFIG_ARM
	dmac_flush_range(&yuv420sp_480x480_interlaced[0],
			 &yuv420sp_480x480_interlaced[480 * 480 * 3 / 2]);
	outer_flush_range(virt_to_phys(&yuv420sp_480x480_interlaced[0]),
		virt_to_phys(&yuv420sp_480x480_interlaced[480 * 480 * 3 / 2]));

	dmac_flush_range(&tmp_buf[0], &tmp_buf[480 * 480 * 3 / 2]);
	outer_flush_range(virt_to_phys(&tmp_buf[0]), virt_to_phys(&tmp_buf[480 * 480 * 3 / 2]));
#elif defined(CONFIG_ARM64)
	__dma_flush_area(&yuv420sp_480x480_interlaced[0], 480 * 480 * 3 / 2);
	__dma_flush_area(&tmp_buf[0], 480 * 480 * 3 / 2);
#endif

	phy_src = virt_to_phys(&yuv420sp_480x480_interlaced[0]);
	phy_tmp = virt_to_phys(&tmp_buf[0]);

	IEP_INFO("*********** IEP MSG GENARATE ************\n");

	msg.src.act_w = 480;
	msg.src.act_h = 480;
	msg.src.x_off = 0;
	msg.src.y_off = 0;
	msg.src.vir_w = 480;
	msg.src.vir_h = 480;
	msg.src.format = IEP_FORMAT_YCbCr_420_SP;
	msg.src.mem_addr = phy_src;
	msg.src.uv_addr  = (phy_src + 480 * 480);
	msg.src.v_addr = 0;

	msg.dst.act_w = 480;
	msg.dst.act_h = 480;
	msg.dst.x_off = 0;
	msg.dst.y_off = 0;
	msg.dst.vir_w = 480;
	msg.dst.vir_h = 480;
	msg.dst.format = IEP_FORMAT_YCbCr_420_SP;
	msg.dst.mem_addr = phy_tmp;
	msg.dst.uv_addr = (phy_tmp + 480 * 480);
	msg.dst.v_addr = 0;

	msg.dein_mode = IEP_DEINTERLACE_MODE_I2O1;
	msg.field_order = FIELD_ORDER_BOTTOM_FIRST;

	IEP_INFO("*********** IEP TEST CASE 0  ************\n");

	iep_config(&session, &msg);
	iep_try_set_reg();
	if (0 > iep_get_result_sync(&session)) {
		IEP_INFO("%s failed, timeout\n", __func__);
		ret = -ETIMEDOUT;
	}

	mdelay(10);

	IEP_INFO("*********** RESULT CHECKING  ************\n");

	for (i = 0; i < 480 * 480 * 3 / 2; i++) {
		if (tmp_buf[i] != yuv420sp_480x480_deinterlaced_i2o1[i]) {
			IEP_INFO("diff occur position %d, 0x%02x 0x%02x\n", i, tmp_buf[i], yuv420sp_480x480_deinterlaced_i2o1[i]);

			if (i > 10) {
				iep_dump();
				break;
			}
		}
	}

	if (i == 480 * 480 * 3 / 2)
		IEP_INFO("IEP pass the checking\n");
}

#endif