* Re: Bestcomm tasks and interrupts on MPC5200(B)
2009-01-20 13:15 Bestcomm tasks and interrupts on MPC5200(B) Dave Best
2009-01-20 15:51 ` Frank Bennett
@ 2009-01-20 15:59 ` Frank Bennett
2009-01-20 16:04 ` Grant Likely
2 siblings, 0 replies; 7+ messages in thread
From: Frank Bennett @ 2009-01-20 15:59 UTC (permalink / raw)
To: arieswar24b; +Cc: linuxppc-dev
Dave Best 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.
>
> I tried to debug this situation but I am still clueless.
> If I use the MPC5200 Interrupt emulation registers to force an interrupt for my interface to occur, nothing happens except that it hangs.
>
> Any hints, tips or help appreciated.
>
Below is a Disassembler I wrote a couple years ago. Just paste
the hex of interest in the array below, re-compile and run.
Cheers,
-Frank
> Dave
>
>
>
> _______________________________________________
> Linuxppc-dev mailing list
> Linuxppc-dev@ozlabs.org
> https://ozlabs.org/mailman/listinfo/linuxppc-dev
>
/*
* disasm.c - disassembler for MPC5200 Bestcomm DMA Firmware
* copy and paste task code into fw, compile a run
* by Frank bennett, 3/29/2006
*
* Based on Freescale pdf "SmartDMA Hand-Assembly Guides"
*
* TODO:
* o add inc[0:7] array (maybe var) to deternmine proper term condition (upper 3 bits)
* o need to review sheet 3 of the pdf
* o simulator would be nice
*
*/
// Task12 (TASK_GEN_TX_BD) : Start of TDT -> 0xf0008528
// linuxppc_2_4_devel/arch/ppc/5xxx_io/bestcomm/code_dma/image_rtos1/"dma_image.reloc.c
//
// 31,30,29,28|27,26,25,24|23,22,21,20|19,18,17,16|15,14,13,12|11,10, 9, 8| 7, 6, 5, 4| 3, 2, 1, 0
// 0 0 0 1 0 0| 0 0 0 0 0| 0 0| 0 0| 0| 0 0 0 1 0 0| 1| 1 0 0 0 0 1| 0 0 0
// 0x10001308, /* 000C DRD1A: var4 = idx1; FN=0 MORE init=0 WS=0 RS=2 */
// 1 0 0| 1 1 0 0 1 0| 0| 0| 1 1 0 0 1 0| 0 0| 0| 0 0 0 0 0 0| 1 1 0| 1 1 0
// 0x99190036, /* 002C LCD: idx2 = idx2; idx2 once var0; idx2 += inc6 */
// 31,30,29,28|27,26,25,24|23,22,21,20|19,18,17,16|15,14,13,12|11,10, 9, 8| 7, 6, 5, 4| 3, 2, 1, 0
// 1 0| 0 1 1 0, 0 1 0 1, 1 0 0, 0 0 1| 1 0| 1 0 0 1 0 0 0 0 1 0 0 0 0
// 0x9950d210
/* Task12(TASK_GEN_TX_BD): Start of TDT -> 0xf0008528 */
unsigned long fw[] = {
0x800220e3, /* 0000 LCD: idx0 = var0, idx1 = var4; idx1 <= var3; idx0 += inc4, idx1 += inc3 */
0x13e01010, /* 0004 DRD1A: var4 = var2; FN=0 MORE init=24 WS=0 RS=0 */
0xb8808264, /* 0008 LCD: idx2 = *idx1, idx3 = var1; idx2 < var9; idx2 += inc4, idx3 += inc4 */
0x10001308, /* 000C DRD1A: var4 = idx1; FN=0 MORE init=0 WS=0 RS=2 */
0x60140002, /* 0010 DRD2A: EU0=0 EU1=0 EU2=0 EU3=2 EXT init=0 WS=2 RS=2 */
0x0cccfcca, /* 0014 DRD2B1: *idx3 = EU3(); EU3(*idx3,var10) */
0xd9190300, /* 0018 LCDEXT: idx2 = idx2; idx2 > var12; idx2 += inc0 */
0xb8c5e009, /* 001C LCD: idx3 = *(idx1 + var00000015); ; idx3 += inc1 */
0x03fec398, /* 0020 DRD1A: *idx0 = *idx3; FN=0 init=24 WS=3 RS=2 */
0x9919826a, /* 0024 LCD: idx2 = idx2, idx3 = idx3; idx2 > var9; idx2 += inc5, idx3 += inc2 */
0x0feac398, /* 0028 DRD1A: *idx0 = *idx3; FN=0 TFD INT init=24 WS=1 RS=2 */
0x99190036, /* 002C LCD: idx2 = idx2; idx2 once var0; idx2 += inc6 */
0x60000005, /* 0030 DRD2A: EU0=0 EU1=0 EU2=0 EU3=5 EXT init=0 WS=0 RS=0 */
0x0c4cf889, /* 0034 DRD2B1: *idx1 = EU3(); EU3(idx2,var9) */
0x000001f8, /* 0038 NOP */
0x9950d210,
0x2c4cf889,
0
};
union
{
unsigned long i;
struct
{
unsigned sb:3; // [02:00] increment #2
unsigned sa:3; // [05:03] increment #1
unsigned tc:6; // [11:06] variable to which idx is compared
unsigned drtc:1; // [12] dr ? *(tc) : (tc)
unsigned tu:2; // [14:13] term usage 00-idx_a, 01-idx_b, 10- lit init 11-no cond
unsigned ib:6; // [20:15] init_b
unsigned drib:1; // [21] dr ? *(init_a) : (init_a)
unsigned p:1; // [22] indx plus offset
unsigned ia:6; // [28:23] init_a
unsigned dria:1; // [29] dr ? *(init_a) : (init_a)
unsigned ext:1; // [30] = 2 or 3 for LCD
unsigned op:1; // [31] = 2 or 3 for LCD
} lcd;
struct
{
unsigned ll:13; // [12:00] literal init low
unsigned tu:2; // [14:13] term usage == 2
unsigned lh:15; // [29:15] literal init hi
unsigned bas:1; // [30] = 2 or 3 for LCD
unsigned op:1; // [31] = 2 or 3 for LCD
} lcdl;
struct
{
unsigned fn:3; // [02:00]
unsigned src:6; // [08:03]
unsigned drs:1; // [09] deref src?
unsigned dst:6; // [15:10]
unsigned drd:1; // [16] deref dst? scal?
unsigned ws:2; // [18:17] 0-32bit, 1-1 byte, 2-16 word, 3-dynamic/reserved??
unsigned rs:2; // [20:19] 0-32bit, 1-1 byte, 2-16 word, 3-dynamic/reserved??
unsigned init:5; // [25:21]
unsigned intr:1; // [26]
unsigned tfd:1; // [27]
unsigned more:1; // [28]
unsigned type:1; // [29]
unsigned ext:1; // [30]
unsigned op:1; // [31] = 0 for DRD
} drd1a;
struct
{
unsigned eu3:4; // [03:00] Execution Unit3 Function number
unsigned eu2:4; // [07:04] Execution Unit3 Function number
unsigned eu1:4; // [11:08] Execution Unit3 Function number
unsigned eu0:4; // [15:12] Execution Unit3 Function number
unsigned drd:1; // [16] deref dst? scal?
unsigned ws:2; // [18:17] 0-32bit, 1-1 byte, 2-16 word, 3-dynamic/reserved??
unsigned rs:2; // [20:19] 0-32bit, 1-1 byte, 2-16 word, 3-dynamic/reserved??
unsigned init:5; // [25:21]
unsigned int:1; // [26]
unsigned tfd:1; // [27]
unsigned more:1; // [28]
unsigned type:1; // [29]
unsigned ext:1; // [30]
unsigned op:1; // [31] = 0 for DRD
} drd2a;
struct
{
unsigned od1:6; // [05:00] EU operand 1
unsigned od0:6; // [11:06] EU operand 0
unsigned eu:2; // [13:12] EU#, only #3 available on MPC5200
unsigned mems:6; // [19:14] memory write source
unsigned drds:1; // [20]
unsigned rsv2:1; // [21]
unsigned memd:6; // [27:22] memory write destination
unsigned rsv1:1; // [28] reserved?
unsigned type:1; // [29]
unsigned ext:1; // [30]
unsigned op:1; // [31] = 0 for DRD
} drd2b1;
struct
{
unsigned odb1:6; // [05:00] EUb operand 1/3/5/7
unsigned odb0:6; // [11:06] EUb operand 0/2/4/6
unsigned eub:2; // [13:12] EUb#, only #3 available on MPC5200
unsigned oda1:6; // [19:14] EUa operand 1/3/5/7
unsigned oda0:6; // [25:20] EUa operand 0/2/4/6
unsigned eua:2; // [27:26] EUa#, only #3 available on MPC5200
unsigned rsv1:1; // [28]
unsigned type:1; // [29]
unsigned ext:1; // [30]
unsigned op:1; // [31] = 0 for DRD
} drd2b2;
} x;
// Condition codes (first 4 bits of the increments)
// >= c
// > 4
// once 0
// != 6
// <= a
// < 2
char *ft[] = {
"ld_acc", "unld_acc", "and", "or", "xor" "andn", "not",
"add", "sub", "lsh", "rsh", "crc8", "crc16", "crc32",
"endian32", "endian16"
};
char s[8];
indvar (int i)
{
switch ((i & 0x3f) >> 4)
{
case 0:
sprintf (s, "var%d", i & 0x1f); // varN
break;
case 1:
sprintf (s, "inc%d", i & 0x07); // incN
break;
case 2: // extraN
case 3:
sprintf (s, "idx%d", i & 0x0f); // idxN
break;
}
}
#define L2 LLev*2
main ()
{
int i, j, LLev = 0, drdt, ex = 0, ml;
char in[12];
for (i = 0; fw[i]; i++)
{
x.i = fw[i];
if (x.i == 0x1f8)
printf ("0x%08x, // NOP\n", x.i);
else if (x.lcd.op) // LCD
{
if (LLev == 0)
strcpy (in, "");
else
strcpy (in, " ");
if (x.lcd.ext) // LCDEXT
{
indvar (x.lcd.tc);
printf
("0x%08x, // %sLCDEXT: idx%d=idx%d; idx%d >%s%s ; idx%d += inc%d\n",
x.i, in, L2, L2, L2, x.lcd.drtc ? "*" : " ", s, L2,
x.lcd.sb);
} else if (x.lcd.tu == 2) // LCDLIT
{
printf ("0x%08x, // %sLCDLIT: idx%d = 0x%07x ??\n",
x.i, in, L2, (x.lcdl.lh << 13) | x.lcdl.ll);
} else // LCD
{
printf ("0x%08x, // %sLCD: ", x.i, in);
if (x.lcd.p) // indexed p-plus
{
indvar (x.lcd.ia);
printf ("idx%d = %s(%s + ", L2 + 1, x.lcd.dria ? "*" : " ", s);
indvar (x.lcd.ib);
printf ("%s%s); ; ", x.lcd.drib ? "*" : " ", s);
} else
{
indvar (x.lcd.ia);
printf ("idx%d=%s%s, ", L2, x.lcd.dria ? "*" : " ", s);
indvar (x.lcd.ib);
printf ("idx%d=%s%s, ", L2 + 1, x.lcd.drib ? "*" : " ", s);
}
if (x.lcd.tu < 2)
{
indvar (x.lcd.tc);
printf ("idx%d <=%s%s, ", (x.lcd.tu == 0) ? L2 : L2 + 1,
x.lcd.drtc ? "*" : " ", s);
}
if (x.lcd.tu != 3)
printf ("idx%d += inc%01x, ", L2, x.lcd.sa);
printf ("idx%d += inc%01x\n", L2 + 1, x.lcd.sb);
ex = 0;
}
}
else
{
// DRD
if (LLev == 0)
strcpy (in, " ");
else
strcpy (in, " ");
drdt = (ex << 2) | (x.drd1a.ext << 1) | x.drd1a.type;
switch (drdt)
{
case 0: // DRD1A
printf ("0x%08x, // %sDRD1A: ", x.i, in);
indvar (x.drd1a.dst);
printf ("%s%s =", x.drd1a.drd ? "*" : " ", s);
indvar (x.drd1a.src);
printf ("%s%s; ", x.drd1a.drs ? "*" : " ", s);
printf ("FN=%d (%s) ", x.drd1a.fn, ft[x.drd1a.fn]);
if (x.drd1a.more)
printf ("MORE ");
if (x.drd1a.tfd)
printf ("TFD ");
if (x.drd1a.intr)
printf ("INT ");
printf ("init=%d ", x.drd1a.init);
printf ("WS=%d RS=%d \n", x.drd1a.ws, x.drd1a.rs);
break;
case 3: // DRD2A
ex = x.drd2a.ext;
printf ("0x%08x, // %sDRD2A: ", x.i, in);
printf ("EU0=0 EU1=0 EU2=0 EU3=%d (%s) EXT init=%d ",
x.drd2a.eu3, ft[x.drd2a.eu3], x.drd2a.init);
printf ("WS=%d RS=%d \n", x.drd2a.ws, x.drd2a.rs);
break;
case 4: // DRD2B1
printf ("0x%08x, // %sDRD2B1: ", x.i, in);
indvar (x.drd2b1.memd); // mems ??
printf ("*%s=EU%d(); ", s, x.drd2b1.eu);
indvar (x.drd2b1.od0); printf ("EU%d(%s, ", x.drd2b1.eu, s);
indvar (x.drd2b1.od1); printf ("%s) \n", s);
break;
case 5: // DRD2B2
case 7: // DRD2B2?
printf ("0x%08x, // %sDRD2B2: ", x.i, in);
indvar (x.drd2b2.oda0); printf ("EU%d(%s, ", x.drd2b2.eub, s);
indvar (x.drd2b2.oda1); printf ("%s) , ", s);
indvar (x.drd2b2.odb0); printf ("EU%d(%s, ", x.drd2b2.eua, s);
indvar (x.drd2b2.odb1); printf ("%s) ??\n", s);
break;
case 1:
case 2:
case 6:
printf ("fix_me\n");
ex = x.drd1a.ext;
break;
}
LLev |= 1;
}
}
}
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: Bestcomm tasks and interrupts on MPC5200(B)
2009-01-20 13:15 Bestcomm tasks and interrupts on MPC5200(B) Dave Best
2009-01-20 15:51 ` Frank Bennett
2009-01-20 15:59 ` Frank Bennett
@ 2009-01-20 16:04 ` Grant Likely
2009-01-21 12:57 ` Dave Best
2 siblings, 1 reply; 7+ messages in thread
From: Grant Likely @ 2009-01-20 16:04 UTC (permalink / raw)
To: arieswar24b; +Cc: linuxppc-dev
[-- Attachment #1: Type: text/plain, Size: 1042 bytes --]
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.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-Bestcomm-localplus-test-utility.patch --]
[-- Type: text/x-patch; name=0001-Add-Bestcomm-localplus-test-utility.patch, Size: 27962 bytes --]
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
^ permalink raw reply related [flat|nested] 7+ messages in thread