From 23ca0c4b1fa01ace41720aaa0fb32bd4351d0afc Mon Sep 17 00:00:00 2001
From: Grant Likely <grant.likely@secretlab.ca>
Date: Mon, 5 Jan 2009 00:53:51 -0700
Subject: [PATCH] Add Bestcomm/localplus test utility

---
 drivers/misc/Kconfig                  |    4 +
 drivers/misc/Makefile                 |    1 +
 drivers/misc/mpc5200-localplus-test.c |  937 +++++++++++++++++++++++++++++++++
 3 files changed, 942 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/mpc5200-localplus-test.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index fee7304..edcab03 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -13,6 +13,10 @@ menuconfig MISC_DEVICES
 
 if MISC_DEVICES
 
+config MPC5200_LOCALPLUS_PERF_TEST
+	tristate "MPC5200 LocalPlus Bus performance test module"
+	select PPC_BESTCOMM_GEN_BD
+
 config ATMEL_PWM
 	tristate "Atmel AT32/AT91 PWM support"
 	depends on AVR32 || ARCH_AT91SAM9263 || ARCH_AT91SAM9RL || ARCH_AT91CAP9
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 817f7f5..19a3d92 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -33,3 +33,4 @@ obj-$(CONFIG_SGI_XP)		+= sgi-xp/
 obj-$(CONFIG_SGI_GRU)		+= sgi-gru/
 obj-$(CONFIG_HP_ILO)		+= hpilo.o
 obj-$(CONFIG_C2PORT)		+= c2port/
+obj-$(CONFIG_MPC5200_LOCALPLUS_PERF_TEST) += mpc5200-localplus-test.o
diff --git a/drivers/misc/mpc5200-localplus-test.c b/drivers/misc/mpc5200-localplus-test.c
new file mode 100644
index 0000000..8ba98fc
--- /dev/null
+++ b/drivers/misc/mpc5200-localplus-test.c
@@ -0,0 +1,937 @@
+/*
+ * LocalPlusBus performance tests.
+ *
+ * Copyright (C) Secret Lab Technologies Ltd. 2008-2009
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2.  This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ *
+ * This file implements a set of LocalPlus bus performance tests when using
+ * direct Programmed IO (PIO), the LocalPlus FIFO, and when using the
+ * Bestcomm DMA engine to transfer data.  It can be compiled into the
+ * kernel or loaded as a module.
+ *
+ * The test module is controlled via files in the sysfs filesystem.  Special
+ * control files are created in /sys/devices/platform/lpbtest.0 which
+ * control the tests and report the results.  Test parameters are set by
+ * writing values into the parameter files (blocksize, blockcount, period,
+ * and type).  The test is started and stopped with the 'action' file.
+ * Results are retrieved by reading the contents of the 'results' file.
+ *
+ * The following parameters can be modified:
+ * blocksize: number of bytes to transfer in each block.
+ * blockcount: number of blocks to transfer per timer tick.
+ * period: period of timer in microseconds.  Every timer tick will start a
+ *         new transfer of data blocks
+ * type: type of test; may be 'ram', 'fifo' or 'bcom'.
+ * chipselect: chipselect to use for transfer
+ *
+ * The first test type will copies contents of an LPB address range
+ * using a memcpy.
+ * Usage:
+ * $ echo ram > /sys/devices/platform/lpbtest.0/type
+ * $ echo start > /sys/devices/platform/lpbtest.0/action
+ * $ sleep 5s
+ * $ echo stop > /sys/devices/platform/lpbtest.0/action
+ *
+ * The second test copies contents of an LPB range to RAM using the
+ * LocalPlus FIFO.  The FIFO ISR copies each packet from the FIFO to RAM.
+ * Usage:
+ * $ echo fifo > /sys/devices/platform/lpbtest.0/type
+ * $ echo start > /sys/devices/platform/lpbtest.0/action
+ * $ sleep 5s
+ * $ echo stop > /sys/devices/platform/lpbtest.0/action
+ *
+ * The third test copies contents of an LPB range to RAM using both the FIFO
+ * and the Bestcomm DMA engine.
+ *
+ * Usage:
+ * $ echo bcom > /sys/devices/platform/lpbtest.0/type
+ * $ echo start > /sys/devices/platform/lpbtest.0/action
+ * $ sleep 5s
+ * $ echo stop > /sys/devices/platform/lpbtest.0/action
+ *
+ * All sysfs entries can be read by using cat <parameter>
+ * e.g. cat /sys/devices/platform/lpbtest.0/type will show the test type
+ *
+ * The following is a useful command to dump out all the state of the module:
+ * $ grep '' *
+ *
+ */
+#define DEBUG
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/mempool.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <sysdev/bestcomm/bestcomm.h>
+#include <sysdev/bestcomm/gen_bd.h>
+#include <sysdev/bestcomm/bestcomm_priv.h>
+#include <asm/page.h>
+#include <asm/time.h>
+
+MODULE_AUTHOR("Steven Cavanagh <scavanagh@secretlab.ca>");
+MODULE_LICENSE("GPL");
+
+#define DRVNAME "lpbtest"
+
+#define LPBTEST_FLASH_BASE_ADDR		(0xfff00000)
+#define LPBTEST_FLASH_SIZE		(0x00080000)	/* 512 KB */
+#define LPBTEST_FIFO_SIZE		(0x200)		/* FIFO size 512bytes */
+
+#define LPBTEST_STATUS_ABORT	(0x10000000)	/* status register abort  */
+#define LPBTEST_STATUS_NORMAL	(0x01000000)	/* status register normal */
+
+#define LPBTEST_FIFO_OFFSET		0x3C00
+#define LPBTEST_PACKET_SIZE_REG		0x00	/* packet Size register */
+#define LPBTEST_START_ADDR_REG		0x04	/* start Address register */
+#define LPBTEST_CONTROL_REG		0x08	/* control register */
+#define LPBTEST_ENABLE_REG		0x0C	/* enable register */
+#define LPBTEST_STATUS_REG		0x14	/* bytes done status register */
+#define LPBTEST_FIFO_STATUS_REG		0x44	/* FIFO status register */
+#define LPBTEST_FIFO_CNTRL_REG		0x48	/* FIFO control register */
+#define LPBTEST_FIFO_DATA_REG		0x40	/* Data Word register */
+#define LPBTEST_FIFO_ALARM_REG		0x4C	/* FIFO alarm register */
+
+#define LPBTEST_BLOCK_SIZE_MIN		4
+#define LPBTEST_BLOCK_SIZE_MAX		LPBTEST_FIFO_SIZE
+
+/**
+ * lpbtest - Private driver data
+ * @lpb_regs_base: pointer to the LPB's registers
+ * @irq: IRQ of this LPB FIFO
+ * @dev: struct device pointer
+ */
+struct lpbtest {
+	unsigned int irq;
+	void *target_base;
+	void *ram_base;
+	dma_addr_t ram_phys;
+	void __iomem *regs;
+	struct device *dev;
+
+	phys_addr_t fifo_data_base;
+
+	/* Timeslice timer */
+	struct timer_list timer;
+	unsigned long time;		/* next deadline; in jiffies */
+
+	/* Statistics */
+	unsigned long irq_time;
+	unsigned long timer_time;
+	unsigned long copy_time;
+	unsigned long bcom_time;
+	unsigned long start_time;
+	unsigned long stop_time;
+	int data_read;
+	int overrun_count;
+
+	/* state variables */
+	int next_block;	/* Number of next block to send.  If this is
+			 * >= .blockcount, then all the transfers are
+			 * finished */
+
+	struct bcom_task *bcom_task;
+
+	/* sysfs attributes */
+	int action;
+	int type;
+	unsigned blockcount;
+	unsigned blocksize;
+	unsigned period;
+	unsigned chipselect;
+	unsigned offset;
+	phys_addr_t target_phys;
+	int verify;
+
+	spinlock_t lock;
+};
+
+static struct of_device_id immr_ids[] = {
+	{ .compatible = "fsl,mpc5200-immr", },
+	{ .compatible = "fsl,mpc5200b-immr", },
+	{ .type = "soc", .compatible = "mpc5200", }, /* lite5200 */
+	{ .type = "builtin", .compatible = "mpc5200", }, /* efika */
+	{}
+};
+
+/* Helper functions to test selected behaviour */
+static inline int fifotest(struct lpbtest *priv) { return priv->type == 1; }
+
+static void lpbtest_do_next_transfer(struct lpbtest *priv)
+{
+	if (priv->next_block < priv->blockcount) {
+		priv->next_block++;
+		/* Kick off the transaction, Set the restart bit */
+		out_8(priv->regs + LPBTEST_PACKET_SIZE_REG, 0x01);
+	}
+}
+
+/*
+ *
+ * Called from DMA IRQ handler
+ *
+ */
+static void lpbtest_enqueue_next_buffer(struct lpbtest *priv)
+{
+	struct bcom_bd *bd;
+
+	/* Prepare and enqueue the next buffer descriptor */
+	bd = bcom_prepare_next_buffer(priv->bcom_task);
+
+	/* Bytes to be transfered by BestComm */
+	bd->status = LPBTEST_FIFO_SIZE;
+	bd->data[0] = priv->ram_phys; /* Set up destination address */
+
+	/* Give BD to BestComm */
+	bcom_submit_next_buffer(priv->bcom_task, NULL);
+}
+
+static void lpbtest_verify_dma_transfer(struct lpbtest *priv)
+{
+	int ret;
+
+	dev_dbg(priv->dev, "verifying transfer\n");
+	ret = memcmp(priv->ram_base, priv->target_base, priv->blocksize);
+	if (ret)
+		dev_err(priv->dev, "error: corrupt transfer\n");
+}
+
+/* Bestcomm SCLPC DMA irq handler */
+static irqreturn_t lpbtest_bcom_irq(int irq, void *_priv)
+{
+	struct lpbtest *priv = _priv;
+	unsigned long bcom_time = get_tbl();
+
+	/* For each finished block, dequeue the completed block buffer
+	 * and enqueue a new one in it's place. */
+	while (bcom_buffer_done(priv->bcom_task)) {
+		bcom_retrieve_buffer(priv->bcom_task, NULL, NULL);
+		if (priv->verify)
+			lpbtest_verify_dma_transfer(priv);
+
+		lpbtest_enqueue_next_buffer(priv);
+		bcom_enable(priv->bcom_task);
+
+		priv->data_read += priv->blocksize;
+	}
+
+	priv->bcom_time += get_tbl() - bcom_time;
+
+	return IRQ_HANDLED;
+}
+
+
+/*
+ * SCLPC FIFO peripheral interrupt
+ * Process a FIFO packet size interrupt, then reset the FIFO.
+ */
+static irqreturn_t lpbtest_fifo_irq(int irq, void *_priv)
+{
+	struct lpbtest *priv = _priv;
+	u32 bytes_done_status = 0;
+	u32 fifo_status = 0;
+	u32 *fifo_data_word;
+	unsigned long copy_time;
+	unsigned long irq_time = get_tbl();
+	int i;
+
+	bytes_done_status = in_be32(priv->regs + LPBTEST_STATUS_REG);
+	fifo_status = in_be32(priv->regs + LPBTEST_FIFO_STATUS_REG);
+
+	/* Check the bytes done status register bits */
+	if (bytes_done_status & LPBTEST_STATUS_ABORT) {
+		dev_err(priv->dev, "ABORT TERMINATION ERROR\n");
+		dev_err(priv->dev, "SCLPC STATUS REG:0x%x\n",
+			bytes_done_status);
+
+		/* Clear AT bit */
+		out_8(priv->regs + LPBTEST_STATUS_REG, 0x1);
+		priv->action = 0;
+	}
+
+	if (bytes_done_status & LPBTEST_STATUS_NORMAL) {
+		/* Clear NT bit */
+		out_8(priv->regs + LPBTEST_STATUS_REG, 0x01);
+
+		/* Check the FIFO status register error bits */
+		if (fifo_status & 0x40) {
+			dev_err(priv->dev, "FIFO ERROR\n");
+			dev_err(priv->dev, "FIFO STATUS REG:0x%x\n",
+				fifo_status);
+
+			/* Clear bit */
+			out_be32(priv->regs + LPBTEST_FIFO_STATUS_REG,
+				 (fifo_status & ~0x40));
+			priv->action = 0;
+		}
+
+		/* Kick off next transfer so it can progress while the FIFO
+		 * is being read */
+		lpbtest_do_next_transfer(priv);
+
+		/* Read FIFO, if the FIFO is full,
+		   read it if not running BestComm */
+		if (fifotest(priv)) {
+			/* Copy FIFO bytes */
+			copy_time = get_tbl();
+			fifo_data_word = priv->ram_base;
+			for (i = 0; i < priv->blocksize; i += 4) {
+				*fifo_data_word = in_be32(priv->regs + LPBTEST_FIFO_DATA_REG);
+				fifo_data_word++;
+			}
+			priv->copy_time += get_tbl() - copy_time;
+			priv->data_read += priv->blocksize;
+		}
+	}
+
+	priv->irq_time += get_tbl() - irq_time;
+	return IRQ_HANDLED;
+}
+
+static int lpbtest_register_status_irq(struct device *dev)
+{
+	struct lpbtest *priv = dev_get_drvdata(dev);
+	u32 intspec[3] = {2, 23, 0}; /* MPC5200 irq id for the FIFO device */
+	int virq, ret = 0;
+
+	/* Setup the interrupt handlers */
+	virq = irq_create_of_mapping(NULL, intspec, 3);
+	dev_dbg(dev, "virq=%i\n", virq);
+
+	if (virq < 0) {
+		dev_err(dev, "irq_create_of_mapping() error: %d\n", ret);
+		return virq;
+	}
+
+	ret = request_irq(virq, lpbtest_fifo_irq, IRQF_SHARED,
+			  "lpbtest-fifo", priv);
+	if (ret) {
+		dev_err(dev, "request_irq() LPB status error: %d\n", ret);
+		return ret;
+	}
+
+	priv->irq = virq;
+	return ret;
+}
+
+static void lpbtest_stop_test(struct lpbtest *priv)
+{
+	priv->stop_time = get_tbl();
+
+	bcom_disable(priv->bcom_task);
+	while (!bcom_queue_empty(priv->bcom_task))
+		bcom_retrieve_buffer(priv->bcom_task, NULL, NULL);
+}
+
+int lpbtest_check_test_stop(struct device *dev)
+{
+	struct lpbtest *priv = dev_get_drvdata(dev);
+
+	if (!priv->action) {
+		lpbtest_stop_test(priv);
+		return 1;
+	}
+
+	/* Update the timeslice time */
+	priv->time += usecs_to_jiffies(priv->period);
+	if ((int)(priv->time - jiffies) < 0) {
+		dev_info(dev, "Timeslice overrun by %ius; aborting\n",
+			 jiffies_to_usecs(jiffies - priv->time) + priv->period);
+		lpbtest_stop_test(priv);
+		return 1;
+	}
+
+	/* Reset the timer */
+	mod_timer(&priv->timer, priv->time);
+
+	return 0;
+}
+
+static void lpbtest_read_channels_to_ram(unsigned long _dev)
+{
+	struct device *dev = (struct device *)_dev;
+	struct lpbtest *priv = dev_get_drvdata(dev);
+	unsigned long time = get_tbl();
+	int i;
+
+	if (lpbtest_check_test_stop(dev))
+		return;
+
+	/* Assume, that all channels have data available */
+	for (i = 0; i < priv->blockcount; i++) {
+		memcpy(priv->ram_base, priv->target_base, priv->blocksize);
+		priv->data_read += priv->blocksize;
+	}
+
+	priv->timer_time += get_tbl() - time;
+}
+
+static void lpbtest_read_channels(unsigned long _dev)
+{
+	struct device *dev = (struct device *)_dev;
+	struct lpbtest *priv = dev_get_drvdata(dev);
+	unsigned long flags;
+	unsigned long time = get_tbl();
+
+	if (lpbtest_check_test_stop(dev))
+		return;
+
+	/* Allow the timer to process the present state w/o an interrupt */
+	spin_lock_irqsave(&priv->lock, flags);
+
+	if (priv->next_block < priv->blockcount) {
+		dev_err(priv->dev, "overrun! next=%i total=%i\n",
+			priv->next_block, priv->blockcount);
+		priv->overrun_count++;
+		goto out;
+	}
+
+	/* This line is the FIFO throttle, the faster the next packet
+	 * is cleared, the faster the FIFO can be read and filled by
+	 * the IRQ.  The ISR will stop handling the FIFO, when all the
+	 * channels have been read.
+	 */
+	priv->next_block = 0;
+	lpbtest_do_next_transfer(priv);
+
+ out:
+	spin_unlock_irqrestore(&priv->lock, flags);
+	priv->timer_time += get_tbl() - time;
+}
+
+static void lpbtest_fifo_start(struct lpbtest *priv)
+{
+	out_be32(priv->regs + LPBTEST_PACKET_SIZE_REG, priv->blocksize);
+
+	/* FIFO receive, BPT 8 bytes/transfer, CS# */
+	out_be32(priv->regs + LPBTEST_CONTROL_REG,
+		 0x00010008 | priv->chipselect << 24);
+
+	/* Kick off the transaction: Set the restart bit for the FIFO */
+	out_8(priv->regs + LPBTEST_PACKET_SIZE_REG, 0x01);
+}
+
+static void lpbtest_bcom_start(struct lpbtest *priv)
+{
+	/* Setup the BestComm engine */
+	bcom_gen_bd_rx_reset(priv->bcom_task);
+	while (!bcom_queue_full(priv->bcom_task))
+		lpbtest_enqueue_next_buffer(priv);
+
+	/* Set the FIFO packet size */
+	out_be32(priv->regs + LPBTEST_PACKET_SIZE_REG, priv->blocksize);
+
+	/* FIFO receive, BPT 8 bytes/transfer, CS# */
+	out_be32(priv->regs + LPBTEST_CONTROL_REG,
+		 0x00010008 | priv->chipselect << 24);
+
+	/* Kick off the transaction: Set the restart bit for the FIFO */
+	out_8(priv->regs + LPBTEST_PACKET_SIZE_REG, 0x01);
+
+	/* Enable the bcom task */
+	bcom_enable(priv->bcom_task);
+}
+
+static const struct lpbtest_type {
+	char *name;
+	void (*start)(struct lpbtest *);
+	void (*timer)(unsigned long);
+} lpbtest_type[] = {
+	{
+		.name = "ram",
+		.timer = lpbtest_read_channels_to_ram,
+	},
+	{
+		.name = "fifo",
+		.start = lpbtest_fifo_start,
+		.timer = lpbtest_read_channels,
+	},
+	{
+		.name = "bcom",
+		.start = lpbtest_bcom_start,
+		.timer = lpbtest_read_channels,
+	}
+};
+
+/* ---------------------------------------------------------------------
+ * sysfs interfaces
+ * --------------------------------------------------------------------- */
+static ssize_t lpbtest_set_type(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct lpbtest *priv = dev_get_drvdata(dev);
+	const char *name;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(lpbtest_type); i++) {
+		name = lpbtest_type[i].name;
+
+		if (count < strlen(name))
+			continue;
+
+		if (strncmp(buf, name, strlen(name)) == 0) {
+			priv->type = i;
+			return count;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static ssize_t lpbtest_show_type(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct lpbtest *priv = dev_get_drvdata(dev);
+	return sprintf(buf, "%s\n", lpbtest_type[priv->type].name);
+}
+
+static ssize_t lpbtest_set_action(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct lpbtest *priv = dev_get_drvdata(dev);
+	const struct lpbtest_type *type = &lpbtest_type[priv->type];
+
+	if (strncmp(buf, "start", strlen("start")) == 0)
+		priv->action = 1;
+	else if (strncmp(buf, "stop", strlen("stop")) == 0)
+		priv->action = 0;
+	else {
+		dev_err(dev, "Usage: echo [start,stop] > action\n");
+		return -EINVAL;
+	}
+
+	if (priv->action) {
+		init_timer(&priv->timer);
+		priv->timer.data = (unsigned long) dev;
+
+		priv->irq_time = 0;
+		priv->timer_time = 0;
+		priv->bcom_time = 0;
+		priv->stop_time = priv->start_time = get_tbl();
+		priv->data_read = 0;
+		priv->overrun_count = 0;
+		priv->timer.function = type->timer;
+
+		/* Map the device */
+		priv->target_base = ioremap(priv->target_phys,
+					    LPBTEST_BLOCK_SIZE_MAX);
+		if (!priv->target_base) {
+			dev_err(dev, "Error mapping device\n");
+			return -ENOMEM;
+		}
+
+		dev_dbg(dev, "Started %s test\n", type->name);
+
+		/* Run any setup code */
+		if (type->start)
+			type->start(priv);
+
+		/* Set the expiration time for the timer. */
+		priv->time = jiffies + usecs_to_jiffies(priv->period);
+		mod_timer(&priv->timer, priv->time);
+	}
+
+	return count;
+}
+
+static ssize_t lpbtest_show_action(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	char *action;
+	struct lpbtest *priv = dev_get_drvdata(dev);
+
+	action = (priv->action == 1) ? "start" : "stop";
+	return sprintf(buf, "%s\n", action);
+}
+
+/*
+ * Export a blockcount attr
+ */
+static ssize_t lpbtest_set_blockcount(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t count)
+{
+	struct lpbtest *priv = dev_get_drvdata(dev);
+	unsigned long temp;
+
+	if (strict_strtoul(buf, 10, &temp))
+		return -EINVAL;
+	priv->blockcount = temp;
+
+	return count;
+}
+
+static ssize_t lpbtest_show_blockcount(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct lpbtest *priv = dev_get_drvdata(dev);
+	return sprintf(buf, "%d\n", priv->blockcount);
+}
+
+/*
+ * Export a blocksize attr
+ */
+static ssize_t lpbtest_set_blocksize(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t count)
+{
+	struct lpbtest *priv = dev_get_drvdata(dev);
+	unsigned long temp;
+
+	if (strict_strtoul(buf, 10, &temp))
+		return -EINVAL;
+
+	if ((temp < LPBTEST_BLOCK_SIZE_MIN) || (temp > LPBTEST_BLOCK_SIZE_MAX))
+		return -EINVAL;
+
+	priv->blocksize = temp & 0xfffffffc;
+	return count;
+}
+
+static ssize_t lpbtest_show_blocksize(struct device *dev,
+			struct device_attribute *attr,
+			char *buf)
+{
+	struct lpbtest *priv = dev_get_drvdata(dev);
+	return sprintf(buf, "%d\n", priv->blocksize);
+}
+
+static ssize_t lpbtest_set_period(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	long temp;
+	struct lpbtest *priv = dev_get_drvdata(dev);
+
+	if (strict_strtol(buf, 0, &temp)) {
+		dev_err(dev, "Usage: echo [period (us)] > period\n");
+		return -EINVAL;
+	}
+	priv->period = temp;
+	return count;
+}
+
+static ssize_t lpbtest_show_period(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct lpbtest *priv = dev_get_drvdata(dev);
+	return sprintf(buf, "%d\n", priv->period);
+}
+
+static ssize_t lpbtest_set_cs(struct device *dev, struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	long temp;
+	struct lpbtest *priv = dev_get_drvdata(dev);
+
+	if (strict_strtol(buf, 0, &temp))
+		return -EINVAL;
+
+	if (temp > 7)
+		return -EINVAL;
+
+	priv->chipselect = temp;
+	return count;
+}
+
+static ssize_t lpbtest_show_cs(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	struct lpbtest *priv = dev_get_drvdata(dev);
+	return sprintf(buf, "%d\n", priv->chipselect);
+}
+
+static ssize_t lpbtest_set_baseaddr(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	unsigned long temp;
+	struct lpbtest *priv = dev_get_drvdata(dev);
+
+	if (strict_strtoul(buf, 0, &temp))
+		return -EINVAL;
+
+	priv->target_phys = temp;
+	return count;
+}
+
+static ssize_t lpbtest_show_baseaddr(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct lpbtest *priv = dev_get_drvdata(dev);
+	return sprintf(buf, "%llx\n", (unsigned long long) priv->target_phys);
+}
+
+static ssize_t lpbtest_set_verify(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t count)
+{
+	struct lpbtest *priv = dev_get_drvdata(dev);
+	unsigned long temp;
+
+	if (strict_strtoul(buf, 10, &temp))
+		return -EINVAL;
+
+	priv->verify = temp;
+	return count;
+}
+
+static ssize_t lpbtest_show_verify(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct lpbtest *priv = dev_get_drvdata(dev);
+	return sprintf(buf, "%d\n", priv->verify);
+}
+
+static ssize_t lpbtest_show_results(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	struct lpbtest *priv = dev_get_drvdata(dev);
+	int systime, realtime, utilization, rate, c;
+
+	realtime = priv->stop_time - priv->start_time;
+	systime = priv->timer_time + priv->irq_time + priv->bcom_time;
+	utilization = systime / (realtime / 10000);
+	rate = priv->data_read / (realtime / (tb_ticks_per_usec * 100));
+
+	c = sprintf(buf,      "real:		%10uticks	%9luus\n",
+		    realtime, realtime / tb_ticks_per_usec);
+	c += sprintf(buf + c, "sys:		%10uticks	%9luus\n",
+		     systime, systime / tb_ticks_per_usec);
+	c += sprintf(buf + c, "timer:		%10luticks	%9luus\n",
+		     priv->timer_time, priv->timer_time / tb_ticks_per_usec);
+	c += sprintf(buf + c, "fifo-irq:	%10luticks	%9luus\n",
+		     priv->irq_time, priv->irq_time / tb_ticks_per_usec);
+	c += sprintf(buf + c, "bcom-irq:	%10luticks	%9luus\n",
+		     priv->bcom_time, priv->bcom_time / tb_ticks_per_usec);
+	c += sprintf(buf + c, "overruns:	%10u\n", priv->overrun_count);
+	c += sprintf(buf + c, "%%CPU:		%10u.%.2u%%\n",
+		     utilization / 100, utilization % 100);
+	c += sprintf(buf + c, "byte count:	%10u\n", priv->data_read);
+	c += sprintf(buf + c, "data rate:	%10u.%.2uMB/s\n",
+		     rate / 100, rate % 100);
+
+	return c;
+}
+
+static struct device_attribute lpbtest_attrib[] = {
+	__ATTR(action, S_IWUSR | S_IRUGO,
+	       lpbtest_show_action, lpbtest_set_action),
+	__ATTR(blockcount, S_IWUSR | S_IRUGO,
+	       lpbtest_show_blockcount, lpbtest_set_blockcount),
+	__ATTR(blocksize, S_IWUSR | S_IRUGO,
+	       lpbtest_show_blocksize, lpbtest_set_blocksize),
+	__ATTR(period, S_IWUSR | S_IRUGO,
+	       lpbtest_show_period, lpbtest_set_period),
+	__ATTR(chipselect, S_IWUSR | S_IRUGO,
+	       lpbtest_show_cs, lpbtest_set_cs),
+	__ATTR(baseaddr, S_IWUSR | S_IRUGO,
+	       lpbtest_show_baseaddr, lpbtest_set_baseaddr),
+	__ATTR(verify, S_IWUSR | S_IRUGO,
+	       lpbtest_show_verify, lpbtest_set_verify),
+	__ATTR(type, S_IWUSR | S_IRUGO,
+	       lpbtest_show_type, lpbtest_set_type),
+	__ATTR(results, S_IWUSR | S_IRUGO, lpbtest_show_results, NULL),
+};
+
+static void lpbtest_cleanup_sysfs(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(lpbtest_attrib); i++)
+		device_remove_file(dev, &lpbtest_attrib[i]);
+}
+
+static int lpbtest_setup(struct device *dev)
+{
+	struct device_node *np;
+	struct resource res;
+	struct lpbtest *priv = dev_get_drvdata(dev);
+	int ret = 0;
+	phys_addr_t phys_addr;
+
+	/* Allocate a destination buffer */
+	priv->ram_base = kzalloc(LPBTEST_BLOCK_SIZE_MAX, GFP_KERNEL);
+	if (!priv->ram_base) {
+		dev_err(dev, "Error allocating test buffer\n");
+		return -ENOMEM;
+	}
+	priv->ram_phys = virt_to_phys(priv->ram_base);
+
+	/* map the whole register space */
+	np = of_find_matching_node(NULL, immr_ids);
+	if (!np) {
+		dev_err(dev, "bcomm_test_init():Unable to locate platform\n");
+		return -ENODEV;
+	}
+	if (of_address_to_resource(np, 0, &res)) {
+		dev_err(dev, "Unable to locate resources\n");
+		return -ENODEV;
+	}
+
+	phys_addr = res.start + LPBTEST_FIFO_OFFSET;
+	priv->fifo_data_base = phys_addr + LPBTEST_FIFO_DATA_REG;
+	dev_info(dev, "FIFO regs at address %llx\n",
+		 (unsigned long long)phys_addr);
+	priv->regs = ioremap(phys_addr, 0x50);
+	if (!priv->regs) {
+		dev_err(dev, "bcomm_test_init():Unable to locate platform\n");
+		return -ENODEV;
+	}
+	of_node_put(np);
+
+	/* Reset LPB FIFO */
+	out_be32(priv->regs + LPBTEST_ENABLE_REG, 0x01010000);
+
+	/* Write the start address; Offset from chipselect base address */
+	out_be32(priv->regs + LPBTEST_START_ADDR_REG, 0);
+
+	/* Write the control register */
+	/* CS0 asserted, FIFO receive, and BPT 8 bytes/transfer */
+	out_be32(priv->regs + LPBTEST_CONTROL_REG, 0x00010008);
+
+	/* Set AIE, NIE, and ME bits */
+	out_be32(priv->regs + LPBTEST_ENABLE_REG, 0x00000301);
+
+	/* Set alarm when only 256 bytes remain in FIFO */
+	out_be32(priv->regs + LPBTEST_FIFO_ALARM_REG, 0x100);
+
+	/* Stop requesting data when 4 bytes remaining in FIFO */
+	out_be32(priv->regs + LPBTEST_FIFO_CNTRL_REG, 0x01000000);
+
+	/* Register the SCLPC status register ISR(), and
+	 * set the packet size to the FIFO size. We want to
+	 * interrupt when the FIFO is full to empty it without
+	 * a BestComm task */
+	ret = lpbtest_register_status_irq(dev);
+	if (ret)
+		return ret;
+
+	/* Setup the bcom gen bd task */
+	priv->bcom_task = bcom_gen_bd_rx_init(32, priv->fifo_data_base,
+					      BCOM_INITIATOR_SCLPC,
+					      BCOM_IPR_SCLPC, 512);
+	if (!priv->bcom_task) {
+		dev_err(dev, "error initializing bestcomm task\n");
+		return -ENODEV;
+	}
+
+	/* Setup the bcom_task irq */
+	ret = request_irq(bcom_get_task_irq(priv->bcom_task), &lpbtest_bcom_irq,
+			  IRQF_SHARED, "lpbtest-bcom", priv);
+	if (ret)
+		dev_err(dev, "error registering Bestcomm task irq: %d\n", ret);
+
+	return ret;
+}
+
+static int __devinit lpbtest_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct lpbtest *priv;
+
+	int ret = 0, i;
+
+	/* Allocate and initialize the driver private data */
+	priv = kzalloc(sizeof *priv, GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->dev = &pdev->dev;
+	priv->blockcount = 16;
+	priv->blocksize = LPBTEST_FIFO_SIZE;
+	priv->period  = 20000;
+	priv->target_phys = LPBTEST_FLASH_BASE_ADDR;
+
+	spin_lock_init(&priv->lock);
+	platform_set_drvdata(pdev, priv);
+
+	ret = lpbtest_setup(dev);
+	if (ret) {
+		dev_err(dev, "lpbtest_setup() error\n");
+		return ret;
+	}
+
+	/* Register the SYSFS files */
+	for (i = 0; i < ARRAY_SIZE(lpbtest_attrib); i++) {
+		ret = device_create_file(dev, &lpbtest_attrib[i]);
+		if (ret) {
+			dev_err(dev, "error creating sysfs files (%d)\n", ret);
+			lpbtest_cleanup_sysfs(pdev);
+			return ret;
+		}
+	}
+
+	return ret;
+}
+
+static int __devexit lpbtest_remove(struct platform_device *pdev)
+{
+	struct lpbtest *priv = platform_get_drvdata(pdev);
+
+	if (priv->irq)
+		free_irq(priv->irq, priv);
+
+	if (priv->bcom_task) {
+		free_irq(bcom_get_task_irq(priv->bcom_task), priv);
+		bcom_gen_bd_rx_release(priv->bcom_task);
+	}
+
+	lpbtest_cleanup_sysfs(pdev);
+
+	del_timer(&priv->timer);
+
+	kfree(priv->ram_base);
+	kfree(priv);
+
+	return 0;
+}
+
+static struct platform_driver lpbtest_driver = {
+	.driver = {
+		.owner = THIS_MODULE,
+		.name  = DRVNAME,
+	},
+	.probe  = lpbtest_probe,
+	.remove = __devexit_p(lpbtest_remove),
+};
+
+static struct platform_device *lpbtest_pdev;
+static int __init lpbtest_init(void)
+{
+	int rc;
+
+	lpbtest_pdev = platform_device_register_simple(DRVNAME, 0, NULL, 0);
+	if (!lpbtest_pdev) {
+		pr_err("%s: error registering test device\n", DRVNAME);
+		return -ENOMEM;
+	}
+
+	rc = platform_driver_register(&lpbtest_driver);
+	if (rc)
+		platform_device_unregister(lpbtest_pdev);
+	return rc;
+}
+module_init(lpbtest_init);
+
+static void lpbtest_exit(void)
+{
+	platform_device_unregister(lpbtest_pdev);
+	platform_driver_unregister(&lpbtest_driver);
+}
+module_exit(lpbtest_exit);
-- 
1.5.6.3
