Patchwork Bestcomm tasks and interrupts on MPC5200(B)

login
register
mail settings
Submitter Grant Likely
Date Jan. 20, 2009, 4:04 p.m.
Message ID <fa686aa40901200804l24a10f56g50c3812f8730a513@mail.gmail.com>
Download mbox | patch
Permalink /patch/19518/
State Rejected, archived
Delegated to: Grant Likely
Headers show

Comments

Grant Likely - Jan. 20, 2009, 4:04 p.m.
On Tue, Jan 20, 2009 at 6:15 AM, Dave Best <arieswar24b@yahoo.de> wrote:
> I'm trying to write a driver which uses the Local Plus Bus on my MPC5200B and therefore have to use BestComm DMA, which requires me to use a Gen_BD task for data transfer with Local Plus.
> I tried to follow the fec driver that is currently used and took a peek at the mpc52xx-ac97 driver which at least uses the same kind of bus as I.
>
> Initialising the task, resetting and enabling works fine. Even request_irq reports no error, but when I start a transfer it hangs and if I am lucky, an interrupt occurs after quite some time. But it's always the BestComm ethernet rx task which produces an RFIFO interrupt, presumably after the watchdog catches on.
> If this happens my interrupt occurs to.

Are you using the LocalPlus fifo device for the transfer (you need to
if you aren't)?

I've attached a test driver that demonstrates how to do FIFO only and
FIFO+DMA transfers over the localplus bus.

g.
Dave Best - Jan. 21, 2009, 12:57 p.m.
Thanks for the driver. 
I am looking into this.

I still have some trouble to compile and use the driver:
- I currently use the 2.6.23.1 kernel source which was provide along with  the pcm030 board by Phytec. And i guess your driver was compile along with a newer driver as i can't find the "strict_strtoul" and "strict_strtol" functions in my kernel but i copied their source into my code. The same with "of_find_matching_node" which i tried to exchange with "of_find_compatible_node" but somehow my devicetree does not include the neccesay compatible types. Here I tried "of_find_node_by_type" with "soc" and it seemed to work.
The biggest problem seems to be "irq_create_of_mapping" for the localplus isr. It's giving me a kernel OOPS by trying to access data from 0x00000010.

If you could point out if I have to use a newer Kernel, and which, to solve these problems or how i can code around these with my current kernel source.


Anyway, I looked over your code and only found that you first create an interrupt routine for the localplus in general before creating the bestcomm task.
As far as I thought it was enough to create the bestcomm task and enable it. And it was upon Bestcomm to watch over its tasks and to look for interrupt sources according to their initiators/requestors to trigger their ISR.

In your example the local plus fifo would trigger its ISR when its done transfering the blocksize and reset the status bits and start over. The task would run its ISR when its buffer is filled to create a new one.

If thats the way to go, i will try it out.

Thanks
Dave

--- Grant Likely <grant.likely@secretlab.ca> schrieb am Di, 20.1.2009:

> Von: Grant Likely <grant.likely@secretlab.ca>
> Betreff: Re: Bestcomm tasks and interrupts on MPC5200(B)
> An: arieswar24b@yahoo.de
> CC: linuxppc-dev@ozlabs.org
> Datum: Dienstag, 20. Januar 2009, 17:04
> On Tue, Jan 20, 2009 at 6:15 AM, Dave Best
> <arieswar24b@yahoo.de> wrote:
> > I'm trying to write a driver which uses the Local
> Plus Bus on my MPC5200B and therefore have to use BestComm
> DMA, which requires me to use a Gen_BD task for data
> transfer with Local Plus.
> > I tried to follow the fec driver that is currently
> used and took a peek at the mpc52xx-ac97 driver which at
> least uses the same kind of bus as I.
> >
> > Initialising the task, resetting and enabling works
> fine. Even request_irq reports no error, but when I start a
> transfer it hangs and if I am lucky, an interrupt occurs
> after quite some time. But it's always the BestComm
> ethernet rx task which produces an RFIFO interrupt,
> presumably after the watchdog catches on.
> > If this happens my interrupt occurs to.
> 
> Are you using the LocalPlus fifo device for the transfer
> (you need to
> if you aren't)?
> 
> I've attached a test driver that demonstrates how to do
> FIFO only and
> FIFO+DMA transfers over the localplus bus.
> 
> g.
> 
> 
> 
> 
> -- 
> Grant Likely, B.Sc., P.Eng.
> Secret Lab Technologies Ltd.
Jon Smirl - Jan. 21, 2009, 3:16 p.m.
On Wed, Jan 21, 2009 at 7:57 AM, Dave Best <arieswar24b@yahoo.de> wrote:
> Thanks for the driver.
> I am looking into this.
>
> I still have some trouble to compile and use the driver:
> - I currently use the 2.6.23.1 kernel source which was provide along with  the pcm030 board by Phytec. And i guess your driver was compile along with a newer driver as i can't find the "strict_strtoul" and "strict_strtol" functions in my kernel but i copied their source into my code. The same with "of_find_matching_node" which i tried to exchange with "of_find_compatible_node" but somehow my devicetree does not include the neccesay compatible types. Here I tried "of_find_node_by_type" with "soc" and it seemed to work.
> The biggest problem seems to be "irq_create_of_mapping" for the localplus isr. It's giving me a kernel OOPS by trying to access data from 0x00000010.
>
> If you could point out if I have to use a newer Kernel, and which, to solve these problems or how i can code around these with my current kernel source.

Current git kernel runs fine on the phytec board without patches.

Pengutronix makes the kernels for the phytec pcm030 boards. Their
2.6.23 kernel has probably been patched for real time support. If you
don't need real time there's no real downside to using current git.
Sascha may have some other patches that I'm unaware of.

>
>
> Anyway, I looked over your code and only found that you first create an interrupt routine for the localplus in general before creating the bestcomm task.
> As far as I thought it was enough to create the bestcomm task and enable it. And it was upon Bestcomm to watch over its tasks and to look for interrupt sources according to their initiators/requestors to trigger their ISR.
>
> In your example the local plus fifo would trigger its ISR when its done transfering the blocksize and reset the status bits and start over. The task would run its ISR when its buffer is filled to create a new one.
>
> If thats the way to go, i will try it out.
>
> Thanks
> Dave
>
> --- Grant Likely <grant.likely@secretlab.ca> schrieb am Di, 20.1.2009:
>
>> Von: Grant Likely <grant.likely@secretlab.ca>
>> Betreff: Re: Bestcomm tasks and interrupts on MPC5200(B)
>> An: arieswar24b@yahoo.de
>> CC: linuxppc-dev@ozlabs.org
>> Datum: Dienstag, 20. Januar 2009, 17:04
>> On Tue, Jan 20, 2009 at 6:15 AM, Dave Best
>> <arieswar24b@yahoo.de> wrote:
>> > I'm trying to write a driver which uses the Local
>> Plus Bus on my MPC5200B and therefore have to use BestComm
>> DMA, which requires me to use a Gen_BD task for data
>> transfer with Local Plus.
>> > I tried to follow the fec driver that is currently
>> used and took a peek at the mpc52xx-ac97 driver which at
>> least uses the same kind of bus as I.
>> >
>> > Initialising the task, resetting and enabling works
>> fine. Even request_irq reports no error, but when I start a
>> transfer it hangs and if I am lucky, an interrupt occurs
>> after quite some time. But it's always the BestComm
>> ethernet rx task which produces an RFIFO interrupt,
>> presumably after the watchdog catches on.
>> > If this happens my interrupt occurs to.
>>
>> Are you using the LocalPlus fifo device for the transfer
>> (you need to
>> if you aren't)?
>>
>> I've attached a test driver that demonstrates how to do
>> FIFO only and
>> FIFO+DMA transfers over the localplus bus.
>>
>> g.
>>
>>
>>
>>
>> --
>> Grant Likely, B.Sc., P.Eng.
>> Secret Lab Technologies Ltd.
>
>
>
> _______________________________________________
> Linuxppc-dev mailing list
> Linuxppc-dev@ozlabs.org
> https://ozlabs.org/mailman/listinfo/linuxppc-dev
>
Wolfram Sang - Jan. 21, 2009, 3:26 p.m.
Hello,

> Pengutronix makes the kernels for the phytec pcm030 boards. Their
> 2.6.23 kernel has probably been patched for real time support. If you
> don't need real time there's no real downside to using current git.
> Sascha may have some other patches that I'm unaware of.

We are currently working on a new release for both, the pcm030 and
pcm032 having 2.6.28 for non-RT and 2.6.26 for RT.

Kind regards,

   Wolfram

Patch

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