[PATCH] pxa: add 2d graphics driver
DORMANTno replies
From: Haojian Zhuang <hidden>
Date: 2009-10-18 17:43:42
Subsystem:
char and misc drivers, the rest · Maintainers:
Arnd Bergmann, Greg Kroah-Hartman, Linus Torvalds
PXA3xx series provides 2D graphics function. It supports line draw, chroma key, scale, alpha blending, and so on. Signed-off-by: Haojian Zhuang <redacted> --- 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.hb/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