vpu: add driver for mxc vpu

Signed-off-by: Jason Chen <b02280@freescale.com>
diff --git a/arch/arm/plat-mxc/include/mach/mxc_vpu.h b/arch/arm/plat-mxc/include/mach/mxc_vpu.h
new file mode 100644
index 0000000..0f81fb9
--- /dev/null
+++ b/arch/arm/plat-mxc/include/mach/mxc_vpu.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU Lesser General
+ * Public License.  You may obtain a copy of the GNU Lesser General
+ * Public License Version 2.1 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/lgpl-license.html
+ * http://www.gnu.org/copyleft/lgpl.html
+ */
+
+/*!
+ * @defgroup VPU Video Processor Unit Driver
+ */
+
+/*!
+ * @file arch-mxc/mxc_vpu.h
+ *
+ * @brief VPU system initialization and file operation definition
+ *
+ * @ingroup VPU
+ */
+
+#ifndef __ASM_ARCH_MXC_VPU_H__
+#define __ASM_ARCH_MXC_VPU_H__
+
+#include <linux/fs.h>
+
+struct mxc_vpu_platform_data {
+	bool iram_enable;
+	int  iram_size;
+	void (*reset) (void);
+	void (*pg) (int);
+};
+
+struct vpu_mem_desc {
+	u32 size;
+	dma_addr_t phy_addr;
+	u32 cpu_addr;		/* cpu address to free the dma mem */
+	u32 virt_uaddr;		/* virtual user space address */
+};
+
+#define VPU_IOC_MAGIC  'V'
+
+#define VPU_IOC_PHYMEM_ALLOC	_IO(VPU_IOC_MAGIC, 0)
+#define VPU_IOC_PHYMEM_FREE	_IO(VPU_IOC_MAGIC, 1)
+#define VPU_IOC_WAIT4INT	_IO(VPU_IOC_MAGIC, 2)
+#define VPU_IOC_PHYMEM_DUMP	_IO(VPU_IOC_MAGIC, 3)
+#define VPU_IOC_REG_DUMP	_IO(VPU_IOC_MAGIC, 4)
+#define VPU_IOC_IRAM_SETTING	_IO(VPU_IOC_MAGIC, 6)
+#define VPU_IOC_CLKGATE_SETTING	_IO(VPU_IOC_MAGIC, 7)
+#define VPU_IOC_GET_WORK_ADDR   _IO(VPU_IOC_MAGIC, 8)
+#define VPU_IOC_REQ_VSHARE_MEM	_IO(VPU_IOC_MAGIC, 9)
+#define VPU_IOC_SYS_SW_RESET	_IO(VPU_IOC_MAGIC, 11)
+#define VPU_IOC_GET_SHARE_MEM   _IO(VPU_IOC_MAGIC, 12)
+
+#define BIT_CODE_RUN			0x000
+#define BIT_CODE_DOWN			0x004
+#define	BIT_INT_CLEAR			0x00C
+#define	BIT_INT_STATUS			0x010
+
+#define MJPEG_PIC_STATUS_REG		0x3004
+
+#define BIT_WORK_CTRL_BUF_BASE		0x100
+#define BIT_WORK_CTRL_BUF_REG(i)	(BIT_WORK_CTRL_BUF_BASE + i * 4)
+#define BIT_CODE_BUF_ADDR		BIT_WORK_CTRL_BUF_REG(0)
+#define BIT_WORK_BUF_ADDR		BIT_WORK_CTRL_BUF_REG(1)
+#define BIT_PARA_BUF_ADDR		BIT_WORK_CTRL_BUF_REG(2)
+#define BIT_BIT_STREAM_CTRL		BIT_WORK_CTRL_BUF_REG(3)
+#define BIT_FRAME_MEM_CTRL		BIT_WORK_CTRL_BUF_REG(4)
+#define BIT_BIT_STREAM_PARAM		BIT_WORK_CTRL_BUF_REG(5)
+
+#define BIT_RESET_CTRL			0x11C
+
+/* i could be 0, 1, 2, 3 */
+#define	BIT_RD_PTR_BASE			0x120
+#define BIT_RD_PTR_REG(i)		(BIT_RD_PTR_BASE + i * 8)
+#define BIT_WR_PTR_REG(i)		(BIT_RD_PTR_BASE + i * 8 + 4)
+
+/* i could be 0, 1, 2, 3 */
+#define BIT_FRM_DIS_FLG_BASE		(cpu_is_mx51() ? 0x150 : 0x140)
+#define	BIT_FRM_DIS_FLG_REG(i)		(BIT_FRM_DIS_FLG_BASE + i * 4)
+
+#define BIT_BUSY_FLAG			0x160
+#define BIT_RUN_COMMAND			0x164
+#define BIT_INT_ENABLE			0x170
+
+#define	BITVAL_PIC_RUN			8
+
+#define	VPU_SLEEP_REG_VALUE		10
+#define	VPU_WAKE_REG_VALUE		11
+
+int vl2cc_init(u32 vl2cc_hw_base);
+void vl2cc_enable(void);
+void vl2cc_flush(void);
+void vl2cc_disable(void);
+void vl2cc_cleanup(void);
+
+int vl2cc_init(u32 vl2cc_hw_base);
+void vl2cc_enable(void);
+void vl2cc_flush(void);
+void vl2cc_disable(void);
+void vl2cc_cleanup(void);
+
+#endif
diff --git a/drivers/mxc/Kconfig b/drivers/mxc/Kconfig
index 2e5b7cf..cebc837 100644
--- a/drivers/mxc/Kconfig
+++ b/drivers/mxc/Kconfig
@@ -19,6 +19,8 @@
 
 source "drivers/mxc/ipu3/Kconfig"
 
+source "drivers/mxc/vpu/Kconfig"
+
 endmenu
 
 endif
diff --git a/drivers/mxc/Makefile b/drivers/mxc/Makefile
index 1d87f1c..581da1a 100644
--- a/drivers/mxc/Makefile
+++ b/drivers/mxc/Makefile
@@ -1,3 +1,4 @@
 # drivers/mxc/Makefile
 obj-$(CONFIG_MXC_IPU_V3)		+= ipu3/
+obj-$(CONFIG_MXC_VPU)                   += vpu/
 
diff --git a/drivers/mxc/vpu/Kconfig b/drivers/mxc/vpu/Kconfig
new file mode 100644
index 0000000..dada204
--- /dev/null
+++ b/drivers/mxc/vpu/Kconfig
@@ -0,0 +1,22 @@
+#
+# Codec configuration
+#
+
+menu "MXC VPU(Video Processing Unit) support"
+
+config MXC_VPU
+	  tristate "Support for MXC VPU(Video Processing Unit)"
+	  depends on (ARCH_MX3 || ARCH_MX27 || ARCH_MX37 || ARCH_MX5 || ARCH_MX6)
+	  default y
+	---help---
+	  The VPU codec device provides codec function for H.264/MPEG4/H.263,
+	  as well as MPEG2/VC-1/DivX on some platforms.
+
+config MXC_VPU_DEBUG
+	bool "MXC VPU debugging"
+	depends on MXC_VPU != n
+	help
+	  This is an option for the developers; most people should
+	  say N here.  This enables MXC VPU driver debugging.
+
+endmenu
diff --git a/drivers/mxc/vpu/Makefile b/drivers/mxc/vpu/Makefile
new file mode 100644
index 0000000..1a821f4
--- /dev/null
+++ b/drivers/mxc/vpu/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for the VPU drivers.
+#
+
+obj-$(CONFIG_MXC_VPU)                  += mxc_vpu.o
+
+ifeq ($(CONFIG_MXC_VPU_DEBUG),y)
+EXTRA_CFLAGS += -DDEBUG
+endif
diff --git a/drivers/mxc/vpu/mxc_vpu.c b/drivers/mxc/vpu/mxc_vpu.c
new file mode 100644
index 0000000..7bfe9bc
--- /dev/null
+++ b/drivers/mxc/vpu/mxc_vpu.c
@@ -0,0 +1,895 @@
+/*
+ * Copyright 2006-2011 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/*!
+ * @file mxc_vpu.c
+ *
+ * @brief VPU system initialization and file operation implementation
+ *
+ * @ingroup VPU
+ */
+
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/stat.h>
+#include <linux/platform_device.h>
+#include <linux/kdev_t.h>
+#include <linux/dma-mapping.h>
+#include <linux/wait.h>
+#include <linux/list.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/fsl_devices.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/sched.h>
+#include <linux/vmalloc.h>
+#include <asm/sizes.h>
+#include <mach/clock.h>
+#include <mach/hardware.h>
+#include <mach/iram.h>
+
+#include <mach/mxc_vpu.h>
+
+struct vpu_priv {
+	struct fasync_struct *async_queue;
+	struct work_struct work;
+	struct workqueue_struct *workqueue;
+};
+
+/* To track the allocated memory buffer */
+typedef struct memalloc_record {
+	struct list_head list;
+	struct vpu_mem_desc mem;
+} memalloc_record;
+
+struct iram_setting {
+	u32 start;
+	u32 end;
+};
+
+static DEFINE_SPINLOCK(vpu_lock);
+static LIST_HEAD(head);
+
+static int vpu_major;
+static int vpu_clk_usercount;
+static struct class *vpu_class;
+static struct vpu_priv vpu_data;
+static u8 open_count;
+static struct clk *vpu_clk;
+static struct vpu_mem_desc bitwork_mem = { 0 };
+static struct vpu_mem_desc pic_para_mem = { 0 };
+static struct vpu_mem_desc user_data_mem = { 0 };
+static struct vpu_mem_desc share_mem = { 0 };
+static struct vpu_mem_desc vshare_mem = { 0 };
+
+static void __iomem *vpu_base;
+static int vpu_ipi_irq;
+static u32 phy_vpu_base_addr;
+static struct mxc_vpu_platform_data *vpu_plat;
+
+/* IRAM setting */
+static struct iram_setting iram;
+
+/* implement the blocking ioctl */
+static int codec_done;
+static wait_queue_head_t vpu_queue;
+
+static u32 workctrl_regsave[6];
+static u32 rd_ptr_regsave[4];
+static u32 wr_ptr_regsave[4];
+static u32 dis_flag_regsave[4];
+
+#ifdef CONFIG_SOC_IMX6Q
+#define MXC_VPU_HAS_JPU
+#endif
+
+#ifdef MXC_VPU_HAS_JPU
+static int vpu_jpu_irq;
+#endif
+
+#define	READ_REG(x)		__raw_readl(vpu_base + x)
+#define	WRITE_REG(val, x)	__raw_writel(val, vpu_base + x)
+#define	SAVE_WORK_REGS	do {					\
+	int i;							\
+	for (i = 0; i < ARRAY_SIZE(workctrl_regsave)/2; i++)	\
+		workctrl_regsave[i] = READ_REG(BIT_WORK_CTRL_BUF_REG(i));\
+} while (0)
+#define	RESTORE_WORK_REGS	do {				\
+	int i;							\
+	for (i = 0; i < ARRAY_SIZE(workctrl_regsave)/2; i++)	\
+		WRITE_REG(workctrl_regsave[i], BIT_WORK_CTRL_BUF_REG(i));\
+} while (0)
+#define	SAVE_CTRL_REGS	do {					\
+	int i;							\
+	for (i = ARRAY_SIZE(workctrl_regsave)/2;		\
+			i < ARRAY_SIZE(workctrl_regsave); i++)		\
+		workctrl_regsave[i] = READ_REG(BIT_WORK_CTRL_BUF_REG(i));\
+} while (0)
+#define	RESTORE_CTRL_REGS	do {				\
+	int i;							\
+	for (i = ARRAY_SIZE(workctrl_regsave)/2;		\
+			i < ARRAY_SIZE(workctrl_regsave); i++)		\
+		WRITE_REG(workctrl_regsave[i], BIT_WORK_CTRL_BUF_REG(i));\
+} while (0)
+#define	SAVE_RDWR_PTR_REGS	do {					\
+	int i;								\
+	for (i = 0; i < ARRAY_SIZE(rd_ptr_regsave); i++)		\
+		rd_ptr_regsave[i] = READ_REG(BIT_RD_PTR_REG(i));	\
+	for (i = 0; i < ARRAY_SIZE(wr_ptr_regsave); i++)		\
+		wr_ptr_regsave[i] = READ_REG(BIT_WR_PTR_REG(i));	\
+} while (0)
+#define	RESTORE_RDWR_PTR_REGS	do {					\
+	int i;								\
+	for (i = 0; i < ARRAY_SIZE(rd_ptr_regsave); i++)		\
+		WRITE_REG(rd_ptr_regsave[i], BIT_RD_PTR_REG(i));	\
+	for (i = 0; i < ARRAY_SIZE(wr_ptr_regsave); i++)		\
+		WRITE_REG(wr_ptr_regsave[i], BIT_WR_PTR_REG(i));	\
+} while (0)
+#define	SAVE_DIS_FLAG_REGS	do {					\
+	int i;								\
+	for (i = 0; i < ARRAY_SIZE(dis_flag_regsave); i++)		\
+		dis_flag_regsave[i] = READ_REG(BIT_FRM_DIS_FLG_REG(i));	\
+} while (0)
+#define	RESTORE_DIS_FLAG_REGS	do {					\
+	int i;								\
+	for (i = 0; i < ARRAY_SIZE(dis_flag_regsave); i++)		\
+		WRITE_REG(dis_flag_regsave[i], BIT_FRM_DIS_FLG_REG(i));	\
+} while (0)
+
+/*!
+ * Private function to alloc dma buffer
+ * @return status  0 success.
+ */
+static int vpu_alloc_dma_buffer(struct vpu_mem_desc *mem)
+{
+	mem->cpu_addr = (unsigned long)
+	    dma_alloc_coherent(NULL, PAGE_ALIGN(mem->size),
+			       (dma_addr_t *) (&mem->phy_addr),
+			       GFP_DMA | GFP_KERNEL);
+	pr_debug("[ALLOC] mem alloc cpu_addr = 0x%x\n", mem->cpu_addr);
+	if ((void *)(mem->cpu_addr) == NULL) {
+		printk(KERN_ERR "Physical memory allocation error!\n");
+		return -1;
+	}
+	return 0;
+}
+
+/*!
+ * Private function to free dma buffer
+ */
+static void vpu_free_dma_buffer(struct vpu_mem_desc *mem)
+{
+	if (mem->cpu_addr != 0) {
+		dma_free_coherent(0, PAGE_ALIGN(mem->size),
+				  (void *)mem->cpu_addr, mem->phy_addr);
+	}
+}
+
+/*!
+ * Private function to free buffers
+ * @return status  0 success.
+ */
+static int vpu_free_buffers(void)
+{
+	struct memalloc_record *rec, *n;
+	struct vpu_mem_desc mem;
+
+	list_for_each_entry_safe(rec, n, &head, list) {
+		mem = rec->mem;
+		if (mem.cpu_addr != 0) {
+			vpu_free_dma_buffer(&mem);
+			pr_debug("[FREE] freed paddr=0x%08X\n", mem.phy_addr);
+			/* delete from list */
+			list_del(&rec->list);
+			kfree(rec);
+		}
+	}
+
+	return 0;
+}
+
+static inline void vpu_worker_callback(struct work_struct *w)
+{
+	struct vpu_priv *dev = container_of(w, struct vpu_priv,
+				work);
+
+	if (dev->async_queue)
+		kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
+
+	codec_done = 1;
+	wake_up_interruptible(&vpu_queue);
+
+	/*
+	 * Clock is gated on when dec/enc started, gate it off when
+	 * interrupt is received.
+	 */
+	clk_disable(vpu_clk);
+}
+
+/*!
+ * @brief vpu interrupt handler
+ */
+static irqreturn_t vpu_ipi_irq_handler(int irq, void *dev_id)
+{
+	struct vpu_priv *dev = dev_id;
+
+	READ_REG(BIT_INT_STATUS);
+	WRITE_REG(0x1, BIT_INT_CLEAR);
+
+	queue_work(dev->workqueue, &dev->work);
+
+	return IRQ_HANDLED;
+}
+
+/*!
+ * @brief vpu jpu interrupt handler
+ */
+#ifdef MXC_VPU_HAS_JPU
+static irqreturn_t vpu_jpu_irq_handler(int irq, void *dev_id)
+{
+	struct vpu_priv *dev = dev_id;
+
+	WRITE_REG(0, MJPEG_PIC_STATUS_REG);
+
+	queue_work(dev->workqueue, &dev->work);
+
+	return IRQ_HANDLED;
+}
+#endif
+
+/*!
+ * @brief open function for vpu file operation
+ *
+ * @return  0 on success or negative error code on error
+ */
+static int vpu_open(struct inode *inode, struct file *filp)
+{
+	spin_lock(&vpu_lock);
+	open_count++;
+	filp->private_data = (void *)(&vpu_data);
+	spin_unlock(&vpu_lock);
+	return 0;
+}
+
+/*!
+ * @brief IO ctrl function for vpu file operation
+ * @param cmd IO ctrl command
+ * @return  0 on success or negative error code on error
+ */
+static long vpu_ioctl(struct file *filp, u_int cmd,
+		     u_long arg)
+{
+	int ret = 0;
+
+	switch (cmd) {
+	case VPU_IOC_PHYMEM_ALLOC:
+		{
+			struct memalloc_record *rec;
+
+			rec = kzalloc(sizeof(*rec), GFP_KERNEL);
+			if (!rec)
+				return -ENOMEM;
+
+			ret = copy_from_user(&(rec->mem),
+					     (struct vpu_mem_desc *)arg,
+					     sizeof(struct vpu_mem_desc));
+			if (ret) {
+				kfree(rec);
+				return -EFAULT;
+			}
+
+			pr_debug("[ALLOC] mem alloc size = 0x%x\n",
+				 rec->mem.size);
+
+			ret = vpu_alloc_dma_buffer(&(rec->mem));
+			if (ret == -1) {
+				kfree(rec);
+				printk(KERN_ERR
+				       "Physical memory allocation error!\n");
+				break;
+			}
+			ret = copy_to_user((void __user *)arg, &(rec->mem),
+					   sizeof(struct vpu_mem_desc));
+			if (ret) {
+				kfree(rec);
+				ret = -EFAULT;
+				break;
+			}
+
+			spin_lock(&vpu_lock);
+			list_add(&rec->list, &head);
+			spin_unlock(&vpu_lock);
+
+			break;
+		}
+	case VPU_IOC_PHYMEM_FREE:
+		{
+			struct memalloc_record *rec, *n;
+			struct vpu_mem_desc vpu_mem;
+
+			ret = copy_from_user(&vpu_mem,
+					     (struct vpu_mem_desc *)arg,
+					     sizeof(struct vpu_mem_desc));
+			if (ret)
+				return -EACCES;
+
+			pr_debug("[FREE] mem freed cpu_addr = 0x%x\n",
+				 vpu_mem.cpu_addr);
+			if ((void *)vpu_mem.cpu_addr != NULL) {
+				vpu_free_dma_buffer(&vpu_mem);
+			}
+
+			spin_lock(&vpu_lock);
+			list_for_each_entry_safe(rec, n, &head, list) {
+				if (rec->mem.cpu_addr == vpu_mem.cpu_addr) {
+					/* delete from list */
+					list_del(&rec->list);
+					kfree(rec);
+					break;
+				}
+			}
+			spin_unlock(&vpu_lock);
+
+			break;
+		}
+	case VPU_IOC_WAIT4INT:
+		{
+			u_long timeout = (u_long) arg;
+			if (!wait_event_interruptible_timeout
+			    (vpu_queue, codec_done != 0,
+			     msecs_to_jiffies(timeout))) {
+				printk(KERN_WARNING "VPU blocking: timeout.\n");
+				ret = -ETIME;
+			} else if (signal_pending(current)) {
+				printk(KERN_WARNING
+				       "VPU interrupt received.\n");
+				ret = -ERESTARTSYS;
+			} else
+				codec_done = 0;
+			break;
+		}
+	case VPU_IOC_IRAM_SETTING:
+		{
+			ret = copy_to_user((void __user *)arg, &iram,
+					   sizeof(struct iram_setting));
+			if (ret)
+				ret = -EFAULT;
+
+			break;
+		}
+	case VPU_IOC_CLKGATE_SETTING:
+		{
+			u32 clkgate_en;
+
+			if (get_user(clkgate_en, (u32 __user *) arg))
+				return -EFAULT;
+
+			if (clkgate_en) {
+				clk_enable(vpu_clk);
+			} else {
+				clk_disable(vpu_clk);
+			}
+
+			break;
+		}
+	case VPU_IOC_GET_SHARE_MEM:
+		{
+			spin_lock(&vpu_lock);
+			if (share_mem.cpu_addr != 0) {
+				ret = copy_to_user((void __user *)arg,
+						   &share_mem,
+						   sizeof(struct vpu_mem_desc));
+				spin_unlock(&vpu_lock);
+				break;
+			} else {
+				if (copy_from_user(&share_mem,
+						   (struct vpu_mem_desc *)arg,
+						 sizeof(struct vpu_mem_desc))) {
+					spin_unlock(&vpu_lock);
+					return -EFAULT;
+				}
+				if (vpu_alloc_dma_buffer(&share_mem) == -1)
+					ret = -EFAULT;
+				else {
+					if (copy_to_user((void __user *)arg,
+							 &share_mem,
+							 sizeof(struct
+								vpu_mem_desc)))
+						ret = -EFAULT;
+				}
+			}
+			spin_unlock(&vpu_lock);
+			break;
+		}
+	case VPU_IOC_REQ_VSHARE_MEM:
+		{
+			spin_lock(&vpu_lock);
+			if (vshare_mem.cpu_addr != 0) {
+				ret = copy_to_user((void __user *)arg,
+						   &vshare_mem,
+						   sizeof(struct vpu_mem_desc));
+				spin_unlock(&vpu_lock);
+				break;
+			} else {
+				if (copy_from_user(&vshare_mem,
+						   (struct vpu_mem_desc *)arg,
+						   sizeof(struct
+							  vpu_mem_desc))) {
+					spin_unlock(&vpu_lock);
+					return -EFAULT;
+				}
+				/* vmalloc shared memory if not allocated */
+				if (!vshare_mem.cpu_addr)
+					vshare_mem.cpu_addr =
+					    (unsigned long)
+					    vmalloc_user(vshare_mem.size);
+				if (copy_to_user
+				     ((void __user *)arg, &vshare_mem,
+				     sizeof(struct vpu_mem_desc)))
+					ret = -EFAULT;
+			}
+			spin_unlock(&vpu_lock);
+			break;
+		}
+	case VPU_IOC_GET_WORK_ADDR:
+		{
+			if (bitwork_mem.cpu_addr != 0) {
+				ret =
+				    copy_to_user((void __user *)arg,
+						 &bitwork_mem,
+						 sizeof(struct vpu_mem_desc));
+				break;
+			} else {
+				if (copy_from_user(&bitwork_mem,
+						   (struct vpu_mem_desc *)arg,
+						   sizeof(struct vpu_mem_desc)))
+					return -EFAULT;
+
+				if (vpu_alloc_dma_buffer(&bitwork_mem) == -1)
+					ret = -EFAULT;
+				else if (copy_to_user((void __user *)arg,
+						      &bitwork_mem,
+						      sizeof(struct
+							     vpu_mem_desc)))
+					ret = -EFAULT;
+			}
+			break;
+		}
+	case VPU_IOC_SYS_SW_RESET:
+		{
+			if (vpu_plat->reset)
+				vpu_plat->reset();
+
+			break;
+		}
+	case VPU_IOC_REG_DUMP:
+		break;
+	case VPU_IOC_PHYMEM_DUMP:
+		break;
+	default:
+		{
+			printk(KERN_ERR "No such IOCTL, cmd is %d\n", cmd);
+			break;
+		}
+	}
+	return ret;
+}
+
+/*!
+ * @brief Release function for vpu file operation
+ * @return  0 on success or negative error code on error
+ */
+static int vpu_release(struct inode *inode, struct file *filp)
+{
+	spin_lock(&vpu_lock);
+	if (open_count > 0 && !(--open_count)) {
+		vpu_free_buffers();
+
+		/* Free shared memory when vpu device is idle */
+		vpu_free_dma_buffer(&share_mem);
+		share_mem.cpu_addr = 0;
+		vfree((void *)vshare_mem.cpu_addr);
+		vshare_mem.cpu_addr = 0;
+	}
+	spin_unlock(&vpu_lock);
+
+	return 0;
+}
+
+/*!
+ * @brief fasync function for vpu file operation
+ * @return  0 on success or negative error code on error
+ */
+static int vpu_fasync(int fd, struct file *filp, int mode)
+{
+	struct vpu_priv *dev = (struct vpu_priv *)filp->private_data;
+	return fasync_helper(fd, filp, mode, &dev->async_queue);
+}
+
+/*!
+ * @brief memory map function of harware registers for vpu file operation
+ * @return  0 on success or negative error code on error
+ */
+static int vpu_map_hwregs(struct file *fp, struct vm_area_struct *vm)
+{
+	unsigned long pfn;
+
+	vm->vm_flags |= VM_IO | VM_RESERVED;
+	vm->vm_page_prot = pgprot_noncached(vm->vm_page_prot);
+	pfn = phy_vpu_base_addr >> PAGE_SHIFT;
+	pr_debug("size=0x%x,  page no.=0x%x\n",
+		 (int)(vm->vm_end - vm->vm_start), (int)pfn);
+	return remap_pfn_range(vm, vm->vm_start, pfn, vm->vm_end - vm->vm_start,
+			       vm->vm_page_prot) ? -EAGAIN : 0;
+}
+
+/*!
+ * @brief memory map function of memory for vpu file operation
+ * @return  0 on success or negative error code on error
+ */
+static int vpu_map_dma_mem(struct file *fp, struct vm_area_struct *vm)
+{
+	int request_size;
+	request_size = vm->vm_end - vm->vm_start;
+
+	pr_debug(" start=0x%x, pgoff=0x%x, size=0x%x\n",
+		 (unsigned int)(vm->vm_start), (unsigned int)(vm->vm_pgoff),
+		 request_size);
+
+	vm->vm_flags |= VM_IO | VM_RESERVED;
+	vm->vm_page_prot = pgprot_writecombine(vm->vm_page_prot);
+
+	return remap_pfn_range(vm, vm->vm_start, vm->vm_pgoff,
+			       request_size, vm->vm_page_prot) ? -EAGAIN : 0;
+
+}
+
+/* !
+ * @brief memory map function of vmalloced share memory
+ * @return  0 on success or negative error code on error
+ */
+static int vpu_map_vshare_mem(struct file *fp, struct vm_area_struct *vm)
+{
+	int ret = -EINVAL;
+
+	spin_lock(&vpu_lock);
+	ret = remap_vmalloc_range(vm, (void *)(vm->vm_pgoff << PAGE_SHIFT), 0);
+	vm->vm_flags |= VM_IO;
+	spin_unlock(&vpu_lock);
+
+	return ret;
+}
+/*!
+ * @brief memory map interface for vpu file operation
+ * @return  0 on success or negative error code on error
+ */
+static int vpu_mmap(struct file *fp, struct vm_area_struct *vm)
+{
+	unsigned long offset;
+
+	offset = vshare_mem.cpu_addr >> PAGE_SHIFT;
+
+	if (vm->vm_pgoff && (vm->vm_pgoff == offset))
+		return vpu_map_vshare_mem(fp, vm);
+	else if (vm->vm_pgoff)
+		return vpu_map_dma_mem(fp, vm);
+	else
+		return vpu_map_hwregs(fp, vm);
+}
+
+struct file_operations vpu_fops = {
+	.owner = THIS_MODULE,
+	.open = vpu_open,
+	.unlocked_ioctl = vpu_ioctl,
+	.release = vpu_release,
+	.fasync = vpu_fasync,
+	.mmap = vpu_mmap,
+};
+
+/*!
+ * This function is called by the driver framework to initialize the vpu device.
+ * @param   dev The device structure for the vpu passed in by the framework.
+ * @return   0 on success or negative error code on error
+ */
+static int vpu_dev_probe(struct platform_device *pdev)
+{
+	int err = 0;
+	struct device *temp_class;
+	struct resource *res;
+	unsigned long addr = 0;
+
+	vpu_plat = pdev->dev.platform_data;
+
+	if (vpu_plat && vpu_plat->iram_enable && vpu_plat->iram_size)
+		iram_alloc(vpu_plat->iram_size, &addr);
+	if (addr == 0)
+		iram.start = iram.end = 0;
+	else {
+		iram.start = addr;
+		iram.end = addr +  vpu_plat->iram_size - 1;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		printk(KERN_ERR "vpu: unable to get vpu base addr\n");
+		return -ENODEV;
+	}
+	phy_vpu_base_addr = res->start;
+	vpu_base = ioremap(res->start, res->end - res->start);
+
+	vpu_major = register_chrdev(vpu_major, "mxc_vpu", &vpu_fops);
+	if (vpu_major < 0) {
+		printk(KERN_ERR "vpu: unable to get a major for VPU\n");
+		err = -EBUSY;
+		goto error;
+	}
+
+	vpu_class = class_create(THIS_MODULE, "mxc_vpu");
+	if (IS_ERR(vpu_class)) {
+		err = PTR_ERR(vpu_class);
+		goto err_out_chrdev;
+	}
+
+	temp_class = device_create(vpu_class, NULL, MKDEV(vpu_major, 0),
+				   NULL, "mxc_vpu");
+	if (IS_ERR(temp_class)) {
+		err = PTR_ERR(temp_class);
+		goto err_out_class;
+	}
+
+	vpu_clk = clk_get(&pdev->dev, "vpu_clk");
+	if (IS_ERR(vpu_clk)) {
+		err = -ENOENT;
+		goto err_out_class;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	if (!res) {
+		printk(KERN_ERR "vpu: unable to get vpu interrupt\n");
+		err = -ENXIO;
+		goto err_out_class;
+	}
+	vpu_ipi_irq = res->start;
+
+	err = request_irq(vpu_ipi_irq, vpu_ipi_irq_handler, 0, "VPU_CODEC_IRQ",
+			  (void *)(&vpu_data));
+	if (err)
+		goto err_out_class;
+
+#ifdef MXC_VPU_HAS_JPU
+	res = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
+	if (!res) {
+		printk(KERN_ERR "vpu: unable to get vpu jpu interrupt\n");
+		err = -ENXIO;
+		free_irq(vpu_ipi_irq, &vpu_data);
+		goto err_out_class;
+	}
+	vpu_jpu_irq = res->start;
+	err = request_irq(vpu_jpu_irq, vpu_jpu_irq_handler, 0, "VPU_JPG_IRQ",
+			  (void *)(&vpu_data));
+	if (err) {
+		free_irq(vpu_ipi_irq, &vpu_data);
+		goto err_out_class;
+	}
+#endif
+
+	vpu_data.workqueue = create_workqueue("vpu_wq");
+	INIT_WORK(&vpu_data.work, vpu_worker_callback);
+	printk(KERN_INFO "VPU initialized\n");
+	goto out;
+
+      err_out_class:
+	device_destroy(vpu_class, MKDEV(vpu_major, 0));
+	class_destroy(vpu_class);
+      err_out_chrdev:
+	unregister_chrdev(vpu_major, "mxc_vpu");
+      error:
+	iounmap(vpu_base);
+      out:
+	return err;
+}
+
+static int vpu_dev_remove(struct platform_device *pdev)
+{
+	free_irq(vpu_ipi_irq, &vpu_data);
+#ifdef MXC_VPU_HAS_JPU
+	free_irq(vpu_jpu_irq, &vpu_data);
+#endif
+	cancel_work_sync(&vpu_data.work);
+	flush_workqueue(vpu_data.workqueue);
+	destroy_workqueue(vpu_data.workqueue);
+
+	iounmap(vpu_base);
+	if (vpu_plat && vpu_plat->iram_enable && vpu_plat->iram_size)
+		iram_free(iram.start,  vpu_plat->iram_size);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int vpu_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	int i;
+	unsigned long timeout;
+
+	/* Wait for vpu go to idle state, suspect vpu cannot be changed
+	   to idle state after about 1 sec */
+	if (open_count > 0) {
+		timeout = jiffies + HZ;
+		clk_enable(vpu_clk);
+		while (READ_REG(BIT_BUSY_FLAG)) {
+			msleep(1);
+			if (time_after(jiffies, timeout))
+				goto out;
+		}
+		clk_disable(vpu_clk);
+	}
+
+	/* Make sure clock is disabled before suspend */
+	vpu_clk_usercount = clk_get_usecount(vpu_clk);
+	for (i = 0; i < vpu_clk_usercount; i++)
+		clk_disable(vpu_clk);
+
+	if (cpu_is_mx51()) {
+		clk_enable(vpu_clk);
+		if (bitwork_mem.cpu_addr != 0) {
+			SAVE_WORK_REGS;
+			SAVE_CTRL_REGS;
+			SAVE_RDWR_PTR_REGS;
+			SAVE_DIS_FLAG_REGS;
+
+			WRITE_REG(0x1, BIT_BUSY_FLAG);
+			WRITE_REG(VPU_SLEEP_REG_VALUE, BIT_RUN_COMMAND);
+			while (READ_REG(BIT_BUSY_FLAG))
+				;
+		}
+		clk_disable(vpu_clk);
+	}
+
+	if (cpu_is_mx51() && vpu_plat->pg)
+		vpu_plat->pg(1);
+
+	return 0;
+
+out:
+	clk_disable(vpu_clk);
+	return -EAGAIN;
+
+}
+
+static int vpu_resume(struct platform_device *pdev)
+{
+	int i;
+
+	if (!cpu_is_mx51())
+		goto recover_clk;
+
+	if (vpu_plat->pg)
+		vpu_plat->pg(0);
+
+	clk_enable(vpu_clk);
+	if (bitwork_mem.cpu_addr != 0) {
+		u32 *p = (u32 *) bitwork_mem.cpu_addr;
+		u32 data;
+		u16 data_hi;
+		u16 data_lo;
+
+		RESTORE_WORK_REGS;
+
+		WRITE_REG(0x0, BIT_RESET_CTRL);
+		WRITE_REG(0x0, BIT_CODE_RUN);
+
+		/*
+		 * Re-load boot code, from the codebuffer in external RAM.
+		 * Thankfully, we only need 4096 bytes, same for all platforms.
+		 */
+		for (i = 0; i < 2048; i += 4) {
+			data = p[(i / 2) + 1];
+			data_hi = (data >> 16) & 0xFFFF;
+			data_lo = data & 0xFFFF;
+			WRITE_REG((i << 16) | data_hi, BIT_CODE_DOWN);
+			WRITE_REG(((i + 1) << 16) | data_lo,
+				  BIT_CODE_DOWN);
+
+			data = p[i / 2];
+			data_hi = (data >> 16) & 0xFFFF;
+			data_lo = data & 0xFFFF;
+			WRITE_REG(((i + 2) << 16) | data_hi,
+				  BIT_CODE_DOWN);
+			WRITE_REG(((i + 3) << 16) | data_lo,
+				  BIT_CODE_DOWN);
+		}
+
+		RESTORE_CTRL_REGS;
+
+		WRITE_REG(BITVAL_PIC_RUN, BIT_INT_ENABLE);
+
+		WRITE_REG(0x1, BIT_BUSY_FLAG);
+		WRITE_REG(0x1, BIT_CODE_RUN);
+		while (READ_REG(BIT_BUSY_FLAG))
+			;
+
+		RESTORE_RDWR_PTR_REGS;
+		RESTORE_DIS_FLAG_REGS;
+
+		WRITE_REG(0x1, BIT_BUSY_FLAG);
+		WRITE_REG(VPU_WAKE_REG_VALUE, BIT_RUN_COMMAND);
+		while (READ_REG(BIT_BUSY_FLAG))
+			;
+	}
+	clk_disable(vpu_clk);
+
+recover_clk:
+	/* Recover vpu clock */
+	for (i = 0; i < vpu_clk_usercount; i++)
+		clk_enable(vpu_clk);
+
+	return 0;
+}
+#else
+#define	vpu_suspend	NULL
+#define	vpu_resume	NULL
+#endif				/* !CONFIG_PM */
+
+/*! Driver definition
+ *
+ */
+static struct platform_driver mxcvpu_driver = {
+	.driver = {
+		   .name = "mxc_vpu",
+		   },
+	.probe = vpu_dev_probe,
+	.remove = vpu_dev_remove,
+	.suspend = vpu_suspend,
+	.resume = vpu_resume,
+};
+
+static int __init vpu_init(void)
+{
+	int ret = platform_driver_register(&mxcvpu_driver);
+
+	init_waitqueue_head(&vpu_queue);
+
+	return ret;
+}
+
+static void __exit vpu_exit(void)
+{
+	if (vpu_major > 0) {
+		device_destroy(vpu_class, MKDEV(vpu_major, 0));
+		class_destroy(vpu_class);
+		unregister_chrdev(vpu_major, "mxc_vpu");
+		vpu_major = 0;
+	}
+
+	vpu_free_dma_buffer(&bitwork_mem);
+	vpu_free_dma_buffer(&pic_para_mem);
+	vpu_free_dma_buffer(&user_data_mem);
+
+	clk_put(vpu_clk);
+
+	platform_driver_unregister(&mxcvpu_driver);
+	return;
+}
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Linux VPU driver for Freescale i.MX/MXC");
+MODULE_LICENSE("GPL");
+
+module_init(vpu_init);
+module_exit(vpu_exit);