On Tue, Jan 20, 2009 at 6:15 AM, Dave Best <arieswar...@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.
From 23ca0c4b1fa01ace41720aaa0fb32bd4351d0afc Mon Sep 17 00:00:00 2001 From: Grant Likely <grant.lik...@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 <scavan...@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
_______________________________________________ Linuxppc-dev mailing list Linuxppc-dev@ozlabs.org https://ozlabs.org/mailman/listinfo/linuxppc-dev