[PATCH] pxa: add 2d graphics driver

Haojian Zhuang haojian.zhuang at marvell.com
Sun Oct 18 13:43:42 EDT 2009


PXA3xx series provides 2D graphics function. It supports line draw, chroma
key, scale, alpha blending, and so on.

Signed-off-by: Haojian Zhuang <haojian.zhuang at marvell.com>
---
 arch/arm/mach-pxa/include/mach/pxa3xx-gcu.h |   69 ++
 drivers/char/Kconfig                        |    6 +
 drivers/char/Makefile                       |    1 +
 drivers/char/pxa3xx-gcu.c                   | 1118 +++++++++++++++++++++++++++
 4 files changed, 1194 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/mach-pxa/include/mach/pxa3xx-gcu.h
 create mode 100644 drivers/char/pxa3xx-gcu.c

diff --git a/arch/arm/mach-pxa/include/mach/pxa3xx-gcu.h
b/arch/arm/mach-pxa/include/mach/pxa3xx-gcu.h
new file mode 100644
index 0000000..14e6b0a
--- /dev/null
+++ b/arch/arm/mach-pxa/include/mach/pxa3xx-gcu.h
@@ -0,0 +1,69 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _PXA3xx_GCU_H
+#define _PXA3xx_GCU_H
+
+#define MAX_DEVICE_GMEM_SIZE		(16*1024*1024)
+#define MAX_CONTEXT_GMEM_SIZE		(16*1024*1024)
+
+#define GCU_RINGBUF_SIZE		(16384)
+#define GCU_SCRATCHREG_NR		(8)
+
+#include <asm/ioctl.h>
+
+#define PXA3xx_GCU_IO_SUBMIT		_IOW('2',  1, struct iovec *)
+#define PXA3xx_GCU_IO_SYNC		_IOW('2',  2, int)
+
+#define PXA3xx_GCU_IO_REQUEST_MEM	_IOW('2', 10, struct
pxa3xx_gcu_mem_request *)
+#define PXA3xx_GCU_IO_RELEASE_MEM	_IOW('2', 11, unsigned long)
+#define PXA3xx_GCU_IO_FLUSH_MEM		_IOW('2', 12, unsigned long)
+
+#define PXA3xx_GCU_IO_GET_BUS_ADDR	_IOW('2', 20, unsigned long)
+
+/* #define PXA3xx_GCU_IO_QUERY_GCU	_IOR('2', 20, struct m2d_gcu_stat *) */
+
+#define PXA3xx_GCU_GRAPHICS_MEM	0
+#define PXA3xx_GCU_FRAME_BUFFER	1
+#define PXA3xx_GCU_REGISTERS		2
+#define PXA3xx_GCU_RING_BUFFER		3
+
+#define PXA3xx_GCU_ATTR_COHERENT	0x00
+#define PXA3xx_GCU_ATTR_WRITECOMBINE	0x10
+#define PXA3xx_GCU_ATTR_CACHEABLE	0x20
+
+#define PXA3xx_GCU_MEM_REQ_TYPE(f)		(f & 0x0f)
+#define PXA3xx_GCU_MEM_REQ_ATTR(f)		(f & 0xf0)
+
+struct pxa3xx_gcu_mem_req {
+	unsigned int	req_type;
+	unsigned int	req_size;
+	unsigned long	phys_addr;
+	unsigned long	mmap_addr;
+	unsigned long	mmap_size;
+};
+
+#define PXA3xx_GCU_SUBMIT_MODE_NDELAY	(1 << 0)
+#define PXA3xx_GCU_SUBMIT_MODE_SYNC	(1 << 1)
+
+struct pxa3xx_gcu_submit_req {
+	unsigned int	mode;
+	void		*base;
+	size_t		len;
+};
+
+#endif
+
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 08a6f50..a7c468c 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -431,6 +431,12 @@ config SGI_MBCS
          If you have an SGI Altix with an attached SABrick
          say Y or M here, otherwise say N.

+config PXA3xx_GCU
+	bool "PXA3xx Processor 2D Graphics Controller Driver"
+	depends on PXA3xx
+	help
+	  Enable graphics support on PXA3xx Processor series.
+
 source "drivers/serial/Kconfig"

 config UNIX98_PTYS
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index 19a79dd..b0beced 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -108,6 +108,7 @@ obj-$(CONFIG_HANGCHECK_TIMER)	+= hangcheck-timer.o
 obj-$(CONFIG_TCG_TPM)		+= tpm/

 obj-$(CONFIG_PS3_FLASH)		+= ps3flash.o
+obj-$(CONFIG_PXA3xx_GCU)	+= pxa3xx-gcu.o

 obj-$(CONFIG_JS_RTC)		+= js-rtc.o
 js-rtc-y = rtc.o
diff --git a/drivers/char/pxa3xx-gcu.c b/drivers/char/pxa3xx-gcu.c
new file mode 100644
index 0000000..81c7f11
--- /dev/null
+++ b/drivers/char/pxa3xx-gcu.c
@@ -0,0 +1,1118 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+ *(C) Copyright 2006 Marvell International Ltd.
+ * All Rights Reserved
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/fs.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/completion.h>
+#include <linux/miscdevice.h>
+#include <linux/dma-mapping.h>
+
+#include <asm/uaccess.h>
+#include <asm/dma-mapping.h>
+#include <asm/cacheflush.h>
+
+#include <mach/pxa3xx-gcu.h>
+
+enum {
+	/* Miscellaneous Control and Interrupt Information */
+	GCCR 		= 0x000, /* Configuration Register */
+	GCISCR		= 0x004, /* Interrupt Status Control Register */
+	GCIECR		= 0x008, /* Interrupt Enable Control Register */
+	GCNOPID		= 0x00C, /* NOP ID From Instruction Stream Register */
+	GCALPHASET	= 0x010, /* Default Alpha value Control Register */
+	GCTSET		= 0x014, /* Default Transparecy Value Control Register */
+	GCFLAGS		= 0x018, /* ALU Operations Flags Status Control Register */
+
+	
+	/* Ring Buffer Information */
+	GCRBBR		= 0x020, /* Ring Buffer Base Address Register */
+	GCRBLR		= 0x024, /* Ring Buffer Length Register */
+	GCRBHR		= 0x028, /* Ring Buffer Head Register */
+	GCRBTR		= 0x02C, /* Ring Buffer Tail Register */
+	GCRBEXHR	= 0x030, /* Ring Buffer Execution Head Register */
+
+	/* Batch Buffer Information */
+	GCBBBR		= 0x040, /* Batch Buffer Base Address Register */
+	GCBBHR		= 0x044, /* Batch Buffer Head Register */
+	GCBBEXHR	= 0x048, /* Batch Buffer Execution Head Register */
+
+	/* Destination 0 Information */
+	GCD0BR		= 0x060, /* Destination 0 Base Address Register */
+	GCD0STP		= 0x064, /* Destination 0 Step Size Register */
+	GCD0STR		= 0x068, /* Destination 0 Stride Size Register */
+	GCD0PF		= 0x06C, /* Destination 0 Pixel Type Register */
+
+	/* Destination 1 Information */
+	GCD1BR		= 0x070, /* Destination 1 Base Address Register */
+	GCD1STP		= 0x074, /* Destination 1 Step Size Register */
+	GCD1STR		= 0x078, /* Destination 1 Stride Size Register */
+	GCD1PF		= 0x07C, /* Destination 1 Pixel Type Register */
+
+	/* Destination 2 Information */
+	GCD2BR		= 0x080, /* Destination 2 Base Address Register */
+	GCD2STP		= 0x084, /* Destination 2 Step Size Register */
+	GCD2STR		= 0x088, /* Destination 2 Stride Size Register */
+	GCD2PF		= 0x08C, /* Destination 2 Pixel Type Register */
+
+	/* Source 0 Information */
+	GCS0BR		= 0x0E0, /* Source 0 Base Address Register */
+	GCS0STP		= 0x0E4, /* Source 0 Step Size Register */
+	GCS0STR		= 0x0E8, /* Source 0 Stride Size Register */
+	GCS0PF		= 0x0EC, /* Source 0 Pixel Type Register */
+
+	/* Source 1 Information */
+	GCS1BR		= 0x0F0, /* Source 1 Base Address Register */
+	GCS1STP		= 0x0F4, /* Source 1 Step Size Register */
+	GCS1STR		= 0x0F8, /* Source 1 Stride Size Register */
+	GCS1PF		= 0x0FC, /* Source 1 Pixel Type Register */
+
+	/* Pixel ALU Scratch Registers */
+	GCSC0WD0	= 0x160, /* Pixel ALU Scratch Register 0 Word 0 */
+	GCSC0WD1	= 0x164, /* Pixel ALU Scratch Register 0 Word 1 */
+	GCSC1WD0	= 0x168, /* Pixel ALU Scratch Register 1 Word 0 */
+	GCSC1WD1	= 0x16C, /* Pixel ALU Scratch Register 1 Word 1 */
+	GCSC2WD0	= 0x170, /* Pixel ALU Scratch Register 2 Word 0 */
+	GCSC2WD1	= 0x174, /* Pixel ALU Scratch Register 2 Word 1 */
+	GCSC3WD0	= 0x178, /* Pixel ALU Scratch Register 3 Word 0 */
+	GCSC3WD1	= 0x17C, /* Pixel ALU Scratch Register 3 Word 1 */
+	GCSC4WD0	= 0x180, /* Pixel ALU Scratch Register 4 Word 0 */
+	GCSC4WD1	= 0x184, /* Pixel ALU Scratch Register 5 Word 1 */
+	GCSC5WD0	= 0x188, /* Pixel ALU Scratch Register 5 Word 0 */
+	GCSC5WD1	= 0x18C, /* Pixel ALU Scratch Register 5 Word 1 */
+	GCSC6WD0	= 0x190, /* Pixel ALU Scratch Register 6 Word 0 */
+	GCSC6WD1	= 0x194, /* Pixel ALU Scratch Register 6 Word 1 */
+	GCSC7WD0	= 0x198, /* Pixel ALU Scratch Register 7 Word 0 */
+	GCSC7WD1	= 0x19C, /* Pixel ALU Scratch Register 7 Word 1 */
+
+	/* Abort Bad Address Storage Registers */
+	GCCABADDR	= 0x1E0, /* Illegal Access Bad Address Register */
+	GCTABADDR	= 0x1E4, /* Target Abort Access Register */
+	GCMABADDR	= 0x1E8, /* Master Abort Access Register */
+
+	GCU_MAX_REG	= 0x200,
+};
+
+#define GCCR_STOP		(1 << 4)
+#define GCCR_ABORT		(1 << 6)
+#define GCCR_BP_RST		(1 << 8)
+#define GCCR_SYNC_CLR		(1 << 9)
+#define GCCR_MASK		(0x000007FF)
+
+#define GCIECR_EOB_INTEN	(1 << 0)
+#define GCIECR_IN_INTEN		(1 << 1)
+#define GCIECR_BF_INTEN		(1 << 2)
+#define GCIECR_IOP_INTEN	(1 << 3)
+#define GCIECR_IIN_INTEN	(1 << 4)
+#define GCIECR_EEOB_INTEN	(1 << 5)
+#define GCIECR_PF_INTEN		(1 << 6)
+#define GCIECR_STOP_INTEN	(1 << 7)
+#define GCIECR_FLAG_INTEN	(1 << 8)
+#define GCIECR_MASK		(0x000001FF)
+
+#define GCISCR_EOB_INTST	(1 << 0)
+#define GCISCR_IN_INTST		(1 << 1)
+#define GCISCR_BF_INTST		(1 << 2)
+#define GCISCR_IOP_INTST	(1 << 3)
+#define GCISCR_IIN_INTST	(1 << 4)
+#define GCISCR_EEOB_INTST	(1 << 5)
+#define GCISCR_PF_INTST		(1 << 6)
+#define GCISCR_STOP_INTST	(1 << 7)
+#define GCISCR_FLAG_INTST	(1 << 8)
+#define GCISCR_MASK		(0x000001FF)
+
+#define GCU_TIMEOUT		(10)	/* jiffies unit */
+
+struct pxa3xx_gcu_context;
+
+struct pxa3xx_gcu_info {
+	struct device		*dev;
+	struct platform_device	*pdev;
+	unsigned int		irq;
+	void __iomem		*mmio_base;
+	struct completion	done;
+	struct mutex		lock;
+
+	struct list_head	list;		/* context list */
+	unsigned long		count;		/* counter of context */
+	struct pxa3xx_gcu_context	*last;
+
+	struct semaphore	submit_sem;
+
+	unsigned long		*ring_addr;
+	unsigned long		ring_size;
+	unsigned long		ring_addr_dma;
+	unsigned long		ring_tail_dma;
+
+	unsigned long		total_gmem_size;
+};
+
+struct pxa3xx_gcu_gmem {
+	struct pxa3xx_gcu_info	*info;
+	struct list_head	list;
+	atomic_t		gmem_count;
+	struct page		*gmem_pages;
+	size_t			gmem_size;
+	unsigned long		gmem_virt_addr;
+	unsigned long		gmem_phys_addr;
+	unsigned long		gmem_uaddr;
+	unsigned long		gmem_type;
+};
+
+struct pxa3xx_gcu_buf {
+	unsigned long		base;
+	unsigned long		stride;
+	unsigned long		step;
+	unsigned long		format;
+};
+
+struct pxa3xx_gcu_context {
+	struct pxa3xx_gcu_info	*info;
+	struct task_struct	*task;
+	struct list_head	list;	/* to struct pxa3xx_gcu_context */
+	//struct pxa3xx_gcu_gmem	*gmem;
+	struct list_head	mlist;	/* to struct pxa3xx_gcu_gmem */
+
+	unsigned long		total_gmem_size;
+	unsigned int		err_iin;
+	unsigned int		err_iop;
+	unsigned int		err_ipf;
+
+	/*
+	 * Graphics memory that is allocated for application usage will be mapped
+	 * into user space. Application and GCU can allocate buffers from
+	 * this area.
+	 */
+	struct pxa3xx_gcu_buf	srcbuf0;
+	struct pxa3xx_gcu_buf	srcbuf1;
+	struct pxa3xx_gcu_buf	dstbuf0;
+	struct pxa3xx_gcu_buf	dstbuf1;
+	struct pxa3xx_gcu_buf	dstbuf2;
+	unsigned long		alpha_set;
+	unsigned long		trans_set;
+	unsigned long		buffer_mode;
+};
+
+static int pxa3xx_gcu_reset(struct pxa3xx_gcu_info *info);
+
+static struct pxa3xx_gcu_info *priv_info = NULL;
+
+static irqreturn_t pxa3xx_gcu_irq(int irq, void *devid)
+{
+	struct pxa3xx_gcu_info *info = (struct pxa3xx_gcu_info *)devid;
+	struct pxa3xx_gcu_context *context = info->last;
+	unsigned long status, gciscr, gciecr, gcrbexhr, gcrbtr;
+
+	gciscr = __raw_readl(info->mmio_base + GCISCR);
+	gciecr = __raw_readl(info->mmio_base + GCIECR);
+	status = gciscr & gciecr;
+
+	gciscr &= GCISCR_MASK;
+	__raw_writel(gciscr, info->mmio_base + GCISCR);
+
+	if (status & (GCISCR_PF_INTST | GCISCR_IIN_INTST | GCISCR_IOP_INTST)) {
+		if (context != NULL) {
+			/* illegal pixel format */
+			if (gciscr & GCISCR_PF_INTST)
+				context->err_ipf++;
+			/* illegal instruction */
+			if (gciscr & GCISCR_IIN_INTST) {
+				context->err_iin++;
+				goto fail;
+			}
+			/* illegal operation */
+			if (gciscr & GCISCR_IOP_INTST) {
+				context->err_iop++;
+				goto fail;
+			}
+			dev_dbg(info->dev, "COUNT ipf:%d, iin:%d, iop:%d\n",
+				context->err_ipf, context->err_iin,
+				context->err_iop);
+		} else
+			dev_dbg(info->dev, "ipf:%d, iin:%d, iop:%d\n",
+				(gciscr & GCISCR_PF_INTST) ? 1 : 0,
+				(gciscr & GCISCR_IIN_INTST) ? 1 : 0,
+				(gciscr & GCISCR_IOP_INTST) ? 1 : 0);
+	}
+	if (status & GCISCR_EEOB_INTST) {
+		gciecr &= ~GCIECR_EEOB_INTEN & GCIECR_MASK;
+		__raw_writel(gciecr, info->mmio_base + GCIECR);
+
+		gcrbexhr = __raw_readl(info->mmio_base + GCRBEXHR);
+		gcrbtr = __raw_readl(info->mmio_base + GCRBTR);
+		if (gcrbexhr != gcrbtr)
+			dev_err(info->dev, "EEOB: exhead:0x%lx, tail:0x%lx\n",
+				gcrbexhr, gcrbtr);
+		complete(&info->done);
+	}
+	return IRQ_HANDLED;
+fail:
+	send_sig(SIGILL, context->task, 0);
+	pxa3xx_gcu_reset(info);
+	return IRQ_HANDLED;
+}
+
+static void __save_context(struct pxa3xx_gcu_context *context)
+{
+	struct pxa3xx_gcu_info *info = context->info;
+
+	context->srcbuf0.base = __raw_readl(info->mmio_base + GCS0BR);
+	context->srcbuf0.step = __raw_readl(info->mmio_base + GCS0STP);
+	context->srcbuf0.stride = __raw_readl(info->mmio_base + GCS0STR);
+	context->srcbuf0.format = __raw_readl(info->mmio_base + GCS0PF);
+
+	context->srcbuf1.base = __raw_readl(info->mmio_base + GCS1BR);
+	context->srcbuf1.step = __raw_readl(info->mmio_base + GCS1STP);
+	context->srcbuf1.stride = __raw_readl(info->mmio_base + GCS1STR);
+	context->srcbuf1.format = __raw_readl(info->mmio_base + GCS1PF);
+
+	context->dstbuf0.base = __raw_readl(info->mmio_base + GCD0BR);
+	context->dstbuf0.step = __raw_readl(info->mmio_base + GCD0STP);
+	context->dstbuf0.stride = __raw_readl(info->mmio_base + GCD0STR);
+	context->dstbuf0.format = __raw_readl(info->mmio_base + GCD0PF);
+
+	context->dstbuf1.base = __raw_readl(info->mmio_base + GCD1BR);
+	context->dstbuf1.step = __raw_readl(info->mmio_base + GCD1STP);
+	context->dstbuf1.stride = __raw_readl(info->mmio_base + GCD1STR);
+	context->dstbuf1.format = __raw_readl(info->mmio_base + GCD1PF);
+
+	context->dstbuf2.base = __raw_readl(info->mmio_base + GCD2BR);
+	context->dstbuf2.step = __raw_readl(info->mmio_base + GCD2STP);
+	context->dstbuf2.stride = __raw_readl(info->mmio_base + GCD2STR);
+	context->dstbuf2.format = __raw_readl(info->mmio_base + GCD2PF);
+
+	context->buffer_mode = __raw_readl(info->mmio_base + GCCR) & 0x0f;
+	context->alpha_set = __raw_readl(info->mmio_base + GCALPHASET);
+	context->trans_set = __raw_readl(info->mmio_base + GCTSET);
+}
+
+static void __restore_context(struct pxa3xx_gcu_context *context)
+{
+	struct pxa3xx_gcu_info *info = context->info;
+	unsigned long gccr;
+
+	__raw_writel(context->srcbuf0.base, info->mmio_base + GCS0BR);
+	__raw_writel(context->srcbuf0.step, info->mmio_base + GCS0STP);
+	__raw_writel(context->srcbuf0.stride, info->mmio_base + GCS0STR);
+	__raw_writel(context->srcbuf0.format, info->mmio_base + GCS0PF);
+
+	__raw_writel(context->srcbuf1.base, info->mmio_base + GCS1BR);
+	__raw_writel(context->srcbuf1.step, info->mmio_base + GCS1STP);
+	__raw_writel(context->srcbuf1.stride, info->mmio_base + GCS1STR);
+	__raw_writel(context->srcbuf1.format, info->mmio_base + GCS1PF);
+
+	__raw_writel(context->dstbuf0.base, info->mmio_base + GCD0BR);
+	__raw_writel(context->dstbuf0.step, info->mmio_base + GCD0STP);
+	__raw_writel(context->dstbuf0.stride, info->mmio_base + GCD0STR);
+	__raw_writel(context->dstbuf0.format, info->mmio_base + GCD0PF);
+
+	__raw_writel(context->dstbuf1.base, info->mmio_base + GCD1BR);
+	__raw_writel(context->dstbuf1.step, info->mmio_base + GCD1STP);
+	__raw_writel(context->dstbuf1.stride, info->mmio_base + GCD1STR);
+	__raw_writel(context->dstbuf1.format, info->mmio_base + GCD1PF);
+
+	__raw_writel(context->dstbuf2.base, info->mmio_base + GCD2BR);
+	__raw_writel(context->dstbuf2.step, info->mmio_base + GCD2STP);
+	__raw_writel(context->dstbuf2.stride, info->mmio_base + GCD2STR);
+	__raw_writel(context->dstbuf2.format, info->mmio_base + GCD2PF);
+
+	gccr = __raw_readl(info->mmio_base + GCCR);
+	gccr = (gccr & ~0xf) | context->buffer_mode;
+	__raw_writel(gccr, info->mmio_base + GCCR);
+	__raw_writel(context->alpha_set, info->mmio_base + GCALPHASET);
+	__raw_writel(context->trans_set, info->mmio_base + GCTSET);
+}
+
+static int pxa3xx_gcu_append(struct pxa3xx_gcu_info *info, void *usrbuf,
+			     size_t len)
+{
+	unsigned int tail_room, head_room;
+	unsigned long exhead;
+	unsigned long tail = info->ring_tail_dma;
+	unsigned long base = info->ring_addr_dma;
+	unsigned long size = info->ring_size;
+	unsigned char *ring_addr = (unsigned char *)info->ring_addr;
+
+	exhead = __raw_readl(info->mmio_base + GCRBEXHR);
+	if (tail >= exhead) {
+		tail_room = size - (tail - base);
+		head_room = exhead - base;
+	} else {
+		tail_room = exhead - tail;
+		head_room = 0;
+	}
+	dev_dbg(info->dev, "base:%lx, exhead:%lx, tail:%lx, head_room:%x,"
+		" tail_room:%x\n", base, exhead, tail, head_room, tail_room);
+	dev_dbg(info->dev, "base:%x, head:%x, exhead:%x, tail:%x, length:%x\n",
+		__raw_readl(info->mmio_base + GCRBBR),
+		__raw_readl(info->mmio_base + GCRBHR),
+		__raw_readl(info->mmio_base + GCRBEXHR),
+		__raw_readl(info->mmio_base + GCRBTR),
+		__raw_readl(info->mmio_base + GCRBLR));
+
+	if (tail_room >= len) {
+		if (copy_from_user(ring_addr + (tail - base), usrbuf, len))
+			return -EFAULT;
+		tail += len;
+	} else if (head_room + tail_room >= len) {
+		if (copy_from_user(ring_addr + (tail - base), usrbuf,
+			tail_room))
+			return -EFAULT;
+		usrbuf += tail_room;
+		len -= tail_room;
+
+		if (copy_from_user(ring_addr, usrbuf, len))
+			return -EFAULT;
+		tail = info->ring_addr_dma + len;
+	} else
+		return -ENOSPC;
+
+	if (tail - base == size)
+		tail = base;
+	info->ring_tail_dma = tail;
+	__raw_writel(tail, info->mmio_base + GCRBTR);
+	dev_dbg(info->dev, "tail:%lx, size:%ld\n", tail, size);
+	return 0;
+}
+
+static void pxa3xx_gcu_wait_for_eeob(struct pxa3xx_gcu_info *info)
+{
+	unsigned long gciscr, gciecr, gcrbexhr, gcrbtr;
+
+	gciscr = __raw_readl(info->mmio_base + GCISCR);
+	__raw_writel(gciscr | GCISCR_EEOB_INTST, info->mmio_base + GCISCR);
+	gciecr = __raw_readl(info->mmio_base + GCIECR);
+	__raw_writel(gciecr | GCIECR_EEOB_INTEN, info->mmio_base + GCIECR);
+
+	for (;;) {
+		gcrbexhr = __raw_readl(info->mmio_base + GCRBEXHR);
+		gcrbtr = __raw_readl(info->mmio_base + GCRBTR);
+		if (gcrbexhr == gcrbtr)
+			return;
+		wait_for_completion_timeout(&info->done, GCU_TIMEOUT);
+	}
+}
+
+static int pxa3xx_gcu_kick(struct pxa3xx_gcu_info *info, int sync)
+{
+	unsigned long gccr;
+
+	gccr = __raw_readl(info->mmio_base + GCCR);
+	gccr &= ~GCCR_STOP & GCCR_MASK;
+	__raw_writel(gccr, info->mmio_base + GCCR);
+
+	if (sync)
+		pxa3xx_gcu_wait_for_eeob(info);
+	return 0;
+}
+
+/*
+ * pxa3xx_gcu_sync is used to ensure that submitted instructions are
+ * completely finished or an error occured.
+ */
+static int pxa3xx_gcu_sync(struct pxa3xx_gcu_context *context)
+{
+	struct pxa3xx_gcu_info *info = context->info;
+
+	if (info->last != context)
+		return -EINVAL;
+	pxa3xx_gcu_wait_for_eeob(info);
+	return 0;
+}
+
+/*
+ * pxa3xx_gcu_switch_context should be called only when no operation on current
+ * context.
+ */
+static void pxa3xx_gcu_switch_context(struct pxa3xx_gcu_context *context)
+{
+	struct pxa3xx_gcu_info *info = context->info;
+
+	mutex_lock(&info->lock);
+	if (info->last)
+		__save_context(info->last);
+	if (context)
+		__restore_context(context);
+	info->last = context;
+	mutex_unlock(&info->lock);
+}
+
+/*
+ * pxa3xx_gcu_submit will try to copy the instructions from a user supplied
+ * buffer to the GCU Ring Buffer for execution. This function will return
+ * immediately once the copy is done and the GCU is started. Synchronization
+ * is done by calling pxa3xx_gcu_sync() to ensure that instructions submitted
+ * are completely executed or until error occurs.
+ *
+ * This function will block if the device is current serving another context,
+ * unless non-delay mode is set.
+ */
+static int pxa3xx_gcu_submit(struct pxa3xx_gcu_context *context,
+			     struct pxa3xx_gcu_submit_req *req)
+{
+	void __user *usrbuf = (void *)req->base;
+	struct pxa3xx_gcu_info *info = NULL;
+	int ndelay, sync, ret;
+	size_t size = req->len;
+
+	if (context == NULL || context->info == NULL)
+		return -ENODEV;
+	info = context->info;
+
+	if ((size >= GCU_RINGBUF_SIZE) || (size % 4))
+		return -EINVAL;
+	if (!access_ok(VERIFY_READ, usrbuf, size))
+		return -EFAULT;
+
+	if (context != info->last) {
+		pxa3xx_gcu_wait_for_eeob(info);
+		pxa3xx_gcu_switch_context(context);
+	}
+
+	ndelay = req->mode & PXA3xx_GCU_SUBMIT_MODE_NDELAY;
+	sync = req->mode & PXA3xx_GCU_SUBMIT_MODE_SYNC;
+
+submit:
+	if (ndelay) {
+		if (down_trylock(&info->submit_sem))
+			return -EAGAIN;
+	} else {
+		if (down_interruptible(&info->submit_sem))
+			return -ERESTARTSYS;
+	}
+
+	ret = pxa3xx_gcu_append(info, usrbuf, size);
+	if (ret == -ENOSPC) {
+		if (ndelay) {
+			up(&info->submit_sem);
+			pxa3xx_gcu_wait_for_eeob(info);
+			goto submit;
+		}
+	}
+
+	if (ret == 0)
+		ret = pxa3xx_gcu_kick(info, sync);
+	up(&info->submit_sem);
+	return ret;
+}
+
+static int pxa3xx_gcu_reset(struct pxa3xx_gcu_info *info)
+{
+	unsigned int gccr, gciscr, gciecr;
+
+	info->ring_tail_dma = info->ring_addr_dma;
+	gccr = __raw_readl(info->mmio_base + GCCR) & 0x0F;
+	__raw_writel(gccr | GCCR_ABORT, info->mmio_base + GCCR);
+	udelay(10);
+	gccr = __raw_readl(info->mmio_base + GCCR) & 0x0F;
+	__raw_writel(gccr | GCCR_STOP, info->mmio_base + GCCR);
+
+	__raw_writel(0, info->mmio_base + GCRBLR);
+	__raw_writel(info->ring_addr_dma, info->mmio_base + GCRBBR);
+	__raw_writel(info->ring_tail_dma, info->mmio_base + GCRBTR);
+	__raw_writel(info->ring_size, info->mmio_base + GCRBLR);
+	dev_dbg(info->dev, "ring head:%lx, ring tail:%lx, ring size:%lx\n",
+		info->ring_addr_dma, info->ring_tail_dma, info->ring_size);
+	dev_dbg(info->dev, "base:%x, head:%x, exhead:%x, tail:%x, length:%x\n",
+		__raw_readl(info->mmio_base + GCRBBR),
+		__raw_readl(info->mmio_base + GCRBHR),
+		__raw_readl(info->mmio_base + GCRBEXHR),
+		__raw_readl(info->mmio_base + GCRBTR),
+		__raw_readl(info->mmio_base + GCRBLR));
+
+	gciscr = __raw_readl(info->mmio_base + GCISCR);
+	__raw_writel(gciscr | GCISCR_MASK, info->mmio_base + GCISCR);
+	gciecr = __raw_readl(info->mmio_base + GCIECR);
+	gciecr &= ~GCIECR_MASK;
+	gciecr |= GCIECR_PF_INTEN | GCIECR_IIN_INTEN | GCIECR_IOP_INTEN;
+	__raw_writel(gciecr, info->mmio_base + GCIECR);
+
+	__raw_writel(0, info->mmio_base + GCNOPID);
+	__raw_writel(0, info->mmio_base + GCTABADDR);
+	__raw_writel(0, info->mmio_base + GCMABADDR);
+	return 0;
+}
+
+static int dump_gmem(struct pxa3xx_gcu_context *context)
+{
+	struct pxa3xx_gcu_info *info = context->info;
+	struct pxa3xx_gcu_gmem *gmem = NULL, *n = NULL;
+
+	list_for_each_entry_safe(gmem, n, &context->mlist, list) {
+		dev_dbg(info->dev, "virt addr:0x%lx, phys addr:0x%lx,"
+			" size:0x%x, usr addr:0x%lx\n", gmem->gmem_virt_addr,
+			gmem->gmem_phys_addr, gmem->gmem_size,
+			gmem->gmem_uaddr);
+	}
+	return 0;
+}
+
+static struct pxa3xx_gcu_gmem *__alloc_gmem(struct pxa3xx_gcu_context *context,
+					    size_t size)
+{
+	struct pxa3xx_gcu_info *info = context->info;
+	struct page *page, *end;
+	struct pxa3xx_gcu_gmem *gmem;
+	unsigned long order, addr;
+
+	if (info == NULL)
+		goto out;
+	if (info->total_gmem_size + size > MAX_DEVICE_GMEM_SIZE)
+		goto out;
+	if (context->total_gmem_size + size > MAX_CONTEXT_GMEM_SIZE)
+		goto out;
+
+	gmem = kzalloc(sizeof(struct pxa3xx_gcu_gmem), GFP_KERNEL);
+	if (gmem == NULL)
+		goto out;
+
+	size = PAGE_ALIGN(size);
+	order = get_order(size);
+	page = alloc_pages(GFP_KERNEL | GFP_DMA, order);
+	if (page == NULL)
+		goto out_mem;
+	addr = (unsigned long)page_address(page);
+	end = page + (1 << order);
+	dma_cache_maint((void *)addr, size, DMA_FROM_DEVICE);
+
+	gmem->gmem_size = size;
+	gmem->gmem_pages = page;
+	gmem->gmem_virt_addr = addr;
+	gmem->gmem_phys_addr = virt_to_phys((void *)addr);
+
+	context->total_gmem_size += size;
+	info->total_gmem_size += size;
+
+	do {
+		SetPageReserved(page);
+		page++;
+	} while (size -= PAGE_SIZE);
+
+	/* free the unused pages */
+	while (page < end)
+		__free_page(page++);
+
+	atomic_set(&gmem->gmem_count, 1);
+	return gmem;
+out_mem:
+	kfree(gmem);
+out:
+	return NULL;
+}
+
+static void __free_gmem(struct pxa3xx_gcu_context *context,
+			struct pxa3xx_gcu_gmem *gmem)
+{
+	struct pxa3xx_gcu_info *info = context->info;
+	struct page *page = gmem->gmem_pages;
+	size_t size = gmem->gmem_size;
+	unsigned long addr;
+
+	if (!atomic_dec_and_test(&gmem->gmem_count))
+		return;
+	context->total_gmem_size -= size;
+	info->total_gmem_size -= size;
+
+	addr = (unsigned long)page_address(page);
+	dma_cache_maint((void *)addr, size, DMA_FROM_DEVICE);
+	for (; size > 0; size -= PAGE_SIZE, page++) {
+		ClearPageReserved(page);
+		__free_page(page);
+	}
+
+	mutex_lock(&info->lock);
+	list_del(&gmem->list);
+	mutex_unlock(&info->lock);
+	kfree(gmem);
+}
+
+static int __request_mem(struct pxa3xx_gcu_context *context,
+			 struct pxa3xx_gcu_mem_req *req)
+{
+	struct pxa3xx_gcu_info *info = context->info;
+	struct pxa3xx_gcu_gmem *gmem = NULL;
+	int ret;
+
+	switch (PXA3xx_GCU_MEM_REQ_TYPE(req->req_type)) {
+	case PXA3xx_GCU_GRAPHICS_MEM:
+		if (req->req_size == 0) {
+			ret = req->req_type;
+			memset(req, 0, sizeof(struct pxa3xx_gcu_mem_req));
+			req->req_type = ret;
+			return 0;
+		}
+
+		gmem = __alloc_gmem(context, req->req_size);
+		if (gmem == NULL)
+			return -ENOMEM;
+
+		gmem->gmem_type = req->req_type;
+
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	mutex_lock(&info->lock);
+	list_add(&gmem->list, &context->mlist);
+	mutex_unlock(&info->lock);
+
+	req->mmap_addr = (unsigned long)gmem->gmem_virt_addr;
+	req->mmap_size = gmem->gmem_size;
+	req->phys_addr = gmem->gmem_phys_addr;
+	dump_gmem(context);
+	return 0;
+}
+
+static int __release_mem(struct pxa3xx_gcu_context *context,
+			 unsigned long addr)
+{
+	struct pxa3xx_gcu_gmem *gmem = NULL, *n = NULL;
+
+	if (addr == 0) {
+		/* release all allocated memory for this context */
+		list_for_each_entry_safe(gmem, n, &context->mlist, list)
+			__free_gmem(context, gmem);
+	} else {
+		list_for_each_entry_safe(gmem, n, &context->mlist, list) {
+			if (gmem->gmem_virt_addr == addr)
+				__free_gmem(context, gmem);
+		}
+	}
+	gmem = NULL;
+	n = NULL;
+	return 0;
+}
+
+static struct pxa3xx_gcu_context *__create_context(struct
pxa3xx_gcu_info *info)
+{
+	struct pxa3xx_gcu_context *context;
+
+	if (info == NULL)
+		return NULL;
+
+	context = kzalloc(sizeof(struct pxa3xx_gcu_context), GFP_KERNEL);
+	if (context == NULL)
+		return NULL;
+
+	context->info = info;
+	context->task = current;
+	context->alpha_set = 0;
+	context->trans_set = 0;
+	context->buffer_mode = 0x08;
+
+	INIT_LIST_HEAD(&context->mlist);
+
+	mutex_lock(&info->lock);
+	list_add_tail(&context->list, &info->list);
+	info->count++;
+	mutex_unlock(&info->lock);
+
+	return context;
+}
+
+static void __free_context(struct pxa3xx_gcu_context *context)
+{
+	struct pxa3xx_gcu_info *info;
+
+	if (context == NULL || context->info == NULL)
+		return;
+
+	info = context->info;
+	__release_mem(context, 0);
+	mutex_lock(&info->lock);
+	list_del(&context->list);
+	info->count--;
+	if (info->last == context)
+		info->last = NULL;
+	mutex_unlock(&info->lock);
+	kfree(context);
+}
+
+#ifdef CONFIG_PM
+static int pxa3xx_gcu_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct pxa3xx_gcu_info *info = platform_get_drvdata(pdev);
+
+	pxa3xx_gcu_wait_for_eeob(info);
+	if (info->last) {
+		mutex_lock(&info->lock);
+		__save_context(info->last);
+		mutex_unlock(&info->lock);
+	}
+	return 0;
+}
+
+static int pxa3xx_gcu_resume(struct platform_device *pdev)
+{
+	struct pxa3xx_gcu_info *info = platform_get_drvdata(pdev);
+
+	pxa3xx_gcu_reset(info);
+	if (info->last) {
+		mutex_lock(&info->lock);
+		__restore_context(info->last);
+		mutex_unlock(&info->lock);
+	}
+	return 0;
+}
+#endif
+
+static int pxa3xx_gcu_open(struct inode *inode, struct file *file)
+{
+	struct pxa3xx_gcu_context *context = file->private_data;
+
+	if (context == NULL) {
+		if (priv_info == NULL)
+			return -EFAULT;
+		context = __create_context(priv_info);
+		if (context == NULL)
+			return -ENOMEM;
+		file->private_data = context;
+	}
+	return 0;
+}
+
+static int pxa3xx_gcu_release(struct inode *inode, struct file *file)
+{
+	struct pxa3xx_gcu_context *context = file->private_data;
+
+	pxa3xx_gcu_wait_for_eeob(context->info);
+	__free_context(context);
+	return 0;
+}
+
+static int pxa3xx_gcu_ioctl(struct inode *inode, struct file *file,
+			    unsigned int cmd, unsigned long arg)
+{
+	struct pxa3xx_gcu_context *context = file->private_data;
+	struct pxa3xx_gcu_info *info = (struct pxa3xx_gcu_info *)context->info;
+	struct pxa3xx_gcu_gmem *gmem = NULL;
+	struct pxa3xx_gcu_submit_req submit_req;
+	struct pxa3xx_gcu_mem_req mem_req;
+	struct vm_area_struct *vma = NULL;
+	void __user *uarg = (void *)arg;
+	int ret = 0;
+
+	if (info == NULL)
+		return -ENODEV;
+
+	switch (cmd) {
+	case PXA3xx_GCU_IO_REQUEST_MEM:
+		if (copy_from_user(&mem_req, uarg,
+			sizeof(struct pxa3xx_gcu_mem_req))) {
+			ret = -EFAULT;
+			goto out;
+		}
+		ret = __request_mem(context, &mem_req);
+		if (ret < 0)
+			goto out;
+		if (copy_to_user(uarg, &mem_req,
+			sizeof(struct pxa3xx_gcu_mem_req))) {
+			__release_mem(context, mem_req.mmap_addr);
+			ret = -EFAULT;
+			goto out;
+		}
+		break;
+	case PXA3xx_GCU_IO_RELEASE_MEM:
+		__release_mem(context, arg);
+		break;
+	case PXA3xx_GCU_IO_FLUSH_MEM:
+		list_for_each_entry(gmem, &context->mlist, list) {
+			if (gmem->gmem_uaddr == arg) {
+				dmac_flush_range((void *)arg, (void *)arg
+						+ gmem->gmem_size);
+				outer_flush_range(gmem->gmem_phys_addr,
+						gmem->gmem_phys_addr
+						+ gmem->gmem_size);
+			}
+		}
+		break;
+	case PXA3xx_GCU_IO_SUBMIT:
+		if (copy_from_user(&submit_req, uarg,
+			sizeof(struct pxa3xx_gcu_submit_req))) {
+			ret = -EFAULT;
+			goto out;
+		}
+		ret = pxa3xx_gcu_submit(context, &submit_req);
+		break;
+	case PXA3xx_GCU_IO_SYNC:
+		ret = pxa3xx_gcu_sync(context);
+		break;
+	case PXA3xx_GCU_IO_GET_BUS_ADDR:
+		/* assume user provided virtual memory are physical continous */
+		memset(&mem_req, 0, sizeof(struct pxa3xx_gcu_mem_req));
+		if (copy_from_user(&mem_req.mmap_addr, uarg,
+			sizeof(unsigned long))) {
+			ret = -EFAULT;
+			goto out;
+		}
+		vma = find_vma(current->mm, mem_req.mmap_addr);
+		if (vma == NULL) {
+			ret = -EINVAL;
+			goto out;
+		}
+		mem_req.phys_addr = (vma->vm_pgoff << PAGE_SHIFT)
+				+ (mem_req.mmap_addr % PAGE_SIZE);
+		if (copy_to_user(uarg, &mem_req.phys_addr,
+			sizeof(unsigned long))) {
+			ret = -EFAULT;
+			goto out;
+		}
+		break;
+	default:
+		break;
+	}
+	return 0;
+out:
+	dev_dbg(info->dev, "%s, return value: %d\n", __func__, ret);
+	return ret;
+}
+
+static int pxa3xx_gcu_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct pxa3xx_gcu_context *context = file->private_data;
+	struct pxa3xx_gcu_gmem *gmem = NULL, *n = NULL;
+	unsigned long pg_off;
+	pgprot_t pgprot;
+
+	if (context == NULL)
+		return -ENODEV;
+
+	/* mmap() specifies the physical address as offset parameter */
+	list_for_each_entry_safe(gmem, n, &context->mlist, list) {
+		pg_off = gmem->gmem_phys_addr >> PAGE_SHIFT;
+		if (pg_off == vma->vm_pgoff)
+			break;
+	}
+	if (gmem == NULL)
+		return -ENOMEM;
+
+	switch (PXA3xx_GCU_MEM_REQ_ATTR(gmem->gmem_type)) {
+	case PXA3xx_GCU_ATTR_COHERENT:
+		pgprot = pgprot_noncached(vma->vm_page_prot);
+		break;
+	case PXA3xx_GCU_ATTR_WRITECOMBINE:
+		pgprot = pgprot_writecombine(vma->vm_page_prot);
+		break;
+	case PXA3xx_GCU_ATTR_CACHEABLE:
+	default:
+		pgprot = vma->vm_page_prot;
+		break;
+	}
+	vma->vm_page_prot = pgprot | PAGE_SHARED;
+	vma->vm_flags |= (VM_IO | VM_RESERVED);
+	if (remap_pfn_range(vma, vma->vm_start,
+			    gmem->gmem_phys_addr >> PAGE_SHIFT,
+			    vma->vm_end - vma->vm_start,
+			    vma->vm_page_prot))
+		return -EAGAIN;
+	gmem->gmem_uaddr = vma->vm_start;
+	dump_gmem(context);
+	dma_cache_maint((void *)gmem->gmem_virt_addr,
+			vma->vm_end - vma->vm_start,
+			DMA_BIDIRECTIONAL);
+	return 0;
+}
+
+static int pxa3xx_gcu_fsync(struct file *file, struct dentry *dentry,
+			    int datasync)
+{
+	struct pxa3xx_gcu_context *context = file->private_data;
+
+	return pxa3xx_gcu_sync(context);
+}
+
+static struct file_operations pxa3xx_gcu_fops = {
+	.owner		= THIS_MODULE,
+	.open		= pxa3xx_gcu_open,
+	.release	= pxa3xx_gcu_release,
+	.ioctl		= pxa3xx_gcu_ioctl,
+	.mmap		= pxa3xx_gcu_mmap,
+	.fsync		= pxa3xx_gcu_fsync,
+};
+
+static struct miscdevice pxa3xx_gcu_miscdev = {
+	.minor		= MISC_DYNAMIC_MINOR,
+	.name		= "m2d",
+	.fops		= &pxa3xx_gcu_fops,
+};
+
+static int pxa3xx_gcu_probe(struct platform_device *pdev)
+{
+	struct pxa3xx_gcu_info *info;
+	struct resource *res;
+	int ret, size;
+
+	ret = misc_register(&pxa3xx_gcu_miscdev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "unable to register device /dev/m2d\n");
+		return ret;
+	}
+
+	info = kzalloc(sizeof(struct pxa3xx_gcu_info), GFP_KERNEL);
+	if (info == NULL) {
+		dev_err(&pdev->dev, "failed to allocate memory\n");
+		ret = -ENOMEM;
+		goto err;
+	}
+	info->pdev = pdev;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "no resource definied for MEM\n");
+		ret = -ENXIO;
+		goto err_res;
+	}
+
+	res = request_mem_region(res->start, GCU_MAX_REG, pdev->name);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "failed to request memory resource\n");
+		ret = -EBUSY;
+		goto err_res;
+	}
+
+	info->mmio_base = ioremap(res->start, GCU_MAX_REG);
+	if (info->mmio_base == NULL) {
+		dev_err(&pdev->dev, "failed to ioremap register\n");
+		ret = -ENOMEM;
+		goto err_map;
+	}
+
+	info->irq = platform_get_irq(pdev, 0);
+	if (info->irq < 0) {
+		dev_err(&pdev->dev, "failed to get IRQ\n");
+		ret = -ENXIO;
+		goto err_irq;
+	}
+	ret = request_irq(info->irq, pxa3xx_gcu_irq, IRQF_DISABLED,
+			  pdev->name, info);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to request GCU IRQ\n");
+		ret = -EBUSY;
+		goto err_irq;
+	}
+
+	info->ring_addr = dma_alloc_coherent(info->dev,
+				GCU_RINGBUF_SIZE + 256,
+				(dma_addr_t *)&info->ring_addr_dma,
+				GFP_KERNEL);
+	if (info->ring_addr == NULL) {
+		dev_err(&pdev->dev, "failed to allocate ring buffer\n");
+		ret = -ENOMEM;
+		goto err_mem;
+	}
+	size = ALIGN(info->ring_addr_dma, 256) - info->ring_addr_dma;
+	if (size > 0) {
+		info->ring_addr_dma += size;
+		info->ring_addr += size;
+	}
+	info->ring_tail_dma = info->ring_addr_dma;
+	info->ring_size = ALIGN(info->ring_addr_dma + GCU_RINGBUF_SIZE, 256)
+				- info->ring_addr_dma;
+	info->dev = &pdev->dev;
+
+	pxa3xx_gcu_reset(info);
+
+	mutex_init(&info->lock);
+	init_MUTEX(&info->submit_sem);
+	init_completion(&info->done);
+	INIT_LIST_HEAD(&info->list);
+
+	priv_info = info;
+
+	platform_set_drvdata(pdev, info);
+
+	dev_info(&pdev->dev, "2D Graphics Driver for PXA3xx\n");
+
+	return 0;
+err_mem:
+	free_irq(info->irq, info);
+err_irq:
+	iounmap(info->mmio_base);
+err_map:
+	release_mem_region(res->start, GCU_MAX_REG);
+err_res:
+	kfree(info);
+err:
+	misc_deregister(&pxa3xx_gcu_miscdev);
+	return ret;
+}
+
+static int pxa3xx_gcu_remove(struct platform_device *pdev)
+{
+	struct pxa3xx_gcu_info *info = platform_get_drvdata(pdev);
+	unsigned int gccr;
+
+	/* Stop the graphics controller */
+	gccr = __raw_readl(info->mmio_base + GCCR);
+	if ((gccr & GCCR_STOP) == 0) {
+		gccr &= GCCR_MASK;
+		gccr |= GCCR_STOP;
+	}
+	__raw_writel(gccr, info->mmio_base + GCCR);
+
+	__raw_writel(0, info->mmio_base + GCRBLR);
+	__raw_writel(0, info->mmio_base + GCRBBR);
+	__raw_writel(0, info->mmio_base + GCRBTR);
+	dma_free_coherent(info->dev, info->ring_size, info->ring_addr,
+			(dma_addr_t)info->ring_addr_dma);
+	free_irq(info->irq, info);
+
+	misc_deregister(&pxa3xx_gcu_miscdev);
+
+	priv_info = NULL;
+
+	return 0;
+}
+
+static struct platform_driver pxa3xx_gcu_driver = {
+	.driver = {
+		.name	= "pxa3xx-gcu",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= pxa3xx_gcu_probe,
+	.remove		= pxa3xx_gcu_remove,
+#ifdef CONFIG_PM
+	.suspend	= pxa3xx_gcu_suspend,
+	.resume		= pxa3xx_gcu_resume,
+#endif
+};
+
+static int __devinit pxa3xx_gcu_init(void)
+{
+	platform_driver_register(&pxa3xx_gcu_driver);
+
+	return 0;
+}
+
+static void __exit pxa3xx_gcu_exit(void)
+{
+	platform_driver_unregister(&pxa3xx_gcu_driver);
+}
+
+module_init(pxa3xx_gcu_init);
+module_exit(pxa3xx_gcu_exit);
+MODULE_LICENSE("GPL");
+
-- 
1.5.6.5



More information about the linux-arm-kernel mailing list