[PATCH 4/4] video: add driver for PXA3xx 2D graphics accelerator

Daniel Mack daniel at caiaq.de
Thu Feb 25 11:05:58 EST 2010


(now used the new AKML address)

Hi,

Was anyone able to give that driver a try?
Haojian, you wanted to have a look IIRC?

I would appreciate seeing this in .35 if possible.

Daniel


On Thu, Jul 09, 2009 at 07:04:51PM +0200, Daniel Mack wrote:
> This adds a driver for the the 2D graphics accelerator found on PXA3xx
> processors. Only resource mapping, interrupt handling and a simple ioctl
> handler is done by the kernel part, the rest of the logic is implemented
> in DirectFB userspace.
> 
> Graphic applications benefit for line drawing, blend, and rectangle and
> triangle filling operations.
> 
> Measurements on a PXA303 using the df_dok benchmarking tool follow,
> where the value in square brackets show the CPU usage during that test.
> 
> Without accelerator:
> 
>   Fill Rectangle                                 3.007 secs (  175.922 MPixel/sec) [100.3%]
>   Fill Rectangles [10]                           3.178 secs (  182.696 MPixel/sec) [100.0%]
>   Fill Triangles                                 3.023 secs (   99.232 MPixel/sec) [100.3%]
>   Blit                                           3.082 secs (   39.770 MPixel/sec) [100.0%]
>   Blit 180                                       3.115 secs (   28.994 MPixel/sec) [100.3%]
>   Blit with format conversion                    3.078 secs (   18.863 MPixel/sec) [100.0%]
> 
> With accelerator:
> 
>   Fill Rectangle                                 4.377 secs (*  88.433 MPixel/sec) [  5.9%]
>   Fill Rectangles [10]                           5.176 secs (*  87.245 MPixel/sec) [  0.5%]
>   Fill Triangles                                 3.025 secs (   87.437 MPixel/sec) [ 46.6%]
>   Blit                                          12.177 secs (*  22.250 MPixel/sec) [  1.3%]
>   Blit 180                                       8.421 secs (*  32.175 MPixel/sec) [  4.9%]
>   Blit with format conversion                    8.216 secs (*  40.045 MPixel/sec) [  2.5%]
> 
> Signed-off-by: Daniel Mack <daniel at caiaq.de>
> Cc: Denis Oliver Kropp <dok at directfb.org>
> ---
>  drivers/video/Kconfig      |   10 +
>  drivers/video/Makefile     |    1 +
>  drivers/video/pxa3xx-gcu.c |  591 ++++++++++++++++++++++++++++++++++++++++++++
>  drivers/video/pxa3xx-gcu.h |   49 ++++
>  4 files changed, 651 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/video/pxa3xx-gcu.c
>  create mode 100644 drivers/video/pxa3xx-gcu.h
> 
> diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
> index 8afcf08..6ad0a1c 100644
> --- a/drivers/video/Kconfig
> +++ b/drivers/video/Kconfig
> @@ -1817,6 +1817,16 @@ config FB_PXA_PARAMETERS
>  
>  	  <file:Documentation/fb/pxafb.txt> describes the available parameters.
>  
> +config PXA3XX_GCU
> +	tristate "PXA3xx 2D graphics accelerator driver"
> +	depends on FB_PXA
> +	help
> +	  Kernelspace driver for the 2D graphics controller unit (GCU)
> +	  found on PXA3xx processors. There is a counterpart driver in the
> +	  DirectFB suite, see http://www.directfb.org/
> +
> +	  If you compile this as a module, it will be called pxa3xx_gcu.
> +
>  config FB_MBX
>  	tristate "2700G LCD framebuffer support"
>  	depends on FB && ARCH_PXA
> diff --git a/drivers/video/Makefile b/drivers/video/Makefile
> index 01a819f..ffe382c 100644
> --- a/drivers/video/Makefile
> +++ b/drivers/video/Makefile
> @@ -98,6 +98,7 @@ obj-$(CONFIG_FB_CIRRUS)		  += cirrusfb.o
>  obj-$(CONFIG_FB_ASILIANT)	  += asiliantfb.o
>  obj-$(CONFIG_FB_PXA)		  += pxafb.o
>  obj-$(CONFIG_FB_PXA168)		  += pxa168fb.o
> +obj-$(CONFIG_PXA3XX_GCU)	  += pxa3xx-gcu.o
>  obj-$(CONFIG_FB_W100)		  += w100fb.o
>  obj-$(CONFIG_FB_TMIO)		  += tmiofb.o
>  obj-$(CONFIG_FB_AU1100)		  += au1100fb.o
> diff --git a/drivers/video/pxa3xx-gcu.c b/drivers/video/pxa3xx-gcu.c
> new file mode 100644
> index 0000000..224faf7
> --- /dev/null
> +++ b/drivers/video/pxa3xx-gcu.c
> @@ -0,0 +1,591 @@
> +/*
> + *  pxa3xx-gc.c - Linux kernel module for PXA3xx graphics controllers
> + *
> + *  This driver needs a DirectFB counterpart in user space, communication
> + *  is handled via mmap()ed memory areas and an ioctl.
> + *
> + *  Copyright (c) 2009 Daniel Mack <daniel at caiaq.de>
> + *  Copyright (c) 2009 Janine Kropp <nin at directfb.org>
> + *  Copyright (c) 2009 Denis Oliver Kropp <dok at directfb.org>
> + *
> + *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +/*
> + * WARNING: This controller is attached to System Bus 2 of the PXA which
> + * needs its arbiter to be enabled explictly (CKENB & 1<<9).
> + * There is currently no way to do this from Linux, so you need to teach
> + * your bootloader for now.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/version.h>
> +
> +#include <linux/platform_device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/miscdevice.h>
> +#include <linux/interrupt.h>
> +#include <linux/spinlock.h>
> +#include <linux/uaccess.h>
> +#include <linux/ioctl.h>
> +#include <linux/delay.h>
> +#include <linux/sched.h>
> +#include <linux/clk.h>
> +#include <linux/fs.h>
> +#include <linux/io.h>
> +
> +#include "pxa3xx-gcu.h"
> +
> +#define DRV_NAME	"pxa3xx-gcu"
> +#define MISCDEV_MINOR	197
> +
> +#define REG_GCCR	0x00
> +#define GCCR_SYNC_CLR	(1 << 9)
> +#define GCCR_BP_RST	(1 << 8)
> +#define GCCR_ABORT	(1 << 6)
> +#define GCCR_STOP	(1 << 4)
> +
> +#define REG_GCISCR	0x04
> +#define REG_GCIECR	0x08
> +#define REG_GCRBBR	0x20
> +#define REG_GCRBLR	0x24
> +#define REG_GCRBHR	0x28
> +#define REG_GCRBTR	0x2C
> +#define REG_GCRBEXHR	0x30
> +
> +#define IE_EOB		(1 << 0)
> +#define IE_EEOB		(1 << 5)
> +#define IE_ALL		0xff
> +
> +#define SHARED_SIZE	PAGE_ALIGN(sizeof(struct pxa3xx_gcu_shared))
> +
> +/* #define PXA3XX_GCU_DEBUG */
> +/* #define PXA3XX_GCU_DEBUG_TIMER */
> +
> +#ifdef PXA3XX_GCU_DEBUG
> +#define QDUMP(msg)	\
> +	do {						\
> +		QPRINT(priv, KERN_DEBUG, msg);		\
> +	} while (0)
> +#else
> +#define QDUMP(msg)	do {} while (0)
> +#endif
> +
> +#define QERROR(msg)	\
> +	do {						\
> +		QPRINT(priv, KERN_ERR, msg);		\
> +	} while (0)
> +
> +struct pxa3xx_gcu_priv {
> +	void __iomem		*mmio_base;
> +	struct clk		*clk;
> +	struct pxa3xx_gcu_shared	*shared;
> +	dma_addr_t		 shared_phys;
> +	struct resource		*resource_mem;
> +	struct miscdevice	 misc_dev;
> +	struct file_operations	 misc_fops;
> +	wait_queue_head_t	 wait_idle;
> +	wait_queue_head_t	 wait_next;
> +	spinlock_t		 spinlock;
> +	struct timeval 		 base_time;
> +};
> +
> +static inline unsigned long
> +gc_readl(struct pxa3xx_gcu_priv *priv, unsigned int off)
> +{
> +	return __raw_readl(priv->mmio_base + off);
> +}
> +
> +static inline void
> +gc_writel(struct pxa3xx_gcu_priv *priv, unsigned int off, unsigned long val)
> +{
> +	__raw_writel(val, priv->mmio_base + off);
> +}
> +
> +#define QPRINT(priv, level, msg)					\
> +	do {								\
> +		struct timeval tv;					\
> +		struct pxa3xx_gcu_shared *shared = priv->shared;	\
> +		u32 base = gc_readl(priv, REG_GCRBBR);			\
> +									\
> +		do_gettimeofday(&tv);					\
> +									\
> +		printk(level "%ld.%03ld.%03ld - %-17s: %-21s (%s, "	\
> +			"hw %5d-%5d, next %5d-%5d, %svalid, STATUS "	\
> +			"0x%02lx, B 0x%08lx [%ld], E %5ld, H %5ld, "	\
> +			"T %5ld)\n",					\
> +			tv.tv_sec - priv->base_time.tv_sec,		\
> +			tv.tv_usec / 1000, tv.tv_usec % 1000,		\
> +			__func__, msg,					\
> +			shared->hw_running ? "running" : "   idle",	\
> +			shared->hw_start,				\
> +			shared->hw_end,					\
> +			shared->next_start,				\
> +			shared->next_end,				\
> +			shared->next_valid ? "  " : "in",		\
> +			gc_readl(priv, REG_GCISCR),			\
> +			gc_readl(priv, REG_GCRBBR),			\
> +			gc_readl(priv, REG_GCRBLR),			\
> +			(gc_readl(priv, REG_GCRBEXHR) - base) / 4,	\
> +			(gc_readl(priv, REG_GCRBHR) - base) / 4,	\
> +			(gc_readl(priv, REG_GCRBTR) - base) / 4);	\
> +	} while (0)
> +
> +static void pxa3xx_gcu_reset(struct pxa3xx_gcu_priv *priv)
> +{
> +	QDUMP("RESET");
> +
> +	/* disable interrupts */
> +	gc_writel(priv, REG_GCIECR, 0);
> +
> +	/* reset hardware */
> +	gc_writel(priv, REG_GCCR, GCCR_ABORT);
> +	gc_writel(priv, REG_GCCR, 0);
> +
> +	memset(priv->shared, 0, SHARED_SIZE);
> +	priv->shared->buffer_phys = priv->shared_phys;
> +	priv->shared->magic = PXA3XX_GCU_SHARED_MAGIC;
> +
> +	do_gettimeofday(&priv->base_time);
> +
> +	/* set up the ring buffer pointers */
> +	gc_writel(priv, REG_GCRBLR, 0);
> +	gc_writel(priv, REG_GCRBBR, priv->shared_phys);
> +	gc_writel(priv, REG_GCRBTR, priv->shared_phys);
> +
> +	/* enable all IRQs except EOB */
> +	gc_writel(priv, REG_GCIECR, IE_ALL & ~IE_EOB);
> +}
> +
> +static void pxa3xx_gcu_start(struct pxa3xx_gcu_priv *priv)
> +{
> +	struct pxa3xx_gcu_shared	*sh = priv->shared;
> +
> +	QDUMP("Start");
> +
> +	gc_writel(priv, REG_GCRBLR, 0);
> +
> +	/* ring base address */
> +	gc_writel(priv, REG_GCRBBR, sh->buffer_phys + sh->hw_start * 4);
> +
> +	/* ring tail address */
> +	gc_writel(priv, REG_GCRBTR, sh->buffer_phys + sh->hw_end * 4);
> +
> +	/* ring length */
> +	gc_writel(priv, REG_GCRBLR,
> +		((sh->hw_end - sh->hw_start + 63) & ~63) * 4);
> +}
> +
> +static irqreturn_t pxa3xx_gcu_handle_irq(int irq, void *ctx)
> +{
> +	struct pxa3xx_gcu_priv *priv = ctx;
> +	struct pxa3xx_gcu_shared *shared = priv->shared;
> +	u32 status = gc_readl(priv, REG_GCISCR) & IE_ALL;
> +
> +	if (!status)
> +		return IRQ_NONE;
> +
> +	QDUMP("-Interrupt");
> +
> +	spin_lock(&priv->spinlock);
> +
> +	shared->num_interrupts++;
> +
> +	if (status & IE_EEOB) {
> +		QDUMP(" [EEOB]");
> +
> +		/* next_valid means user space is not in the process of
> +		 * extending the buffer, so we can safely access the
> +		 * contents. */
> +		if (shared->next_valid &&
> +		    shared->next_start != shared->next_end) {
> +			shared->hw_start = shared->next_start;
> +			shared->hw_end = shared->next_end;
> +			shared->next_start = (shared->hw_end + 63) & ~63;
> +			shared->next_end = shared->next_start;
> +			shared->num_words += shared->hw_end - shared->hw_start;
> +			shared->num_starts++;
> +
> +			pxa3xx_gcu_start(priv);
> +
> +			wake_up_all(&priv->wait_next);
> +		} else {
> +			/* There is no more data prepared by the userspace.
> +			 * Set hw_running = 0 and wait for the next userspace
> +			 * kick-off */
> +			shared->num_idle++;
> +			shared->hw_running = 0;
> +
> +			QDUMP(" '-> Idle.");
> +
> +			/* set ring buffer length to zero */
> +			gc_writel(priv, REG_GCRBLR, 0);
> +
> +			wake_up_all(&priv->wait_next);
> +			wake_up_all(&priv->wait_idle);
> +		}
> +
> +		shared->num_done++;
> +	} else
> +		QERROR(" [???]");
> +
> +	/* Clear the interrupt */
> +	gc_writel(priv, REG_GCISCR, status);
> +	spin_unlock(&priv->spinlock);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int
> +pxa3xx_gcu_wait_idle(struct pxa3xx_gcu_priv *priv)
> +{
> +	int ret = 0;
> +
> +	QDUMP("Waiting for idle...");
> +
> +	while (priv->shared->hw_running) {
> +		int num = priv->shared->num_interrupts;
> +		u32 rbexhr = gc_readl(priv, REG_GCRBEXHR);
> +
> +		ret = wait_event_interruptible_timeout(priv->wait_idle,
> +					!priv->shared->hw_running, HZ/4);
> +
> +		if (ret < 0)
> +			break;
> +
> +		if (ret > 0)
> +			continue;
> +
> +		if (gc_readl(priv, REG_GCRBEXHR) == rbexhr &&
> +		    priv->shared->num_interrupts == num) {
> +			QERROR("TIMEOUT");
> +			ret = -ETIMEDOUT;
> +			break;
> +		}
> +	}
> +
> +	QDUMP("done");
> +
> +	return ret;
> +}
> +
> +static int
> +pxa3xx_gcu_wait_next(struct pxa3xx_gcu_priv *priv)
> +{
> +	int ret = 0;
> +	int num = priv->shared->num_interrupts;
> +
> +	QDUMP("Waiting for next...");
> +
> +	while (priv->shared->hw_running &&
> +	       num == priv->shared->num_interrupts) {
> +		u32 rbexhr = gc_readl(priv, REG_GCRBEXHR);
> +
> +		ret = wait_event_interruptible_timeout(priv->wait_next,
> +			(!priv->shared->hw_running ||
> +			 num != priv->shared->num_interrupts), HZ/4);
> +
> +		if (ret < 0)
> +			break;
> +
> +		if (ret > 0)
> +			continue;
> +
> +		if (gc_readl(priv, REG_GCRBEXHR) == rbexhr) {
> +			QERROR("TIMEOUT");
> +			ret = -ETIMEDOUT;
> +			break;
> +		}
> +	}
> +
> +	QDUMP("done");
> +
> +	return ret;
> +}
> +
> +/* Misc device layer */
> +
> +static int
> +pxa3xx_gcu_misc_ioctl(struct inode *inode, struct file *filp,
> +		 unsigned int cmd, unsigned long arg)
> +{
> +	unsigned long flags;
> +	struct pxa3xx_gcu_priv *priv =
> +		container_of(filp->f_op, struct pxa3xx_gcu_priv, misc_fops);
> +
> +	switch (cmd) {
> +	case PXA3XX_GCU_IOCTL_RESET:
> +		spin_lock_irqsave(&priv->spinlock, flags);
> +		pxa3xx_gcu_reset(priv);
> +		spin_unlock_irqrestore(&priv->spinlock, flags);
> +		return 0;
> +
> +	case PXA3XX_GCU_IOCTL_START:
> +		spin_lock_irqsave(&priv->spinlock, flags);
> +		pxa3xx_gcu_start(priv);
> +		spin_unlock_irqrestore(&priv->spinlock, flags);
> +		return 0;
> +
> +	case PXA3XX_GCU_IOCTL_WAIT_IDLE:
> +		/* Does not need to be atomic. There's a lock in user space,
> +		 * but anyhow, this is just for statistics. */
> +		priv->shared->num_wait_idle++;
> +		return pxa3xx_gcu_wait_idle(priv);
> +
> +	case PXA3XX_GCU_IOCTL_WAIT_NEXT:
> +		/* Does not need to be atomic. There's a lock in user space,
> +		 * but anyhow, this is just for statistics. */
> +		priv->shared->num_wait_next++;
> +		return pxa3xx_gcu_wait_next(priv);
> +	}
> +
> +	return -ENOSYS;
> +}
> +
> +static int
> +pxa3xx_gcu_misc_mmap(struct file *filp, struct vm_area_struct *vma)
> +{
> +	unsigned int size = vma->vm_end - vma->vm_start;
> +	struct pxa3xx_gcu_priv *priv =
> +		container_of(filp->f_op, struct pxa3xx_gcu_priv, misc_fops);
> +
> +	switch (vma->vm_pgoff) {
> +	case 0:
> +		/* hand out the shared data area */
> +		if (size != SHARED_SIZE)
> +			return -EINVAL;
> +
> +		return dma_mmap_coherent(NULL, vma,
> +			priv->shared, priv->shared_phys, size);
> +
> +	case SHARED_SIZE >> PAGE_SHIFT:
> +		/* hand out the MMIO base for direct register access
> +		 * from userspace */
> +		if (size != resource_size(priv->resource_mem))
> +			return -EINVAL;
> +
> +		vma->vm_flags |= VM_IO;
> +		vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
> +
> +		return io_remap_pfn_range(vma, vma->vm_start,
> +				priv->resource_mem->start >> PAGE_SHIFT,
> +				size, vma->vm_page_prot);
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +
> +#ifdef PXA3XX_GCU_DEBUG_TIMER
> +static struct timer_list pxa3xx_gcu_debug_timer;
> +
> +static void pxa3xx_gcu_debug_timedout(unsigned long ptr)
> +{
> +	struct pxa3xx_gcu_priv *priv = (struct pxa3xx_gcu_priv *) ptr;
> +
> +	QERROR("Timer DUMP");
> +
> +	/* init the timer structure */
> +	init_timer(&pxa3xx_gcu_debug_timer);
> +	pxa3xx_gcu_debug_timer.function = pxa3xx_gcu_debug_timedout;
> +	pxa3xx_gcu_debug_timer.data = ptr;
> +	pxa3xx_gcu_debug_timer.expires = jiffies + 5*HZ; /* one second */
> +
> +	add_timer(&pxa3xx_gcu_debug_timer);
> +}
> +
> +static void pxa3xx_gcu_init_debug_timer(void)
> +{
> +	pxa3xx_gcu_debug_timedout((unsigned long) &pxa3xx_gcu_debug_timer);
> +}
> +#else
> +static inline void pxa3xx_gcu_init_debug_timer(void) {}
> +#endif
> +
> +static int __devinit pxa3xx_gcu_probe(struct platform_device *dev)
> +{
> +	int ret, irq;
> +	struct resource *r;
> +	struct pxa3xx_gcu_priv *priv;
> +
> +	priv = kzalloc(sizeof(struct pxa3xx_gcu_priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	init_waitqueue_head(&priv->wait_idle);
> +	init_waitqueue_head(&priv->wait_next);
> +	spin_lock_init(&priv->spinlock);
> +
> +	/* we allocate the misc device structure as part of our own allocation,
> +	 * so we can get a pointer to our priv structure later on with
> +	 * container_of(). This isn't really necessary as we have a fixed minor
> +	 * number anyway, but this is to avoid statics. */
> +
> +	priv->misc_fops.owner	= THIS_MODULE;
> +	priv->misc_fops.ioctl	= pxa3xx_gcu_misc_ioctl;
> +	priv->misc_fops.mmap	= pxa3xx_gcu_misc_mmap;
> +
> +	priv->misc_dev.minor	= MISCDEV_MINOR,
> +	priv->misc_dev.name	= DRV_NAME,
> +	priv->misc_dev.fops	= &priv->misc_fops,
> +
> +	/* register misc device */
> +	ret = misc_register(&priv->misc_dev);
> +	if (ret < 0) {
> +		dev_err(&dev->dev, "misc_register() for minor %d failed\n",
> +			MISCDEV_MINOR);
> +		goto err_free_priv;
> +	}
> +
> +	/* handle IO resources */
> +	r = platform_get_resource(dev, IORESOURCE_MEM, 0);
> +	if (r == NULL) {
> +		dev_err(&dev->dev, "no I/O memory resource defined\n");
> +		ret = -ENODEV;
> +		goto err_misc_deregister;
> +	}
> +
> +	if (!request_mem_region(r->start, resource_size(r), dev->name)) {
> +		dev_err(&dev->dev, "failed to request I/O memory\n");
> +		ret = -EBUSY;
> +		goto err_misc_deregister;
> +	}
> +
> +	priv->mmio_base = ioremap_nocache(r->start, resource_size(r));
> +	if (!priv->mmio_base) {
> +		dev_err(&dev->dev, "failed to map I/O memory\n");
> +		ret = -EBUSY;
> +		goto err_free_mem_region;
> +	}
> +
> +	/* allocate dma memory */
> +	priv->shared = dma_alloc_coherent(&dev->dev, SHARED_SIZE,
> +					  &priv->shared_phys, GFP_KERNEL);
> +
> +	if (!priv->shared) {
> +		dev_err(&dev->dev, "failed to allocate DMA memory\n");
> +		ret = -ENOMEM;
> +		goto err_free_io;
> +	}
> +
> +	/* enable the clock */
> +	priv->clk = clk_get(&dev->dev, NULL);
> +	if (IS_ERR(priv->clk)) {
> +		dev_err(&dev->dev, "failed to get clock\n");
> +		ret = -ENODEV;
> +		goto err_free_dma;
> +	}
> +
> +	ret = clk_enable(priv->clk);
> +	if (ret < 0) {
> +		dev_err(&dev->dev, "failed to enable clock\n");
> +		goto err_put_clk;
> +	}
> +
> +	/* request the IRQ */
> +	irq = platform_get_irq(dev, 0);
> +	if (irq < 0) {
> +		dev_err(&dev->dev, "no IRQ defined\n");
> +		ret = -ENODEV;
> +		goto err_put_clk;
> +	}
> +
> +	ret = request_irq(irq, pxa3xx_gcu_handle_irq,
> +			  IRQF_DISABLED, DRV_NAME, priv);
> +	if (ret) {
> +		dev_err(&dev->dev, "request_irq failed\n");
> +		ret = -EBUSY;
> +		goto err_put_clk;
> +	}
> +
> +	platform_set_drvdata(dev, priv);
> +	priv->resource_mem = r;
> +	pxa3xx_gcu_reset(priv);
> +	pxa3xx_gcu_init_debug_timer();
> +
> +	dev_info(&dev->dev, "registered @0x%p, DMA 0x%p (%d bytes), IRQ %d\n",
> +			(void *) r->start, (void *) priv->shared_phys,
> +			SHARED_SIZE, irq);
> +	return 0;
> +
> +err_put_clk:
> +	clk_put(priv->clk);
> +
> +err_free_dma:
> +	dma_free_coherent(&dev->dev, SHARED_SIZE,
> +			priv->shared, priv->shared_phys);
> +
> +err_free_io:
> +	iounmap(priv->mmio_base);
> +	clk_disable(priv->clk);
> +
> +err_free_mem_region:
> +	release_mem_region(r->start, resource_size(r));
> +
> +err_misc_deregister:
> +	misc_deregister(&priv->misc_dev);
> +
> +err_free_priv:
> +	platform_set_drvdata(dev, NULL);
> +	kfree(priv);
> +	return ret;
> +}
> +
> +static int __devexit pxa3xx_gcu_remove(struct platform_device *dev)
> +{
> +	struct pxa3xx_gcu_priv *priv = platform_get_drvdata(dev);
> +	struct resource *r = priv->resource_mem;
> +
> +	misc_deregister(&priv->misc_dev);
> +	dma_free_coherent(&dev->dev, SHARED_SIZE,
> +			priv->shared, priv->shared_phys);
> +	iounmap(priv->mmio_base);
> +	release_mem_region(r->start, resource_size(r));
> +	platform_set_drvdata(dev, NULL);
> +	clk_disable(priv->clk);
> +	kfree(priv);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver pxa3xx_gcu_driver = {
> +	.probe	  = pxa3xx_gcu_probe,
> +	.remove	 = __devexit_p(pxa3xx_gcu_remove),
> +	.driver	 = {
> +		.owner  = THIS_MODULE,
> +		.name   = DRV_NAME,
> +	},
> +};
> +
> +static int __init pxa3xx_gcu_init(void)
> +{
> +	return platform_driver_register(&pxa3xx_gcu_driver);
> +}
> +
> +static void __exit pxa3xx_gcu_exit(void)
> +{
> +	platform_driver_unregister(&pxa3xx_gcu_driver);
> +}
> +
> +module_init(pxa3xx_gcu_init);
> +module_exit(pxa3xx_gcu_exit);
> +
> +MODULE_DESCRIPTION("PXA3xx graphics controller unit driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS_MISCDEV(MISCDEV_MINOR);
> +MODULE_AUTHOR("Janine Kropp <nin at directfb.org>, "
> +		"Denis Oliver Kropp <dok at directfb.org>, "
> +		"Daniel Mack <daniel at caiaq.de>");
> +
> diff --git a/drivers/video/pxa3xx-gcu.h b/drivers/video/pxa3xx-gcu.h
> new file mode 100644
> index 0000000..18d8e43
> --- /dev/null
> +++ b/drivers/video/pxa3xx-gcu.h
> @@ -0,0 +1,49 @@
> +#ifndef __PXA3XX_GCU_H__
> +#define __PXA3XX_GCU_H__
> +
> +#include <linux/types.h>
> +
> +/* Number of 32bit words in display list (ring buffer). */
> +#define PXA3XX_GCU_BUFFER_WORDS  ((256 * 1024 - 256) / 4)
> +
> +/* To be increased when breaking the ABI */
> +#define PXA3XX_GCU_SHARED_MAGIC  0x30000001
> +
> +struct pxa3xx_gcu_shared {
> +	u32            buffer[PXA3XX_GCU_BUFFER_WORDS];
> +
> +	bool           hw_running;
> +	int            hw_start;
> +	int            hw_end;
> +
> +	int            next_start;
> +	int            next_end;
> +	int            next_valid;
> +
> +	unsigned long  buffer_phys;
> +
> +	unsigned int   num_words;
> +	unsigned int   num_starts;
> +	unsigned int   num_done;
> +	unsigned int   num_interrupts;
> +	unsigned int   num_wait_idle;
> +	unsigned int   num_wait_next;
> +	unsigned int   num_idle;
> +
> +	u32            magic;
> +};
> +
> +struct pxa3xx_gcu_register {
> +	u32		address;	/* in */
> +	u32		value;		/* in/out */
> +};
> +
> +/* Initialization and synchronization.
> + * Hardware is started from user space via MMIO or via ioctl. */
> +#define PXA3XX_GCU_IOCTL_RESET		_IO('G', 0)
> +#define PXA3XX_GCU_IOCTL_START		_IO('G', 1)
> +#define PXA3XX_GCU_IOCTL_WAIT_IDLE	_IO('G', 2)
> +#define PXA3XX_GCU_IOCTL_WAIT_NEXT	_IO('G', 3)
> +
> +#endif /* __PXA3XX_GCU_H__ */
> +
> -- 
> 1.6.3.1
> 



More information about the linux-arm-kernel mailing list