[PATCH] excite: Add image capturing driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This is the excite's image capturing driver. As it does not fit in
any of the existing driver categories and is truly platform specific
(what good is a camera that cannot capture images?), it is included
in the platform.

Signed-off-by: Thomas Koeller <thomas.koeller@xxxxxxxxxxxxx>
---
 arch/mips/Kconfig                    |   11 +-
 arch/mips/basler/excite/Kconfig      |   29 +
 arch/mips/basler/excite/Makefile     |    3 +
 arch/mips/basler/excite/xicap.h      |   40 ++
 arch/mips/basler/excite/xicap_core.c |  472 ++++++++++++++
 arch/mips/basler/excite/xicap_gpi.c  | 1184 
++++++++++++++++++++++++++++++++++
 arch/mips/basler/excite/xicap_priv.h |   48 ++
 7 files changed, 1777 insertions(+), 10 deletions(-)
 create mode 100644 arch/mips/basler/excite/Kconfig
 create mode 100644 arch/mips/basler/excite/xicap.h
 create mode 100644 arch/mips/basler/excite/xicap_core.c
 create mode 100644 arch/mips/basler/excite/xicap_gpi.c
 create mode 100644 arch/mips/basler/excite/xicap_priv.h

diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index 3315a71..498b0b8 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -145,20 +145,11 @@ config BASLER_EXCITE
 	select SYS_SUPPORTS_64BIT_KERNEL
 	select SYS_SUPPORTS_BIG_ENDIAN
 	select SYS_SUPPORTS_KGDB
+	source "arch/mips/basler/excite/Kconfig"
 	help
 	  The eXcite is a smart camera platform manufactured by
 	  Basler Vision Technologies AG.
 
-config BASLER_EXCITE_PROTOTYPE
-	bool "Support for pre-release units"
-	depends on BASLER_EXCITE
-	default n
-	help
-	  Pre-series (prototype) units are different from later ones in
-	  some ways. Select this option if you have one of these. Please
-	  note that a kernel built with this option selected will not be
-	  able to run on normal units.
-
 config MIPS_COBALT
 	bool "Cobalt Server"
 	select DMA_NONCOHERENT
diff --git a/arch/mips/basler/excite/Kconfig b/arch/mips/basler/excite/Kconfig
new file mode 100644
index 0000000..3270815
--- /dev/null
+++ b/arch/mips/basler/excite/Kconfig
@@ -0,0 +1,29 @@
+config BASLER_EXCITE_PROTOTYPE
+	bool "Support for pre-release units"
+	depends on BASLER_EXCITE
+	default n
+	help
+	  Pre-series (prototype) units are different from later ones in
+	  some ways. Select this option if you have one of these. Please
+	  note that a kernel built with this option selected will not be
+	  able to run on normal units.
+
+config BASLER_EXCITE_XICAP
+	tristate "Image capturing driver"
+	depends on BASLER_EXCITE
+	default m
+	help
+	  Enable basic platform-specific support for frame capture devices.
+	  You do not want to disable this, because that would give you a
+	  camera incapable of capturing images, which is kind of pointless.
+	  This can also be compiled as a module, which will be named
+	  xicap_core.
+
+config BASLER_EXCITE_XICAP_GPI
+	tristate "Image capturing via GPI"
+	depends on BASLER_EXCITE_XICAP && GPI_RM9000
+	default m
+	help
+	  Image capturing driver for the GPI hardware found on RM9xxx
+	  chips manufactured by PMC-Sierra.
+
diff --git a/arch/mips/basler/excite/Makefile 
b/arch/mips/basler/excite/Makefile
index 519142c..1dd26a4 100644
--- a/arch/mips/basler/excite/Makefile
+++ b/arch/mips/basler/excite/Makefile
@@ -7,3 +7,6 @@ obj-$(CONFIG_BASLER_EXCITE)	+= excite_irq.o excite_prom.o 
excite_setup.o \
 
 obj-$(CONFIG_KGDB)		+= excite_dbg_io.o
 obj-m				+= excite_iodev.o
+
+obj-$(CONFIG_BASLER_EXCITE_XICAP)	+= xicap_core.o
+obj-$(CONFIG_BASLER_EXCITE_XICAP_GPI)	+= xicap_gpi.o
diff --git a/arch/mips/basler/excite/xicap.h b/arch/mips/basler/excite/xicap.h
new file mode 100644
index 0000000..6614bc4
--- /dev/null
+++ b/arch/mips/basler/excite/xicap.h
@@ -0,0 +1,40 @@
+#if ! defined(XICAP_H)
+#define XICAP_H
+
+#include <linux/ioctl.h>
+
+/* A buffer descriptor. */
+typedef struct {
+	void	*data;			/* data buffer */
+	size_t	size;			/* data buffer size */
+	void	*ctxt;			/* user-defined context pointer */
+} xicap_arg_qbuf_t;
+
+
+/*
+ * Result block passed back to user after operation completed.
+ * Todo: add time stamp field.
+ */
+typedef struct {
+	void		*data;		/* data buffer pointer */
+	void		*ctxt;		/* user context */
+	int		status;		/* buffer status, see below */
+} xicap_result_t;
+
+/* Returned buffer status values */
+#define XICAP_BUFSTAT_OK	0	/* normal return */
+#define XICAP_BUFSTAT_ABORTED	1	/* aborted by flush */
+#define XICAP_BUFSTAT_VMERR	2	/* buffer mapping error */
+
+
+
+/* Definitions for ioctl() */
+#define	XICAP_IOC_TYPE		0xbb	/* a random choice */
+
+/* Ready to grab next frame */
+#define XICAP_IOC_QBUF		_IOW(XICAP_IOC_TYPE, 0, xicap_arg_qbuf_t)
+#define XICAP_IOC_FLUSH		_IO(XICAP_IOC_TYPE, 1)
+
+#define XICAP_IOC_MAXNR		1
+
+#endif	/* ! defined(XICAP_H) */
diff --git a/arch/mips/basler/excite/xicap_core.c 
b/arch/mips/basler/excite/xicap_core.c
new file mode 100644
index 0000000..281a99c
--- /dev/null
+++ b/arch/mips/basler/excite/xicap_core.c
@@ -0,0 +1,472 @@
+/*
+ *  Copyright (C) 2004...2007 by Basler Vision Technologies AG
+ *  Author: Thomas Koeller <thomas.koeller@xxxxxxxxxxxxx>
+ *
+ *  This file contains basic support for image capturing on the
+ *  Basler eXcite intelligent camera platform, to be used by a
+ *  hardware-specific capturing driver.
+ */
+
+#include <linux/list.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/mm.h>
+#include <linux/pagemap.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <asm/bitops.h>
+#include <asm/atomic.h>
+#include <asm/uaccess.h>
+#include <asm/page.h>
+
+#include "xicap.h"
+#include "xicap_priv.h"
+
+
+
+/* Get device context from inode */
+#define cxi(__i__) container_of((__i__)->i_cdev, xicap_device_context_t, 
chardev)
+
+/* Get device context from file */
+#define cxf(__f__) ((xicap_device_context_t *) (__f__)->private_data)
+
+
+
+/* String constants */
+static const char xicap_name[] = "xicap";
+
+
+
+/* Data used for device number allocation */
+static dev_t devnum_base;
+static DECLARE_MUTEX(devnum_lock);
+static unsigned long devnum_bitmap = 0;
+
+#define MAX_DEVICES	(sizeof devnum_bitmap * 8)
+
+
+
+/* Function prototypes */
+static void xicap_device_release(struct class_device *);
+static long xicap_ioctl(struct file *, unsigned int, unsigned long);
+static unsigned int xicap_poll(struct file *, poll_table *);
+static ssize_t xicap_read(struct file *, char __user *, size_t, loff_t *);
+static int xicap_open(struct inode *, struct file *);
+static int xicap_release(struct inode *, struct file *);
+static int xicap_queue_buffer(xicap_device_context_t *,
+			      const xicap_arg_qbuf_t *);
+
+
+
+/* A class for xicap devices */
+static struct class xicap_class = {
+	.name		= (char *) xicap_name,
+	.release	= xicap_device_release,
+	.class_release	= NULL
+};
+
+
+
+/* The file operations vector */
+static struct file_operations xicap_fops = {
+	.unlocked_ioctl	= xicap_ioctl,
+	.read		= xicap_read,
+	.open		= xicap_open,
+	.release	= xicap_release,
+	.poll		= xicap_poll
+};
+
+
+
+struct xicap_devctxt {
+	struct class_device		classdev;
+	struct cdev			chardev;
+	const xicap_hw_driver_t *	hwdrv;
+	dev_t				devnum;
+	spinlock_t			compl_lock;
+	struct list_head		compl_queue;
+	atomic_t			opencnt;
+	wait_queue_head_t		wq;
+};
+
+
+
+/* A context for every class device */
+struct xicap_clsdev_ctxt {
+	xicap_hw_driver_t *	hwdrv;
+};
+
+
+
+/* Check for completed buffers */
+static inline int xicap_check_completed(xicap_device_context_t *dc)
+{
+	int r;
+	spin_lock(&dc->compl_lock);
+	r = !list_empty(&dc->compl_queue);
+	spin_unlock(&dc->compl_lock);
+	return r;
+}
+
+
+
+/* Retreive a completed buffer from the queue */
+static inline xicap_data_buffer_t *
+xicap_retreive_completed(xicap_device_context_t *dc)
+{
+	xicap_data_buffer_t * p;
+	spin_lock(&dc->compl_lock);
+	p = list_empty(&dc->compl_queue) ?
+		NULL : list_entry(dc->compl_queue.next, xicap_data_buffer_t,
+				  link);
+	if (p)
+		list_del(&p->link);
+	spin_unlock(&dc->compl_lock);
+	return p;
+}
+
+
+
+
+static long xicap_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
+{
+	int res = -ENOTTY;
+	union {
+		xicap_arg_qbuf_t qbuf;
+	} a;
+
+
+	if (unlikely(_IOC_TYPE(cmd) != XICAP_IOC_TYPE))
+		return -ENOTTY;
+
+	if ((_IOC_DIR(cmd) & _IOC_READ)
+	    && !access_ok(VERIFY_WRITE, arg, _IOC_SIZE(cmd)))
+		return -EFAULT;
+
+	if ((_IOC_DIR(cmd) & _IOC_WRITE)
+	    && !access_ok(VERIFY_READ, arg, _IOC_SIZE(cmd)))
+		return -EFAULT;
+
+	switch (cmd) {
+		case XICAP_IOC_QBUF:
+			if (_IOC_SIZE(XICAP_IOC_QBUF) != sizeof a.qbuf) {
+				res = -EINVAL;
+				break;
+			}
+			res = __copy_from_user(&a.qbuf, (void *) arg,
+					       sizeof a.qbuf) ?
+				-EINVAL : xicap_queue_buffer(cxf(f), &a.qbuf);
+			break;
+
+		case XICAP_IOC_FLUSH:
+			{
+				xicap_device_context_t * const dc = cxf(f);
+				res = dc->hwdrv->flush ?
+				      dc->hwdrv->flush(dc->classdev.dev) :
+				      -ENOTTY;
+			}
+			break;
+	}
+
+	return res;
+}
+
+
+
+static unsigned int xicap_poll(struct file *f, poll_table *tbl)
+{
+	xicap_device_context_t * const dc = cxf(f);
+	poll_wait(f, &dc->wq, tbl);
+	return POLLOUT | POLLWRNORM
+	       | (xicap_check_completed(dc) ? POLLIN | POLLRDNORM : 0);
+}
+
+
+
+static ssize_t xicap_read(struct file *f, char __user *buf, size_t len,
+			  loff_t *offs)
+{
+	int res;
+	xicap_device_context_t * const dc = cxf(f);
+	xicap_data_buffer_t *bd;
+	xicap_result_t dat;
+
+	if (unlikely(len != sizeof dat))
+		return -EINVAL;
+
+	if (unlikely(!access_ok(VERIFY_WRITE, buf, len)))
+		return -EFAULT;
+
+	if (f->f_flags & O_NONBLOCK) {
+		/* If nonblocking and no completed ops, return error status */
+		if (bd = xicap_retreive_completed(dc), !bd)
+			return -EAGAIN;
+	} else {
+		res = wait_event_interruptible(
+			dc->wq,
+			(bd = xicap_retreive_completed(dc), bd));
+		if (res)
+			return res;
+	}
+
+	if (dc->hwdrv->finish_buffer) {
+		const int i = dc->hwdrv->finish_buffer(dc->classdev.dev,
+						       bd->frmctxt);
+		if (i) {
+			kfree(bd);
+			return i;
+		}
+	}
+
+	dat.data = bd->uaddr;
+	dat.ctxt = bd->uctxt;
+	dat.status = bd->status;
+	__copy_to_user(buf, &dat, sizeof dat);
+	kfree(bd);
+
+	return sizeof dat;
+}
+
+
+
+static int xicap_open(struct inode *i, struct file *f)
+{
+	xicap_device_context_t * const dc = cxi(i);
+
+	/* Only one opener allowed */
+	if (atomic_dec_if_positive(&dc->opencnt) < 0)
+		return -EBUSY;
+
+	spin_lock_init(&dc->compl_lock);
+	INIT_LIST_HEAD(&dc->compl_queue);
+	init_waitqueue_head(&dc->wq);
+	f->private_data = cxi(i);
+	return dc->hwdrv->start(dc->classdev.dev);
+}
+
+
+
+static int xicap_release(struct inode *i, struct file *f)
+{
+	xicap_device_context_t * const dc = cxi(i);
+	dc->hwdrv->stop(dc->classdev.dev);
+
+	while (xicap_check_completed(dc))
+		kfree(xicap_retreive_completed(dc));
+
+	atomic_set(&dc->opencnt, 1);
+	return 0;
+}
+
+
+
+/* Device registration */
+xicap_device_context_t *
+xicap_device_register(struct device *dev, const xicap_hw_driver_t *hwdrv)
+{
+	int res = 0, devi = -1;
+
+	/* Set up a device context */
+	xicap_device_context_t * const dc =
+		(xicap_device_context_t *) kmalloc(sizeof *dc, GFP_KERNEL);
+	if (!dc) {
+		res = -ENOMEM;
+		goto ex;
+	}
+
+	memset(dc, 0, sizeof *dc);
+	cdev_init(&dc->chardev, &xicap_fops);
+	dc->chardev.owner = THIS_MODULE;
+	dc->classdev.dev = get_device(dev);
+	dc->hwdrv = hwdrv;
+	atomic_set(&dc->opencnt, 1);
+
+	/* Allocate a device number */
+	down(&devnum_lock);
+	if (unlikely(devnum_bitmap == ~0x0)) {
+		up(&devnum_lock);
+		res = -ENODEV;
+		goto ex;
+	}
+	devi = ffz(devnum_bitmap);
+	devnum_bitmap |= 0x1 << devi;
+	up(&devnum_lock);
+
+	/* Register the class device with its class */
+	dc->classdev.class = &xicap_class;
+	dc->classdev.devt = devi + devnum_base;
+	res = snprintf(dc->classdev.class_id, sizeof dc->classdev.class_id,
+		       "%s%u", xicap_name, devi)
+		< sizeof dc->classdev.class_id ? 0 : -ENAMETOOLONG;
+	if (!res)
+		res = class_device_register(&dc->classdev);
+	if (unlikely(res)) {
+		dc->classdev.class = NULL;
+		goto ex;
+	}
+
+	/* Register the character device */
+	res = cdev_add(&dc->chardev, devi + devnum_base, 1);
+
+ex:
+	if (res) {
+		if (dc->classdev.class)
+			class_device_unregister(&dc->classdev);
+		if (devi >= 0) {
+			down(&devnum_lock);
+			devnum_bitmap &= ~(0x1 << devi);
+			up(&devnum_lock);
+		}
+		if (dc) {
+			put_device(dc->classdev.dev);
+			kfree(dc);
+		}
+	} else {
+		dc->devnum = devi + devnum_base;
+	}
+
+	return res ? (xicap_device_context_t *) ERR_PTR(res) : dc;
+}
+
+
+
+/* Device unregistration */
+void xicap_device_unregister(xicap_device_context_t *dc)
+{
+	cdev_del(&dc->chardev);
+	class_device_unregister(&dc->classdev);
+	down(&devnum_lock);
+	devnum_base &= ~(0x1 << (dc->devnum - devnum_base));
+	up(&devnum_lock);
+}
+
+
+
+void xicap_frame_done(xicap_device_context_t *dc, xicap_data_buffer_t *bd)
+{
+	struct page **p;
+
+	for (p = bd->pages; p < bd->pages + bd->npages; p++)
+		page_cache_release(*p);
+
+	spin_lock(&dc->compl_lock);
+	list_add_tail(&bd->link, &dc->compl_queue);
+	spin_unlock(&dc->compl_lock);
+	wake_up_interruptible(&dc->wq);
+}
+
+
+
+static void xicap_device_release(struct class_device *cldev)
+{
+	xicap_device_context_t * const dc =
+		container_of(cldev, xicap_device_context_t, classdev);
+	put_device(dc->classdev.dev);
+	kfree(dc);
+}
+
+
+
+static int xicap_queue_buffer(xicap_device_context_t *dc,
+			      const xicap_arg_qbuf_t *arg)
+{
+	int res, npages;
+	xicap_data_buffer_t *bufdesc;
+
+	/* Check for buffer write permissions */
+	if (!access_ok(VERIFY_WRITE, arg->data, arg->size))
+		return -EFAULT;
+
+	npages = (PAGE_ALIGN((unsigned long ) arg->data + arg->size)
+		 - ((unsigned long ) arg->data & PAGE_MASK)) >> PAGE_SHIFT;
+
+	bufdesc = (xicap_data_buffer_t *)
+		kmalloc(sizeof *bufdesc + sizeof (struct page *) * npages,
+			GFP_KERNEL);
+	if (!bufdesc)
+		return -ENOMEM;
+
+	bufdesc->uctxt = arg->ctxt;
+	bufdesc->uaddr = arg->data;
+	bufdesc->size = arg->size;
+	bufdesc->npages = npages;
+
+	/* Get hold of the data buffer pages */
+	res = get_user_pages(current, current->mm, (unsigned long) arg->data,
+			     npages, 1, 0, bufdesc->pages, NULL);
+	if (res < 0) {
+		kfree(bufdesc);
+		return res;
+	}
+
+	bufdesc->frmctxt = dc->hwdrv->do_buffer(dc->classdev.dev, bufdesc);
+	if (IS_ERR(bufdesc->frmctxt))  {
+		int i;
+
+		for (i = 0; i < npages; i++)
+			put_page(bufdesc->pages[i]);
+		i = PTR_ERR(bufdesc->frmctxt);
+		kfree(bufdesc);
+		return i;
+	}
+
+	return 0;
+}
+
+
+
+static int __init xicap_init_module(void)
+{
+	int res;
+	static __initdata char
+		errfmt[] = KERN_ERR "%s: %s failed - error = %d\n",
+		clsreg[] = "class registration",
+		devalc[] = "device number allocation";
+
+	/* Register the xicap class */
+	res = class_register(&xicap_class);
+	if (unlikely(res)) {
+		printk(errfmt, xicap_class.name, clsreg, res);
+		return res;
+	}
+
+	/* Allocate a range of device numbers */
+	res = alloc_chrdev_region(&devnum_base, 0, MAX_DEVICES, xicap_name);
+	if (unlikely(res)) {
+		printk(errfmt, xicap_name, devalc, res);
+		class_unregister(&xicap_class);
+		return res;
+	}
+
+	return 0;
+}
+
+
+
+static void __exit xicap_cleanup_module(void)
+{
+	unregister_chrdev_region(devnum_base, MAX_DEVICES);
+	class_unregister(&xicap_class);
+}
+
+
+
+EXPORT_SYMBOL(xicap_device_register);
+EXPORT_SYMBOL(xicap_device_unregister);
+EXPORT_SYMBOL(xicap_frame_done);
+module_init(xicap_init_module);
+module_exit(xicap_cleanup_module);
+
+
+
+MODULE_AUTHOR("Thomas Koeller <thomas.koeller@xxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("Basler eXcite frame capturing core driver");
+MODULE_VERSION("0.1");
+MODULE_LICENSE("GPL");
diff --git a/arch/mips/basler/excite/xicap_gpi.c 
b/arch/mips/basler/excite/xicap_gpi.c
new file mode 100644
index 0000000..29ac093
--- /dev/null
+++ b/arch/mips/basler/excite/xicap_gpi.c
@@ -0,0 +1,1184 @@
+/*
+ *  Copyright (C) 2004...2007 by Basler Vision Technologies AG
+ *  Author: Thomas Koeller <thomas.koeller@xxxxxxxxxxxxx>
+ *
+ *  This driver uses one of the GPI channels present on the
+ *  eXcite's RM9122 SoC to implement image data capturing.
+ */
+
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+#include <linux/mm.h>
+#include <linux/dma-mapping.h>
+#include <linux/vmalloc.h>
+#include <linux/workqueue.h>
+#include <linux/moduleparam.h>
+#include <asm/types.h>
+#include <asm/io.h>
+#include <asm/rm9k-ocd.h>
+#include <asm/page.h>
+#include <asm/semaphore.h>
+
+#include <rm9k_xicap.h>
+
+#include "xicap_priv.h"
+
+
+/* Module arguments */
+unsigned int min_packet_size = (32 * L1_CACHE_BYTES);
+module_param(min_packet_size, uint, 0644);
+
+
+
+/*
+ * Using vmap() to map the buffer led to problems that still
+ * have to be investigated. For now, use a workaround.
+ */
+#define VMAP_WORKAROUND			1
+
+
+
+#define PAGES_PER_FULL_PACKET		7
+#define MAX_PAGES_PER_PACKET		(PAGES_PER_FULL_PACKET + 1)
+#define FULL_PACKET_SIZE		(PAGE_SIZE * PAGES_PER_FULL_PACKET)
+#define ABSOLUTE_MIN_PACKET_SIZE	5
+#define MAX_PACKET_SIZE			32767
+#define DUMMY_PACKET_SIZE		min_packet_size
+
+
+
+/* DMA decriptor-related definitions */
+#define XDMA_RING_SIZE_CODE		3
+#define XDMA_DESC_RING_SIZE		(512 >> XDMA_RING_SIZE_CODE)
+#define XDMA_ENABLE_REGVAL		\
+	(0x80000000 | (XDMA_RING_SIZE_CODE << 16) | (3 << 5))
+
+
+/*
+ * I/O register access macros
+ * Do not use __raw_writeq() and __raw_readq(), these do not seem to work!
+ */
+#define io_writeq(__v__, __a__)	\
+	*(volatile unsigned long long *) (__a__) = (__v__)
+#define io_readq(__a__)		(*(volatile unsigned long long *) (__a__))
+#define io_readl(__a__)		__raw_readl((__a__))
+#define io_writel(__v__, __a__)	__raw_writel((__v__), (__a__))
+#define io_readb(__a__)		__raw_readb((__a__))
+#define io_writeb(__v__, __a__)	__raw_writeb((__v__), (__a__))
+
+
+
+typedef struct __pkt		packet_t;
+typedef struct __gpi_devctxt	xicap_gpi_device_context_t;
+
+
+/* Function prototypes */
+static int __init xicap_gpi_probe(struct device *);
+static int __exit xicap_gpi_remove(struct device *);
+static int xicap_gpi_start(struct device *);
+static void xicap_gpi_stop(struct device *);
+static int xicap_gpi_flush(struct device *);
+static xicap_frame_context_t * xicap_gpi_do_buffer(struct device *, 
xicap_data_buffer_t *);
+#if VMAP_WORKAROUND
+static int xicap_gpi_finish_buffer(struct device *, xicap_frame_context_t *);
+#endif
+static void xicap_gpi_run_pkt_queue(xicap_gpi_device_context_t *);
+static void xicap_gpi_start_data(xicap_gpi_device_context_t *);
+static void xicap_gpi_stop_data(xicap_gpi_device_context_t *);
+static void xicap_gpi_pkt_finish(struct work_struct *);
+static void xicap_gpi_flush_queue(struct list_head *, unsigned int);
+static irqreturn_t xicap_gpi_int_handler(int, void *);
+
+
+
+/* A common name for various objects */
+static const char xicap_gpi_name[] = "xicap_gpi";
+
+
+
+/* The driver struct */
+static struct device_driver xicap_gpi_driver = {
+	.name		= (char *) xicap_gpi_name,
+	.bus		= &platform_bus_type,
+	.owner		= THIS_MODULE,
+	.probe		= xicap_gpi_probe,
+	.remove		= __exit_p(xicap_gpi_remove),
+	.shutdown	= NULL,
+	.suspend	= NULL,
+	.resume		= NULL
+};
+
+
+
+static const xicap_hw_driver_t xicap_gpi_hwdrv = {
+	.start		= xicap_gpi_start,
+	.stop		= xicap_gpi_stop,
+	.do_buffer	= xicap_gpi_do_buffer,
+#if VMAP_WORKAROUND
+	.finish_buffer	= xicap_gpi_finish_buffer,
+#else
+	.finish_buffer	= NULL,
+#endif
+	.flush		= xicap_gpi_flush
+};
+
+
+
+/* A work queue for cleting packets */
+struct workqueue_struct *wq;
+
+
+
+/* A DMA buffer used to work around the RM9K GPI silicon bug */
+unsigned long dummy_dma_buffer;
+
+
+
+/* XDMA read descriptor */
+typedef struct {
+	u64	cpu_part;
+	u64	xdma_part;
+} xdmadesc_t;
+
+
+
+/* A context struct for every device */
+struct __gpi_devctxt {
+	struct semaphore		lock;
+	unsigned int			slice;
+	unsigned int			irq;
+	unsigned int			fifomem_start;
+	unsigned int			fifomem_size;
+
+	void __iomem *			regaddr_fifo_rx;
+	void __iomem *			regaddr_fifo_tx;
+	void __iomem *			regaddr_xdma;
+	void __iomem *			regaddr_pktproc;
+	void __iomem *			regaddr_fpga;
+	void __iomem *			dmadesc;
+
+	dma_addr_t			dmadesc_p;
+	atomic_t			desc_cnt;
+
+	struct list_head		frm_queue;
+	unsigned int			frm_cnt;
+	unsigned int			frm_ready_cnt;
+
+	/* Core driver context pointer */
+	xicap_device_context_t *	devctxt;
+
+	/*
+	 * The interrupt queue, where packes are queued for
+	 * processing at interrupt level.
+	 */
+	spinlock_t			int_queue_lock;
+	struct list_head		int_queue;
+	unsigned int			int_errflg;
+
+
+	/* The packet queue & related stuff */
+	struct list_head		pkt_queue;
+	unsigned int			pkt_page_i;
+
+
+	void __iomem *			curdesc;
+};
+
+
+
+struct __pkt {
+	union {
+		struct list_head	link;
+		struct work_struct	wrk;
+	}			link;
+	dma_addr_t		pgaddr[MAX_PAGES_PER_PACKET];
+	xicap_frame_context_t *	fctxt;
+	volatile const u64 *	desc2;
+	u16			copy_size;
+	u16			mapped_size;
+	u16			remain_size;
+	void *			copy_src;
+	struct page **		copy_pg;
+	unsigned int		copy_offs;
+	struct page **		page;
+	unsigned int 		ndirty;
+};
+
+
+
+#define PKTFLG_FIFO_OVERFLOW	0x01
+#define PKTFLG_DATA_ERROR	0x02
+#define PKTFLG_XDMA_ERROR	0x04
+#define PKTFLG_DESC_UNDERRUN	0x08
+
+
+
+
+struct xicap_frmctxt {
+	struct list_head		link;
+	struct device *			dev;
+	unsigned int			flags;
+	xicap_gpi_device_context_t *	dc;
+	int				status;
+	u16				fpga_data[5];
+	xicap_data_buffer_t *		buf;
+	atomic_t			npkts;
+	unsigned int 			total_pkts;
+	packet_t			pkts[0];
+};
+
+
+
+static inline xicap_data_buffer_t * xicap_gpi_delete_packet(packet_t *pkt)
+{
+	xicap_frame_context_t * const fctxt = pkt->fctxt;
+	xicap_data_buffer_t * res = NULL;
+
+	if (!atomic_dec_return(&fctxt->npkts)) {
+		res = fctxt->buf;
+		res->status = fctxt->status;
+#if !VMAP_WORKAROUND
+		kfree(fctxt);
+#endif
+	}
+
+	return res;
+}
+
+
+
+static inline const struct resource *
+xicap_gpi_get_resource(struct platform_device *d, unsigned long flags,
+		 const char *basename)
+{
+	const char fmt[] = "%s_%u";
+	char buf[80];
+
+	if (unlikely(snprintf(buf, sizeof buf, fmt, basename, d->id) >= sizeof buf))
+		return NULL;
+	return platform_get_resource_byname(d, flags, buf);
+}
+
+
+
+static inline void __iomem *
+xicap_gpi_map_regs(struct platform_device *d, const char *basename)
+{
+	void * result = NULL;
+	const struct resource * const r =
+		xicap_gpi_get_resource(d, IORESOURCE_MEM, basename);
+	if (likely(r))
+		result = ioremap_nocache(r->start, r->end + 1 - r->start);
+	return result;
+}
+
+
+
+/* No hotplugging on the platform bus - use __init */
+static int __init xicap_gpi_probe(struct device *dev)
+{
+	int res;
+	xicap_gpi_device_context_t *dc = NULL;
+	struct platform_device * pdv;
+	const struct resource * rsrc;
+
+	static char __initdata
+		rsrcname_gpi_slice[] = XICAP_RESOURCE_GPI_SLICE,
+		rsrcname_fifo_blk[] = XICAP_RESOURCE_FIFO_BLK,
+		rsrcname_irq[] = XICAP_RESOURCE_IRQ,
+		rsrcname_dmadesc[] = XICAP_RESOURCE_DMADESC,
+		rsrcname_fifo_rx[] = XICAP_RESOURCE_FIFO_RX,
+		rsrcname_fifo_tx[] = XICAP_RESOURCE_FIFO_TX,
+		rsrcname_xdma[] = XICAP_RESOURCE_XDMA,
+		rsrcname_pktproc[] = XICAP_RESOURCE_PKTPROC,
+		rsrcname_pkt_stream[] = XICAP_RESOURCE_PKT_STREAM;
+
+	/* Get the platform device. */
+	if (unlikely(dev->bus != &platform_bus_type)) {
+		res = -ENODEV;
+		goto errex;
+	}
+
+	pdv = to_platform_device(dev);
+
+	/* Create and set up the device context */
+	dc = (xicap_gpi_device_context_t *)
+	      kmalloc(sizeof (xicap_gpi_device_context_t), GFP_KERNEL);
+	if (!dc) {
+		res = -ENOMEM;
+		goto errex;
+	}
+	memset(dc, 0, sizeof *dc);
+	init_MUTEX(&dc->lock);
+
+	/* Evaluate resources */
+	res = -ENXIO;
+
+	rsrc = xicap_gpi_get_resource(pdv, 0, rsrcname_gpi_slice);
+	if (unlikely(!rsrc)) goto errex;
+	dc->slice = rsrc->start;
+
+	rsrc = xicap_gpi_get_resource(pdv, 0, rsrcname_fifo_blk);
+	if (unlikely(!rsrc)) goto errex;
+	dc->fifomem_start = rsrc->start;
+	dc->fifomem_size = rsrc->end + 1 - rsrc->start;
+
+	rsrc = xicap_gpi_get_resource(pdv, IORESOURCE_IRQ, rsrcname_irq);
+	if (unlikely(!rsrc)) goto errex;
+	dc->irq = rsrc->start;
+
+	rsrc = xicap_gpi_get_resource(pdv, IORESOURCE_MEM, rsrcname_dmadesc);
+	if (unlikely(!rsrc)) goto errex;
+	if (unlikely((rsrc->end + 1 - rsrc->start)
+	             < (XDMA_DESC_RING_SIZE * sizeof (xdmadesc_t))))
+		goto errex;
+	dc->dmadesc_p = (dma_addr_t) rsrc->start;
+	dc->dmadesc = ioremap_nocache(rsrc->start, rsrc->end + 1 - rsrc->start);
+
+	dc->regaddr_fifo_rx = xicap_gpi_map_regs(pdv, rsrcname_fifo_rx);
+	dc->regaddr_fifo_tx = xicap_gpi_map_regs(pdv, rsrcname_fifo_tx);
+	dc->regaddr_xdma = xicap_gpi_map_regs(pdv, rsrcname_xdma);
+	dc->regaddr_pktproc = xicap_gpi_map_regs(pdv, rsrcname_pktproc);
+	dc->regaddr_fpga = xicap_gpi_map_regs(pdv, rsrcname_pkt_stream);
+
+	if (unlikely(!dc->regaddr_fifo_rx || !dc->regaddr_fifo_tx
+	    || !dc->regaddr_xdma || !dc->regaddr_pktproc || !dc->regaddr_fpga
+	    || !dc->dmadesc))
+		goto errex;
+
+	/* Register the device with the core */
+	dc->devctxt = xicap_device_register(dev, &xicap_gpi_hwdrv);
+	res = IS_ERR(dc->devctxt) ? PTR_ERR(dc->devctxt) : 0;
+
+errex:
+	if (res) {
+		if (dc->regaddr_fifo_rx) iounmap(dc->regaddr_fifo_rx);
+		if (dc->regaddr_fifo_tx) iounmap(dc->regaddr_fifo_tx);
+		if (dc->regaddr_xdma) iounmap(dc->regaddr_xdma);
+		if (dc->regaddr_pktproc) iounmap(dc->regaddr_pktproc);
+		if (dc->regaddr_fpga) iounmap(dc->regaddr_fpga);
+		if (dc->dmadesc) iounmap(dc->dmadesc);
+		if (dc) kfree(dc);
+		dev_dbg("%s: %s failed, error = %d\n", xicap_gpi_name,
+			__func__, res);
+	} else {
+		dev->driver_data = dc;
+		dev_dbg("%s: Context at %p\n", xicap_gpi_name, dc);
+	}
+
+	return res;
+}
+
+
+
+static int __exit xicap_gpi_remove(struct device *dev)
+{
+	xicap_gpi_device_context_t * const dc =
+		(xicap_gpi_device_context_t *) dev->driver_data;
+
+	xicap_device_unregister(dc->devctxt);
+	kfree(dc);
+	dev->driver_data = NULL;
+	return 0;
+}
+
+
+
+static int xicap_gpi_start(struct device *dev)
+{
+	xicap_gpi_device_context_t * const dc =
+		(xicap_gpi_device_context_t *) dev->driver_data;
+	u32 reg;
+	int res;
+
+	/* Lock the device context */
+	down(&dc->lock);
+
+	/* Device context initialization */
+	INIT_LIST_HEAD(&dc->pkt_queue);
+	INIT_LIST_HEAD(&dc->frm_queue);
+	INIT_LIST_HEAD(&dc->int_queue);
+	spin_lock_init(&dc->int_queue_lock);
+	dc->int_errflg = 0;
+
+	lock_titan_regs();
+
+	/* Disable the slice status interrupts */
+	reg = titan_readl(0x0050) & ~(0x1f << (dc->slice * 5));
+	titan_writel(reg, 0x0050);
+
+	/* Disable the XDMA interrupts for this slice */
+	reg = titan_readl(0x0058) & ~(0xff << (dc->slice * 8));
+	titan_writel(reg, 0x0058);
+
+	unlock_titan_regs();
+
+	xicap_gpi_start_data(dc);
+
+	res = request_irq(dc->irq, xicap_gpi_int_handler,
+			  SA_SHIRQ, xicap_gpi_name, dc);
+	if (unlikely(res))
+		return res;
+
+	lock_titan_regs();
+
+	/* Enable the slice status interrupts */
+	reg = titan_readl(0x0050) | (0x2 << (dc->slice * 5));
+	titan_writel(reg, 0x0050);
+
+	/* Enable the XDMA data interrupt */
+	reg = 0xff << (dc->slice * 8);
+	titan_writel(reg, 0x0048);
+	titan_writel(reg, 0x004c);
+	reg = titan_readl(0x0058);
+	titan_writel(reg | (0x1 << (dc->slice * 8)), 0x0058);
+
+	unlock_titan_regs();
+
+	/* Release the device context and exit */
+	up(&dc->lock);
+	return 0;
+}
+
+
+
+static void xicap_gpi_start_data(xicap_gpi_device_context_t *dc)
+{
+	unsigned int i;
+
+	/* Reset all XDMA channels for this slice */
+	io_writel(0x80080000, dc->regaddr_xdma + 0x0000);
+	io_writel(0x80080000, dc->regaddr_xdma + 0x0040);
+	io_writel(0x80080000, dc->regaddr_xdma + 0x0080);
+	io_writel(0x80080000, dc->regaddr_xdma + 0x00c0);
+
+	/* Reset & enable the XDMA slice interrupts */
+	io_writel(0x80068002, dc->regaddr_xdma + 0x000c);
+	io_writel(0x00008002, dc->regaddr_xdma + 0x0010);
+
+	dc->pkt_page_i = 0;
+	dc->frm_ready_cnt = dc->frm_cnt = 0;
+
+	/* Set up the XDMA descriptor ring & enable the XDMA */
+	dc->curdesc = dc->dmadesc;
+	atomic_set(&dc->desc_cnt, XDMA_DESC_RING_SIZE);
+	io_writel(dc->dmadesc_p, dc->regaddr_xdma + 0x0018);
+	wmb();
+	memset(dc->dmadesc, 0, XDMA_DESC_RING_SIZE * sizeof (xdmadesc_t));
+	io_writel(XDMA_ENABLE_REGVAL, dc->regaddr_xdma + 0x0000);
+
+	/*
+	 * Enable the rx fifo we are going to use. Disable the
+	 * unused ones as well as the tx fifo.
+	 */
+	io_writel(0x00100000 | ((dc->fifomem_size) << 10)
+		  | dc->fifomem_start,
+		  dc->regaddr_fifo_rx + 0x0000);
+	wmb();
+	io_writel((10 << 20) | (10 << 10) | 128, dc->regaddr_fifo_rx
+		  + 0x0004);
+	io_writel(0x00100400, dc->regaddr_fifo_rx + 0x000c);
+	io_writel(0x00100400, dc->regaddr_fifo_rx + 0x0018);
+	io_writel(0x00100400, dc->regaddr_fifo_rx + 0x0024);
+	io_writel(0x00100400, dc->regaddr_fifo_tx + 0x0000);
+
+	/* Reset any pending interrupt, then enable fifo */
+	titan_writel(0xf << (dc->slice * 4), 0x482c);
+	wmb();
+	io_writel(0x00200000 | ((dc->fifomem_size) << 10)
+		  | dc->fifomem_start,
+		  dc->regaddr_fifo_rx + 0x0000);
+
+	/* Enable the packet processor */
+	io_writel(0x00000000, dc->regaddr_pktproc + 0x0000);
+	wmb();
+	io_writel(0x0000001f, dc->regaddr_pktproc + 0x0008);
+	io_writel(0x00000e08, dc->regaddr_pktproc + 0x0010);
+	io_writel(0x0000080f, dc->regaddr_pktproc + 0x0014);
+	io_writel(0x000003ff, dc->regaddr_pktproc + 0x0018);
+	io_writel(0x00000100, dc->regaddr_pktproc + 0x0038);
+	wmb();
+	io_writel(0x00000001, dc->regaddr_pktproc + 0x0000);
+
+	/* Disable address filtering */
+	io_writel(0x0, dc->regaddr_pktproc + 0x0120);
+	io_writel(0x2, dc->regaddr_pktproc + 0x0124);
+	for (i = 0; i < 8; i++) {
+		io_writel(  i, dc->regaddr_pktproc + 0x0128);
+		wmb();
+		io_writel(0x0, dc->regaddr_pktproc + 0x0100);
+		io_writel(0x0, dc->regaddr_pktproc + 0x0104);
+		io_writel(0x0, dc->regaddr_pktproc + 0x0108);
+		io_writel(0x0, dc->regaddr_pktproc + 0x010c);
+		wmb();
+	}
+
+	io_writel(0x1, dc->regaddr_pktproc + 0x012c);
+
+}
+
+
+
+static void xicap_gpi_stop_data(xicap_gpi_device_context_t *dc)
+{
+	/* Shut down the data transfer */
+	io_writeb(0x01, dc->regaddr_fpga + 0x000b);
+
+	/* Reset the XDMA channel */
+	io_writel(0x80080000, dc->regaddr_xdma + 0x0000);
+
+	/* Disable the FIFO */
+	io_writel(0x00100400, dc->regaddr_fifo_rx + 0x0000);
+
+	/* Disable the packet processor */
+	io_writel(0x00000000, dc->regaddr_pktproc + 0x0000);
+
+	dc->frm_ready_cnt = 0;
+	INIT_LIST_HEAD(&dc->frm_queue);
+}
+
+
+
+static void xicap_gpi_flush_queue(struct list_head *l, unsigned int stat)
+{
+	while (!list_empty(l)) {
+		packet_t * const pkt =
+			list_entry(l->next, packet_t, link.link);
+		xicap_gpi_device_context_t * const dc = pkt->fctxt->dc;
+		const dma_addr_t *pa = pkt->pgaddr;
+		xicap_data_buffer_t * buf;
+
+		list_del(&pkt->link.link);
+
+		while (pkt->mapped_size) {
+			size_t sz = pkt->mapped_size;
+			if (sz > PAGE_SIZE)
+				sz = PAGE_SIZE;
+			pkt->mapped_size -= sz;
+			dma_unmap_page(pkt->fctxt->dev, *pa++, sz,
+				       DMA_FROM_DEVICE);
+		}
+
+		if (pkt->copy_size) {
+			free_pages((unsigned long) pkt->copy_src,
+				   pkt->copy_size > PAGE_SIZE ? 1 : 0);
+			pkt->copy_size = 0;
+		}
+
+		buf = xicap_gpi_delete_packet(pkt);
+		if (buf) {
+			buf->status = stat;
+			xicap_frame_done(dc->devctxt, buf);
+		}
+	}
+}
+
+
+
+static void xicap_gpi_stop(struct device *dev)
+{
+	u32 reg;
+	LIST_HEAD(l);
+	xicap_gpi_device_context_t * const dc =
+		(xicap_gpi_device_context_t *) dev->driver_data;
+
+	lock_titan_regs();
+
+	/* Disable the slice status interrupts */
+	reg = titan_readl(0x0050) & ~(0x1f << (dc->slice * 5));
+	titan_writel(reg, 0x0050);
+
+	/* Disable the XDMA interrupts for this slice */
+	reg = titan_readl(0x0058) & ~(0xff << (dc->slice * 8));
+	titan_writel(reg, 0x0058);
+
+	unlock_titan_regs();
+	flush_workqueue(wq);
+	down(&dc->lock);
+	xicap_gpi_stop_data(dc);
+
+	/* Now clean up the packet & int queues */
+	spin_lock_irq(&dc->int_queue_lock);
+	list_splice_init(&dc->int_queue, &l);
+	spin_unlock_irq(&dc->int_queue_lock);
+	list_splice_init(&dc->pkt_queue, l.prev);
+	xicap_gpi_flush_queue(&l, ~0x0);
+	up(&dc->lock);
+
+	/* Detach interrupt handler */
+	free_irq(dc->irq, dc);
+}
+
+
+
+static int xicap_gpi_flush(struct device *dev)
+{
+	u32 reg;
+	xicap_gpi_device_context_t * const dc =
+		(xicap_gpi_device_context_t *) dev->driver_data;
+	LIST_HEAD(l);
+
+	lock_titan_regs();
+
+	/* Disable the slice status interrupts */
+	reg = titan_readl(0x0050) & ~(0x1f << (dc->slice * 5));
+	titan_writel(reg, 0x0050);
+
+	/* Disable the XDMA interrupts for this slice */
+	reg = titan_readl(0x0058) & ~(0xff << (dc->slice * 8));
+	titan_writel(reg, 0x0058);
+
+	unlock_titan_regs();
+
+	/* Now clean up the packet & int queues */
+	flush_workqueue(wq);
+	down(&dc->lock);
+	xicap_gpi_stop_data(dc);
+	spin_lock_irq(&dc->int_queue_lock);
+	list_splice_init(&dc->int_queue, &l);
+	spin_unlock_irq(&dc->int_queue_lock);
+	list_splice_init(&dc->pkt_queue, l.prev);
+	xicap_gpi_flush_queue(&l, XICAP_BUFSTAT_ABORTED);
+	xicap_gpi_start_data(dc);
+	up(&dc->lock);
+
+	lock_titan_regs();
+
+	/* Re-enable the slice status interrupts */
+	reg = titan_readl(0x0050) | (0x2 << (dc->slice * 5));
+	titan_writel(reg, 0x0050);
+
+	/* Re-enable the XDMA data interrupt */
+	reg = 0xff << (dc->slice * 8);
+	titan_writel(reg, 0x0048);
+	titan_writel(reg, 0x004c);
+	reg = titan_readl(0x0058);
+	wmb();
+	titan_writel(reg | (0x1 << (dc->slice * 8)), 0x0058);
+
+	unlock_titan_regs();
+
+	return 0;
+}
+
+
+
+static xicap_frame_context_t *
+xicap_gpi_do_buffer(struct device *dev, xicap_data_buffer_t *buf)
+{
+	unsigned long head_buf = 0, tail_buf = 0;
+	u16 head_size = 0, tail_size = 0, full_size = FULL_PACKET_SIZE;
+	u16 total_packets;
+	size_t sz = buf->size;
+	unsigned int full_packets = 0, head_order = 0, i, request_frame = 1;
+	const unsigned int boffs = (unsigned long) buf->uaddr & ~PAGE_MASK;
+	packet_t *pkt;
+	struct page ** pg;
+	LIST_HEAD(packets);
+	xicap_frame_context_t * fctxt;
+	xicap_gpi_device_context_t * const dc =
+		(xicap_gpi_device_context_t *) dev->driver_data;
+
+	if (unlikely(sz < ABSOLUTE_MIN_PACKET_SIZE))
+		return ERR_PTR(-EINVAL);
+
+	/*
+	 * If the buffer is not page aligned, the first part of it
+	 * (the 'head') is DMA'ed into a temporary buffer and later
+	 * copied to the user's buffer. The size of the head is chosen
+	 * so that the remaining part of the buffer is page aligned.
+	 */
+	if (boffs) {
+		head_size = PAGE_SIZE - boffs;
+		if (head_size < min_packet_size) {
+			head_size += PAGE_SIZE;
+			head_order = 1;
+		}
+		if (head_size > sz) {
+			head_size = sz;
+			head_order = (head_size > PAGE_SIZE) ? 1 : 0;
+		}
+		head_buf = __get_dma_pages(GFP_KERNEL, head_order);
+		if (!head_buf)
+			return ERR_PTR(-ENOMEM);
+
+		/* Compute the residual buffer size */
+		sz -= head_size;
+	}
+
+	/*
+	 * Now compute the number of full-sized packets, and the size
+	 * of the last ('tail') packet.
+	 */
+	if (sz) {
+		full_packets = sz / FULL_PACKET_SIZE;
+		tail_size = sz % FULL_PACKET_SIZE;
+	}
+
+	/*
+	 * If the tail packet is less than a page, it can be merged with
+	 * the previous one. This also covers the case where the size of
+	 * the tail packet is less than min_packet_size.
+	 */
+	if ((tail_size < PAGE_SIZE) && full_packets) {
+		full_packets--;
+		tail_size += FULL_PACKET_SIZE;
+	}
+
+	/*
+	 * The XDMA will pad the last packet to the next cache line
+	 * boundary, so in order to avoid writing beyond the user's
+	 * buffer, we need to use a temporary buffer if the tail packet
+	 * size is not an integer multiple of the cache line size.
+	 */
+	if (tail_size % L1_CACHE_BYTES) {
+		tail_buf = __get_dma_pages(GFP_KERNEL, 0);
+		if (unlikely(!tail_buf)) {
+			if (head_buf)
+				free_pages(head_buf, head_order);
+			return ERR_PTR(-ENOMEM);
+		}
+	}
+
+	/*
+	 * Now we know how many packets to process for the buffer, so we
+	 * can allocate a packet set. Add one extra dummy packet (silicon
+	 * bug workaround).
+	 */
+	total_packets =
+		full_packets + (head_size ? 1 : 0) + (tail_size ? 1 : 0) + 1;
+	fctxt = kmalloc(
+		sizeof (xicap_frame_context_t) + sizeof (packet_t) * total_packets,
+		GFP_KERNEL);
+	if (unlikely(!fctxt)) {
+		if (tail_buf)
+			free_pages(tail_buf, 0);
+		if (head_buf)
+			free_pages(head_buf, head_order);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	fctxt->buf = buf;
+	fctxt->dev = dev;
+	fctxt->flags = 0;
+	fctxt->status = XICAP_BUFSTAT_OK;
+	atomic_set(&fctxt->npkts, total_packets);
+	fctxt->total_pkts = total_packets;
+	pkt = &fctxt->pkts[0];
+	pg = buf->pages;
+	fctxt->dc = (xicap_gpi_device_context_t *) dev->driver_data;
+
+	/* Set up the head packet descriptor */
+	if (head_size) {
+		struct page * const p = virt_to_page((void *) head_buf);
+
+		pkt->page = pkt->copy_pg = pg;
+
+		if (head_order) {
+			pkt->pgaddr[0] = dma_map_page(dev, p, 0, PAGE_SIZE,
+						      DMA_FROM_DEVICE);
+			pkt->pgaddr[1] = dma_map_page(dev, p + 1, 0,
+						      head_size - PAGE_SIZE,
+						      DMA_FROM_DEVICE);
+			pkt->ndirty = 2;
+			pg += 2;
+		} else {
+			pkt->pgaddr[0] = dma_map_page(dev, p, 0, head_size,
+						      DMA_FROM_DEVICE);
+			pkt->ndirty = 1;
+			pg++;
+		}
+
+		pkt->copy_src = (void *) head_buf;
+		pkt->copy_offs = ((unsigned long)buf->uaddr & ~PAGE_MASK);
+		pkt->remain_size = pkt->mapped_size = pkt->copy_size = head_size;
+		pkt->fctxt = fctxt;
+		list_add_tail(&pkt->link.link, &packets);
+		pkt++;
+	}
+
+	/* Set up descriptors for all full-sized packets */
+	while (full_packets--) {
+		pkt->remain_size = pkt->mapped_size = FULL_PACKET_SIZE;
+		pkt->copy_size = 0;
+		pkt->page = pg;
+		pkt->ndirty = PAGES_PER_FULL_PACKET;
+
+		for (i = 0; i < PAGES_PER_FULL_PACKET; i++)
+			pkt->pgaddr[i] = dma_map_page(dev, *pg++, 0, PAGE_SIZE,
+						      DMA_FROM_DEVICE);
+
+		pkt->fctxt = fctxt;
+		list_add_tail(&pkt->link.link, &packets);
+		pkt++;
+	}
+
+	/* Set up the descriptor for the tail packet */
+	if (tail_size) {
+		const size_t cs = tail_size % PAGE_SIZE;
+
+		pkt->remain_size = pkt->mapped_size = tail_size;
+		pkt->page = pg;
+		pkt->ndirty = tail_size / PAGE_SIZE;
+
+		for (i = 0; i < pkt->ndirty; i++)
+			pkt->pgaddr[i] = dma_map_page(dev, *pg++, 0, PAGE_SIZE,
+						      DMA_FROM_DEVICE);
+
+		if (cs) {
+			if (tail_buf) {
+				struct page * const p =
+					virt_to_page((void *) tail_buf);
+				pkt->pgaddr[i] =
+					dma_map_page(dev, p, 0, cs,
+						     DMA_FROM_DEVICE);
+				pkt->copy_src = (void *) tail_buf;
+				pkt->copy_size = cs;
+				pkt->copy_pg = pg;
+				pkt->copy_offs = 0;
+			} else {
+				pkt->pgaddr[i] = dma_map_page(dev, *pg, 0, cs,
+							      DMA_FROM_DEVICE);
+				pkt->copy_size = 0;
+			}
+			pkt->ndirty++;
+		} else {
+			pkt->copy_size = 0;
+		}
+
+		pkt->fctxt = fctxt;
+		list_add_tail(&pkt->link.link, &packets);
+		pkt++;
+	}
+
+	/*
+	 * Set up the trailing dummy packet (silicon bug workaround).
+	 *
+	 * The XDMA does not generate an interrupt if the memory
+	 * controlled by the last DMA descriptor is filled with data
+	 * to the last byte. We work around this by adding a dummy packet
+	 * after each frame. This guarantees that there always is an
+	 * active DMA descriptor after the last one of the frame, and so
+	 * the XDMA will interrupt. The dummy packet size is chosen so that
+	 * the dummy buffer is not entirely filled and hence will always
+	 * generate an interrupt, too.
+	 */
+	pkt->remain_size = pkt->mapped_size = DUMMY_PACKET_SIZE;
+	pkt->page = NULL;
+	pkt->copy_size = 0;
+	pkt->ndirty = 0;
+
+	for (i = 0; i < DUMMY_PACKET_SIZE / PAGE_SIZE; i++)
+		pkt->pgaddr[i] =
+			dma_map_page(dev,
+				     virt_to_page((void *) dummy_dma_buffer),
+				     0, PAGE_SIZE, DMA_FROM_DEVICE);
+
+	pkt->pgaddr[i] =
+		dma_map_page(dev,
+			     virt_to_page((void *) dummy_dma_buffer),
+			     0, DUMMY_PACKET_SIZE % PAGE_SIZE,
+			     DMA_FROM_DEVICE);
+
+	pkt->fctxt = fctxt;
+	list_add_tail(&pkt->link.link, &packets);
+
+	/*
+	 * Set up data to send to the FPGA. The total number of packets
+	 * requested does _not_ include the dummy packet. If DUMMY_PACKET_SIZE
+	 * is not zero, a dummy packet is always sent.
+	 */
+	fctxt->fpga_data[0] = cpu_to_le16(fctxt->pkts[0].mapped_size);
+	fctxt->fpga_data[1] = cpu_to_le16(full_size);
+	fctxt->fpga_data[2] = cpu_to_le16((pkt - 1)->mapped_size);
+	fctxt->fpga_data[3] = cpu_to_le16(DUMMY_PACKET_SIZE);
+	fctxt->fpga_data[4] = cpu_to_le16(total_packets - 1);
+
+	down(&dc->lock);
+
+	/* Now enqueue all the packets in one step */
+	list_splice(&packets, dc->pkt_queue.prev);
+	if (!dc->frm_cnt++)
+		xicap_gpi_run_pkt_queue(dc);
+
+	dev_dbg("%s: created packet set %p\n"
+		"\thead size = %#06x, full size = %#06x, "
+		"tail size = %#06x, dummy size = %#06x\n"
+		"\ttotal packets = %u, active DMA descriptors = %u\n",
+		xicap_gpi_name, fctxt, head_size, full_size, tail_size,
+		DUMMY_PACKET_SIZE, total_packets,
+		io_readl(dc->regaddr_xdma + 0x0008));
+
+	/*
+	 * If less than two frames are currently pending, we can send
+	 * the frame parameters to the FPGA right away. Otherwise, we
+	 * need to queue the frame.
+	 */
+	if (dc->frm_ready_cnt++ > 1) {
+		list_add_tail(&fctxt->link, &dc->frm_queue);
+		request_frame = 0;
+	}
+
+	up(&dc->lock);
+
+	if (request_frame)
+		memcpy_toio(dc->regaddr_fpga, fctxt->fpga_data,
+			    sizeof fctxt->fpga_data);
+
+	return fctxt;
+}
+
+
+
+static void xicap_gpi_run_pkt_queue(xicap_gpi_device_context_t *dc)
+{
+	packet_t *pkt = list_empty(&dc->pkt_queue) ? NULL :
+			list_entry(dc->pkt_queue.next, packet_t, link.link);
+
+	while (pkt) {
+		int i;
+		size_t sz;
+
+		/* Stop, if no more free DMA descriptors */
+		if (0 > atomic_dec_if_positive(&dc->desc_cnt))
+			break;
+
+		i = dc->pkt_page_i++;
+
+		sz = pkt->remain_size;
+		if (sz > PAGE_SIZE)
+			sz = PAGE_SIZE;
+		pkt->remain_size -= sz;
+
+		/* Set up the DMA descriptor */
+		io_writeq(cpu_to_be64(pkt->pgaddr[i]), dc->curdesc);
+		if (i) {
+			io_writeq(cpu_to_be64(0x1ULL << 53), dc->curdesc + 8);
+		} else {
+			io_writeq(cpu_to_be64((0x1ULL << 63) | (0x1ULL << 53)),
+				  dc->curdesc + 8);
+			pkt->desc2 = dc->curdesc + 8;
+		}
+
+		dev_dbg("%s: Desc. %2u = %016Lx, %016Lx\n",
+			xicap_gpi_name,
+			(dc->curdesc - dc->dmadesc) / sizeof (xdmadesc_t),
+			(u64) pkt->pgaddr[i], pkt->desc2);
+
+		dc->curdesc += sizeof (xdmadesc_t);
+		if ((dc->curdesc - dc->dmadesc) >=
+		    (XDMA_DESC_RING_SIZE * sizeof (xdmadesc_t)))
+			dc->curdesc = dc->dmadesc;
+
+		/* Add the packet to the interrupt queue */
+		if (!pkt->remain_size) {
+			spin_lock_irq(&dc->int_queue_lock);
+			list_move_tail(&pkt->link.link, &dc->int_queue);
+			spin_unlock_irq(&dc->int_queue_lock);
+			dc->pkt_page_i = 0;
+			pkt = list_empty(&dc->pkt_queue) ? NULL :
+			      list_entry(dc->pkt_queue.next, packet_t,
+					 link.link);
+		}
+
+		io_writel(1, dc->regaddr_xdma + 0x0008);
+	}
+}
+
+
+#if VMAP_WORKAROUND
+static int
+xicap_gpi_finish_buffer(struct device *dev, xicap_frame_context_t *frmctxt)
+{
+	struct __pkt * const pkt_h = frmctxt->pkts,
+		     * const pkt_t = frmctxt->pkts + frmctxt->total_pkts - 2;
+
+	if (pkt_h->copy_size) {
+		__copy_to_user(frmctxt->buf->uaddr, pkt_h->copy_src,
+			       pkt_h->copy_size);
+		free_pages((unsigned long) pkt_h->copy_src,
+			   (pkt_h->copy_size > PAGE_SIZE) ? 1 : 0);
+	}
+
+	if (2 < frmctxt->total_pkts) {
+		if (pkt_t->copy_size) {
+			__copy_to_user(frmctxt->buf->uaddr + frmctxt->buf->size
+				       - pkt_t->copy_size,
+				       pkt_t->copy_src,
+				       pkt_t->copy_size);
+			free_pages((unsigned long) pkt_t->copy_src, 0);
+		}
+	}
+
+	kfree(frmctxt);
+	return 0;
+}
+#endif
+
+
+
+static void xicap_gpi_pkt_finish(struct work_struct *work)
+{
+	packet_t * const pkt = container_of(work, packet_t, link.wrk);
+	xicap_gpi_device_context_t * const dc =
+		(xicap_gpi_device_context_t *) pkt->fctxt->dev->driver_data;
+	const dma_addr_t *pa = pkt->pgaddr;
+	xicap_data_buffer_t * buf;
+
+	while (pkt->mapped_size) {
+		size_t sz = pkt->mapped_size;
+		if (sz > PAGE_SIZE)
+			sz = PAGE_SIZE;
+		pkt->mapped_size -= sz;
+		dma_unmap_page(pkt->fctxt->dev, *pa++, sz, DMA_FROM_DEVICE);
+	}
+
+#if !VMAP_WORKAROUND
+	if (pkt->copy_size) {
+		const unsigned int page_order =
+			(pkt->copy_size > PAGE_SIZE) ? 1 : 0;
+		void * const dst = vmap(pkt->copy_pg, 0x1 << page_order,
+					VM_MAP, PAGE_USERIO);
+
+		if (dst) {
+			memcpy(dst + pkt->copy_offs, pkt->copy_src,
+			       pkt->copy_size);
+			free_pages((unsigned long) pkt->copy_src, page_order);
+			vunmap(dst);
+		} else {
+			pkt->fctxt->status = XICAP_BUFSTAT_VMERR;
+		}
+	}
+#endif
+
+	while (pkt->ndirty--)
+		set_page_dirty_lock(*pkt->page++);
+
+	down(&dc->lock);
+	buf = xicap_gpi_delete_packet(pkt);
+	if (buf) {
+		xicap_frame_context_t *fctxt = NULL;
+		xicap_frame_done(dc->devctxt, buf);
+		dc->frm_cnt--;
+		if (dc->frm_ready_cnt-- > 2) {
+			fctxt = list_entry(dc->frm_queue.next,
+					  xicap_frame_context_t, link);
+			list_del(&fctxt->link);
+		}
+
+		if (fctxt)
+			memcpy_toio(dc->regaddr_fpga, fctxt->fpga_data,
+				    sizeof fctxt->fpga_data);
+	}
+
+	if (dc->frm_cnt)
+		xicap_gpi_run_pkt_queue(dc);
+	up(&dc->lock);
+}
+
+
+
+/* The interrupt handler */
+static irqreturn_t xicap_gpi_int_handler(int irq, void *arg)
+{
+	xicap_gpi_device_context_t * const dc =
+		(xicap_gpi_device_context_t *) arg;
+	u32 flg_dmadata, flg_slstat, flg_fofl, reg;
+
+	/* Check, if this interrupt is for us */
+	flg_dmadata = titan_readl(0x0048) & (0x1 << (dc->slice * 8));
+	flg_slstat = titan_readl(0x0040) & (0x1f << (dc->slice * 5));
+	flg_fofl = titan_readl(0x482c) & ((dc->slice * 4));
+	if (!(flg_dmadata | flg_slstat | flg_fofl))
+		return IRQ_NONE;	/* not our interrupt */
+
+	if (unlikely(flg_slstat)) {
+		reg = io_readl(dc->regaddr_pktproc + 0x000c) & 0x1f;
+		io_writel(reg, dc->regaddr_pktproc + 0x000c);
+		dc->int_errflg |= PKTFLG_DATA_ERROR;
+	}
+
+	if (unlikely(flg_fofl)) {
+		titan_writel(flg_fofl, 0x482c);
+		dc->int_errflg |= PKTFLG_FIFO_OVERFLOW;
+	}
+
+	reg = io_readl(dc->regaddr_xdma + 0x000c) & 0x00008002;
+	if (unlikely(reg)) {
+		io_writel(reg, dc->regaddr_xdma + 0x000c);
+		dc->int_errflg |= ((reg & 0x00008000) ? PKTFLG_XDMA_ERROR : 0)
+				  | ((reg & 0x00000002) ? PKTFLG_DESC_UNDERRUN : 0);
+	}
+
+	if (likely(flg_dmadata)) {
+		titan_writel(flg_dmadata, 0x0048);
+		spin_lock(&dc->int_queue_lock);
+		while (!list_empty(&dc->int_queue)) {
+			packet_t * const pkt = list_entry(dc->int_queue.next,
+							  packet_t, link.link);
+
+			/* If the packet is not completed yet, exit */
+			if (be64_to_cpu(*pkt->desc2) & (0x1ULL << 53))
+				break;
+			list_del(&pkt->link.link);
+
+			/* Release the DMA descriptors used by this packet */
+			atomic_add(PAGE_ALIGN(pkt->mapped_size) >> PAGE_SHIFT, &dc->desc_cnt);
+
+			/* All further processing is deferred to a worker thread */
+			INIT_WORK(&pkt->link.wrk, xicap_gpi_pkt_finish);
+			if(unlikely(!queue_work(wq, &pkt->link.wrk)))
+				panic("%s: worker thread error\n",
+				      xicap_gpi_name);
+		}
+		spin_unlock(&dc->int_queue_lock);
+	}
+
+	return IRQ_HANDLED;
+}
+
+
+static int __init xicap_gpi_init_module(void)
+{
+	int res;
+	dummy_dma_buffer = __get_dma_pages(GFP_KERNEL, 0);
+	if (!dummy_dma_buffer)
+		return -ENOMEM;
+	wq = create_workqueue(xicap_gpi_name);
+	if (unlikely(!wq)) {
+		free_pages(dummy_dma_buffer, 0);
+		return -ENOMEM;
+	}
+	res = driver_register(&xicap_gpi_driver);
+	if (unlikely(res)) {
+		free_pages(dummy_dma_buffer, 0);
+		destroy_workqueue(wq);
+	}
+	return res;
+}
+
+
+
+static void __exit xicap_gpi_cleanup_module(void)
+{
+	driver_unregister(&xicap_gpi_driver);
+	destroy_workqueue(wq);
+	free_pages(dummy_dma_buffer, 0);
+}
+
+module_init(xicap_gpi_init_module);
+module_exit(xicap_gpi_cleanup_module);
+
+
+
+MODULE_AUTHOR("Thomas Koeller <thomas.koeller@xxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("Basler eXcite frame capturing driver for gpi devices");
+MODULE_VERSION("0.1");
+MODULE_LICENSE("GPL");
+MODULE_PARM_DESC(min_packet_size, "Minimum data packet size");
diff --git a/arch/mips/basler/excite/xicap_priv.h 
b/arch/mips/basler/excite/xicap_priv.h
new file mode 100644
index 0000000..4149a7e
--- /dev/null
+++ b/arch/mips/basler/excite/xicap_priv.h
@@ -0,0 +1,48 @@
+#if ! defined(XICAP_PRIV_H)
+#define XICAP_PRIV_H
+
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+
+#include "xicap.h"
+
+
+
+typedef struct xicap_devctxt xicap_device_context_t;
+typedef struct xicap_frmctxt xicap_frame_context_t;
+
+
+
+/* A queue block for a data buffer */
+typedef struct {
+	struct list_head	link;
+	void __user *		uaddr;
+	size_t			size;
+	void *			uctxt;
+	xicap_frame_context_t *	frmctxt;
+	unsigned int		status;
+	unsigned int		npages;
+	struct page *		pages[0]; /* must be last element! */
+} xicap_data_buffer_t;
+
+
+
+/* Functions invoked by the core */
+typedef struct {
+	int			(*start)(struct device *);
+	void			(*stop)(struct device *);
+	xicap_frame_context_t *	(*do_buffer)(struct device *, xicap_data_buffer_t 
*);
+	int			(*finish_buffer)(struct device *, xicap_frame_context_t *);
+	int			(*flush)(struct device *);
+} xicap_hw_driver_t;
+
+
+
+/* Functions exported by the core */
+xicap_device_context_t *
+	xicap_device_register(struct device *, const xicap_hw_driver_t *);
+void xicap_device_unregister(xicap_device_context_t *);
+void xicap_frame_done(xicap_device_context_t *, xicap_data_buffer_t *);
+
+#endif	/* ! defined(XICAP_PRIV_H) */
-- 
1.5.0



[Index of Archives]     [Linux MIPS Home]     [LKML Archive]     [Linux ARM Kernel]     [Linux ARM]     [Linux]     [Git]     [Yosemite News]     [Linux SCSI]     [Linux Hams]

  Powered by Linux