summaryrefslogtreecommitdiff
path: root/drivers/fims/fim.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/fims/fim.c')
-rw-r--r--drivers/fims/fim.c2216
1 files changed, 2216 insertions, 0 deletions
diff --git a/drivers/fims/fim.c b/drivers/fims/fim.c
new file mode 100644
index 000000000000..8df1db902a88
--- /dev/null
+++ b/drivers/fims/fim.c
@@ -0,0 +1,2216 @@
+/* -*- linux-c -*-
+ *
+ * drivers/fim.c
+ *
+ * Copyright (C) 2008 by Digi International Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * !Revision: $Revision: 1.45 $
+ * !Author: Silvano Najera, Luis Galdos
+ * !Descr:
+ * !References:
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/blkdev.h>
+#include <linux/spinlock.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/signal.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <asm/irq.h>
+#include <asm/signal.h>
+#include <linux/kfifo.h>
+#include <linux/sysfs.h>
+
+/* Arch-dependent header files */
+#include <mach/regs-sys-ns921x.h>
+#include <mach/regs-iohub-ns921x.h>
+#include <mach/irqs.h>
+
+/* Include the structure for the FIM-firmware */
+#include <mach/fim-ns921x.h>
+#include <mach/fim-firmware.h>
+
+#include "fim_reg.h"
+#include "dma.h"
+
+#define DRIVER_VERSION "v0.2"
+#define DRIVER_AUTHOR "Silvano Najera, Luis Galdos"
+#define DRIVER_DESC "FIMs driver"
+
+/* Module information */
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+MODULE_VERSION("0.1");
+
+#define DRIVER_NAME "fim"
+#define FIM_DRIVER_NAME "fims"
+#define FIM_BUS_TYPE_NAME "fim-bus"
+
+#define IRQ_NS921X_PIC IRQ_NS921X_PIC0
+#define FIM0_SHIFT 6
+
+#if 0
+#define FIM_CORE_DEBUG
+#endif
+
+#define printk_err(fmt, args...) printk(KERN_ERR "[ ERROR ] fims: " fmt, ## args)
+#define printk_info(fmt, args...) printk(KERN_INFO "fims: " fmt, ## args)
+#define printk_warn(fmt, args...) printk(KERN_DEBUG "fims: " fmt, ## args)
+
+#ifdef FIM_CORE_DEBUG
+# define printk_debug(fmt, args...) printk(KERN_DEBUG "fims: %s() " fmt, __FUNCTION__ , ## args)
+#else
+# define printk_debug(fmt, args...)
+#endif
+
+
+#define PIC_IS_LOCKED(pic) (atomic_read(&pic->requested))
+#define PIC_LOCK_REQUEST(pic) do { atomic_set(&pic->requested, 1); } while (0)
+#define PIC_FREE_REQUEST(pic) do { atomic_set(&pic->requested, 0); } while (0)
+
+#define PIC_TX_LOCK(pic) do { atomic_set(&pic->tx_tasked, 1); } while (0)
+#define PIC_TX_FREE(pic) do { atomic_set(&pic->tx_tasked, 0); } while (0)
+#define PIC_TX_IS_FREE(pic) (!atomic_read(&pic->tx_tasked))
+
+
+/* Use the same function for all the binary attributes */
+#define FIM_SYSFS_ATTR(_name) \
+{ \
+ .attr = { \
+ .name = __stringify(_name), \
+ .mode = S_IRUGO, \
+ .owner = THIS_MODULE, \
+ }, \
+ .read = fim_sysfs_attr_read, \
+}
+
+
+struct fims_t {
+ struct pic_t pics[FIM_NR_PICS];
+ struct bus_type *bus_type;
+ struct device *bus_dev;
+ struct device_driver *driver;
+};
+
+static struct fims_t *the_fims;
+
+
+static int pic_config_output_clock_divisor(struct pic_t *pic,
+ struct fim_program_t *program);
+static int pic_send_interrupt(struct pic_t *pic, unsigned int code);
+
+
+static int pic_download_firmware(struct pic_t *pic, const unsigned char *buffer);
+static int pic_stop_and_reset(struct pic_t *pic);
+static int pic_start_at_zero(struct pic_t *pic);
+
+
+static int fim_remove(struct device *dev);
+static int fim_probe(struct device *dev);
+static irqreturn_t pic_irq(int irq, void *tpic);
+static int pic_dma_init(struct pic_t *pic, struct fim_dma_cfg_t *cfg);
+static void pic_dma_stop(struct pic_t *pic);
+static int pic_is_running(struct pic_t *pic);
+
+
+/* Interrupt handlers */
+static void isr_from_pic(struct pic_t *pic, int irqnr);
+static void isr_dma_tx(struct pic_t *pic, int irqnr);
+static void isr_dma_rx(struct pic_t *pic, int irqnr);
+static int fim_start_tx_dma(struct pic_t *pic);
+
+
+/*
+ * Be aware with this function, we have max. one PAGE available for writing into
+ * the return buffer
+ * When the module is unregistered it's NOT required to reset the attributes
+ */
+static ssize_t fim_sysfs_attr_read(struct kobject *kobj, struct bin_attribute *attr,
+ char *buffer, loff_t off, size_t count)
+{
+ int size, cnt;
+ struct device *dev;
+ struct pic_t *pic;
+ struct attribute *bin;
+ struct iohub_dma_desc_t *desc;
+ unsigned int ctrlreg;
+
+ dev = container_of(kobj, struct device, kobj);
+ bin = &attr->attr;
+ pic = (struct pic_t *)dev->driver_data;
+ if (!pic)
+ return 0;
+
+ /* Check for the correct attribute */
+ if (!strcmp(bin->name, "irq"))
+ size = snprintf(buffer, PAGE_SIZE, "%i", pic->irq);
+
+ else if (pic->driver && !strcmp(bin->name, "module"))
+ size = snprintf(buffer, PAGE_SIZE, "%s", pic->driver->driver.name);
+
+ else if (!strcmp(bin->name, "running"))
+ size = snprintf(buffer, PAGE_SIZE, "%i", pic_is_running(pic));
+
+ else if (pic->driver && !strcmp(bin->name, "version"))
+ size = snprintf(buffer, PAGE_SIZE, "@TODO: Version of the firmware");
+
+ else if (pic->driver && !strcmp(bin->name, "fwname"))
+ size = snprintf(buffer, PAGE_SIZE, "%s", pic->fw_name);
+
+ else if (pic->driver && !strcmp(bin->name, "fwlength"))
+ size = snprintf(buffer, PAGE_SIZE, "%i", pic->fw_length);
+
+ else if (pic->driver && !strcmp(bin->name, "txdma")) {
+ ctrlreg = readl(pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG);
+ size = snprintf(buffer, PAGE_SIZE, "CTRL 0x%08x\n", ctrlreg);
+ ctrlreg = readl(pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG);
+ for (cnt=0; cnt < pic->tx_fifo.length; cnt++) {
+ desc = fim_dma_get_by_index(&pic->tx_fifo, cnt);
+ size += snprintf(buffer + size,
+ PAGE_SIZE - size,
+ "[ TX %02i ] 0x%04x | %i\n",
+ cnt, desc->control, desc->length);
+ }
+ }
+
+ else if (pic->driver && !strcmp(bin->name, "rxdma")) {
+ ctrlreg = readl(pic->iohub_addr + IOHUB_RX_DMA_CTRL_REG);
+ size = snprintf(buffer, PAGE_SIZE, "CTRL 0x%08x\n", ctrlreg);
+ for (cnt=0; cnt < pic->rx_fifo.length; cnt++) {
+ desc = fim_dma_get_by_index(&pic->rx_fifo, cnt);
+ size += snprintf(buffer + size,
+ PAGE_SIZE - size,
+ "[ RX %02i ] 0x%04x | %i\n",
+ cnt, desc->control, desc->length);
+ }
+ }
+
+ else
+ size = 0;
+
+ /* Only allows a single read for this binary attributes */
+ if (off || count < size)
+ return 0;
+
+ return size;
+}
+
+
+/* These are the non-default binary attributes for the FIMs */
+static struct bin_attribute fim_sysfs_attrs[] = {
+ FIM_SYSFS_ATTR(irq),
+ FIM_SYSFS_ATTR(running),
+ FIM_SYSFS_ATTR(module),
+ FIM_SYSFS_ATTR(version),
+ FIM_SYSFS_ATTR(fwname),
+ FIM_SYSFS_ATTR(fwlength),
+ FIM_SYSFS_ATTR(txdma),
+ FIM_SYSFS_ATTR(rxdma),
+};
+
+
+
+/*
+ * This tasklet will search for full receive DMA-buffers
+ * Only if the PIC has an associated driver the corresponding callback will be started
+ */
+static void pic_rx_tasklet_func(unsigned long data)
+{
+ struct pic_t *pic;
+ struct iohub_dma_desc_t *desc;
+ struct fim_driver *driver;
+ struct fim_buffer_t buffer;
+ struct iohub_dma_fifo_t *fifo;
+
+ /* If no driver is attached, then the DMA-channel is normally disabled */
+ pic = (struct pic_t *)data;
+ if (!pic || !pic->driver)
+ return;
+
+ driver = pic->driver;
+ fifo = &pic->rx_fifo;
+
+ /* The pointer to DMA-first will be updated in the locked segment! */
+ spin_lock(&pic->rx_lock);
+ desc = fifo->dma_first;
+
+ /*
+ * If a driver is waiting for the data then create a callback buffer,
+ * otherwise only restore the DMA-buffer descriptor
+ */
+ while (desc->control & IOHUB_DMA_DESC_CTRL_FULL) {
+ /*
+ * If the driver doesn't have a RX-callback then only free
+ * the DMA-descriptors
+ */
+ if (driver->dma_rx_isr) {
+ buffer.data = phys_to_virt(desc->src);
+ buffer.length = desc->length;
+ pic->driver->dma_rx_isr(driver, pic->irq, &buffer);
+ }
+
+ /* And free the DMA-descriptor for a next transfer */
+ desc->length = pic->dma_cfg.rxsz;
+ desc->control &= ~(IOHUB_DMA_DESC_CTRL_FULL |
+ IOHUB_DMA_DESC_CTRL_LAST);
+ desc = fim_dma_get_next(fifo, desc);
+ }
+
+ /* Update the buffer index for the next tasklet */
+ fifo->dma_first = desc;
+
+ /* Check for previously detected errors */
+ if (fifo->rx_error) {
+
+ if (pic->driver->dma_error_isr)
+ pic->driver->dma_error_isr(pic->driver, fifo->rx_error, 0);
+
+ /* fim_dma_reset_fifo(fifo); */
+ fifo->rx_error = 0;
+ }
+
+ spin_unlock(&pic->rx_lock);
+}
+
+
+
+inline static struct pic_t *get_pic_by_index(int index)
+{
+ if (index < FIM_MIN_PIC_INDEX || index > FIM_MAX_PIC_INDEX)
+ return NULL;
+
+
+ return &the_fims->pics[index];
+}
+
+
+static struct pic_t *get_pic_from_driver(struct fim_driver *driver)
+{
+ if (!driver)
+ return NULL;
+
+ return get_pic_by_index(driver->picnr);
+}
+
+
+static int pic_get_ctrl_reg(struct pic_t *pic, int reg, unsigned int *val)
+{
+ if (NS92XX_FIM_CTRL_REG_CHECK(reg))
+ return -EINVAL;
+
+ *val = readl(pic->reg_addr + NS92XX_FIM_CTRL_REG(reg));
+ return 0;
+}
+
+
+static void pic_set_ctrl_reg(struct pic_t *pic, int reg, unsigned int val)
+{
+ if (NS92XX_FIM_CTRL_REG_CHECK(reg))
+ return;
+
+ writel(val, pic->reg_addr + NS92XX_FIM_CTRL_REG(reg));
+}
+
+
+static int pic_is_running(struct pic_t *pic)
+{
+ unsigned int regval;
+
+ regval = readl(pic->reg_addr + NS92XX_FIM_GEN_CTRL_REG);
+ if (regval & NS92XX_FIM_GEN_CTRL_PROGMEM)
+ return 1;
+ else
+ return 0;
+}
+
+int fim_is_running(struct fim_driver *driver)
+{
+ struct pic_t *pic;
+
+ if (!(pic = get_pic_from_driver(driver)))
+ return -ENODEV;
+
+ return pic_is_running(pic);
+}
+
+
+static void fim_pic_release(struct device *dev)
+{
+ /* @TODO: Nothing to do here? */
+}
+
+
+/*
+ * Returns the number of available bytes that can be theoretically requested
+ * for a TX-transfer with more than one buffer descriptor
+ */
+int fim_tx_buffers_room(struct fim_driver *driver)
+{
+ int retval, cnt;
+ struct pic_dma_desc_t *pic_desc;
+ struct pic_t *pic;
+
+ if (!(pic = get_pic_by_index(driver->picnr)))
+ return -ENODEV;
+
+ retval = 0;
+ for (cnt=0; cnt < pic->tx_fifo.length; cnt++) {
+ pic_desc = pic->tx_desc + cnt;
+ if (atomic_read(&pic_desc->tasked))
+ continue;
+
+ retval += pic_desc->length;
+ }
+
+ return retval;
+}
+
+
+
+int fim_tx_buffers_level(struct fim_driver *driver)
+{
+ int retval, cnt;
+ struct pic_dma_desc_t *pic_desc;
+ struct pic_t *pic;
+
+ if (!(pic = get_pic_by_index(driver->picnr)))
+ return -ENODEV;
+
+ retval = 0;
+ for (cnt=0; cnt < pic->tx_fifo.length; cnt++) {
+ pic_desc = pic->tx_desc + cnt;
+ if (atomic_read(&pic_desc->tasked))
+ retval += pic_desc->length;
+ }
+
+ return retval;
+}
+
+
+int fim_number_pics(void)
+{
+ return FIM_NR_PICS;
+}
+
+
+
+/*
+ * Allocate a new FIM-buffer
+ * @TODO: Bind this buffer to an internal list of the driver, so that we can free
+ * all the buffers if the driver is being unloaded
+ */
+struct fim_buffer_t *fim_alloc_buffer(struct fim_driver *driver, int length,
+ unsigned int gfp_flags)
+{
+ struct fim_buffer_t *retval;
+
+ if (!driver || length < 0)
+ return NULL;
+
+ if (!(retval = kmalloc(sizeof(struct fim_buffer_t) + length , gfp_flags)))
+ return NULL;
+
+ retval->sent = 0;
+ retval->length = length;
+ retval->data = (void *)retval + sizeof(struct fim_buffer_t);
+ retval->private = NULL;
+ return retval;
+}
+
+
+/*
+ * Free an already requested FIM-buffer
+ * @TODO: Use the internal list for removing this buffer
+ */
+void fim_free_buffer(struct fim_driver *driver, struct fim_buffer_t *buffer)
+{
+ if (!driver || !buffer)
+ return;
+
+ kfree(buffer);
+}
+
+
+/*
+ * Be sure that you protect this function correctly.
+ * Use the spinlock tx_lock for this purpose.
+ */
+inline static void pic_reset_tx_fifo(struct pic_t *pic)
+{
+ int cnt;
+ struct iohub_dma_desc_t *desc;
+ struct pic_dma_desc_t *pic_desc;
+
+ fim_dma_reset_fifo(&pic->tx_fifo);
+ for(cnt=0; cnt < pic->tx_fifo.length; cnt++) {
+ desc = fim_dma_get_by_index(&pic->tx_fifo, cnt);
+ desc->control &= ~IOHUB_DMA_DESC_CTRL_FULL;
+ desc->length = 0;
+ pic_desc = pic->tx_desc + cnt;
+ atomic_set(&pic_desc->tasked, 0);
+ }
+}
+
+
+/*
+ * Check if there are enough buffers for the requested memory size
+ * In success then will try to restart the DMA-channel
+ * The return value is different then ZERO by errors.
+ * This function can be called from an interrupt context too.
+ */
+int fim_send_buffer(struct fim_driver *driver, const struct fim_buffer_t *bufdesc)
+{
+ struct pic_t *pic;
+ int cnt, len, last_len, pos;
+ struct pic_dma_desc_t *pic_desc;
+ struct pic_dma_desc_t *pic_descs[IOHUB_MAX_DMA_BUFFERS] = { NULL }; /* @XXX */
+ struct iohub_dma_desc_t *desc;
+ int backup_len;
+ int count, retval;
+ unsigned char *buffer;
+
+ if (!bufdesc || !driver || (bufdesc->length <= 0) || !bufdesc->data ||
+ !bufdesc->private)
+ return -EINVAL;
+
+ if (!(pic = get_pic_from_driver(driver)))
+ return -ENODEV;
+
+ /*
+ * Lock this section so that the PIC can be unregistered now
+ */
+ spin_lock(&pic->tx_lock);
+ if (!pic->driver) {
+ retval = -ENODEV;
+ goto exit_ret;
+ }
+
+ /* Check for the available buffers */
+ len = count = bufdesc->length;
+ buffer = bufdesc->data;
+ pos = 0;
+ for (cnt=0; cnt < pic->tx_fifo.length; cnt++) {
+ pic_desc = pic->tx_desc + cnt;
+ if (atomic_read(&pic_desc->tasked))
+ continue;
+
+ atomic_set(&pic_desc->tasked, 1);
+ pic_descs[pos++] = pic_desc;
+
+ last_len = len;
+ len -= (int)pic_desc->length;
+ if (len <= 0) {
+ /* Try to get the correct number of FIFO descriptors */
+ if(!(desc = fim_dma_alloc(&pic->tx_fifo, pos))) {
+ printk_err(" Couldn't get the FIFO descriptors.\n");
+ break;
+ }
+
+ /* Copy the transfer data to the corresponding DMA-buffers */
+ backup_len = pic_desc->length;
+ pic_desc->length = last_len;
+ for (cnt=0; pic_descs[cnt] && cnt < pos; cnt++) {
+ len = pic_descs[cnt]->length;
+ memcpy(phys_to_virt(pic_descs[cnt]->src), buffer, len);
+ desc->src = pic_descs[cnt]->src;
+ desc->reserved = 0;
+ desc->length = len;
+ buffer += len;
+ desc = fim_dma_get_next(&pic->tx_fifo, desc);
+ }
+ pic_desc->length = backup_len;
+
+ /*
+ * Save the private data into the first PIC-DMA descriptor
+ * This private data will be used inside the TX-callback function
+ * @BUG: We must save the requested length for the TX-callback,
+ * then the DMA-controller set the length to zero.
+ */
+ pic_descs[0]->private = bufdesc->private;
+ pic_descs[0]->total_length = bufdesc->length;
+ fim_start_tx_dma(pic);
+ retval = 0;
+ goto exit_ret;
+ }
+ }
+
+ printk_debug("No free TX-buffers available (%i). Aborting.\n", bufdesc->length);
+ for (cnt=0; pic_descs[cnt] && cnt < pic->tx_fifo.length; cnt++)
+ atomic_dec(&pic_descs[cnt]->tasked);
+ retval = -ENOMEM;
+
+ exit_ret:
+ spin_unlock(&pic->tx_lock);
+ return retval;
+}
+
+
+/* @TODO: Please test this function before using it */
+void fim_flush_rx(struct fim_driver *driver)
+{
+ unsigned int cnt;
+ struct pic_t *pic;
+ struct iohub_dma_desc_t *desc;
+ struct iohub_dma_fifo_t *fifo;
+
+ if (!(pic = get_pic_by_index(driver->picnr)))
+ return;
+
+ writel(0x00, pic->iohub_addr + IOHUB_RX_DMA_ICTRL_REG);
+ writel(IOHUB_RX_DMA_CTRL_CA, pic->iohub_addr + IOHUB_RX_DMA_CTRL_REG);
+ for (cnt=0; cnt < pic->rx_fifo.length; cnt++) {
+ desc = fim_dma_get_by_index(&pic->rx_fifo, cnt);
+ desc->control = IOHUB_DMA_DESC_CTRL_INT;
+ }
+
+ writel(IOHUB_ICTRL_RXNCIE | IOHUB_ICTRL_RXNRIE | IOHUB_ICTRL_RXPCIE,
+ pic->iohub_addr + IOHUB_RX_DMA_ICTRL_REG);
+ writel(pic->rx_fifo.phys_descs, pic->iohub_addr + IOHUB_RX_DMA_BUFPTR_REG);
+ writel(IOHUB_RX_DMA_CTRL_CE, pic->iohub_addr + IOHUB_RX_DMA_CTRL_REG);
+
+ /* Reset the internal fifo too */
+ fifo = &pic->rx_fifo;
+ fim_dma_reset_fifo(fifo);
+}
+
+
+/*
+ * Drain the TX-buffers by first aborting the DMA-channel. The DMA-descriptors
+ * will be reseted inside the interrupt routine
+ */
+void fim_flush_tx(struct fim_driver *driver)
+{
+ struct pic_t *pic;
+
+ if (!(pic = get_pic_by_index(driver->picnr)))
+ return;
+
+ pic_reset_tx_fifo(pic);
+
+ writel(IOHUB_RX_DMA_CTRL_CA, pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG);
+}
+
+
+
+/*
+ * This function is responsible for restarting the DMA-channel with the correct index
+ * If the DMA-channel is transferring data it will returns inmediately, but the
+ * callback of the DMA-TX will recall this function and send the requested data buffers
+ */
+static int fim_start_tx_dma(struct pic_t *pic)
+{
+ unsigned long txctrl;
+ struct iohub_dma_desc_t *desc = NULL;
+ struct iohub_dma_fifo_t *fifo;
+ int retval = 0;
+ unsigned int channel_index, fifo_index;
+
+ if (fim_dma_is_empty(&pic->tx_fifo)) {
+ printk(KERN_DEBUG "Nothing to do, the TX-FIFO is empty.\n");
+ return 0;
+ }
+
+ /* @XXX: Need to protect this section */
+ if (atomic_read(&pic->tx_tasked)) {
+ printk_debug("The DMA-Controller seems to be tasked.\n");
+ return 0;
+ }
+
+ /* Lock the next DMA-transfer and get the internal data */
+ atomic_set(&pic->tx_tasked, 1);
+
+ spin_lock(&pic->tx_lock);
+ fifo = &pic->tx_fifo;
+ fifo->dma_last = fifo->dma_next;
+
+ /*
+ * Write the control bit fields for the new TX-transfer
+ * The close-buffer interrupt will be enabled only for the last descriptor!
+ */
+ for (desc=fifo->dma_first; ; desc=fim_dma_get_next(fifo, desc)) {
+ if (desc->length <= 0) {
+ printk_err("Invalid FIFO descriptor 0x%p length, %u\n",
+ desc, desc->length);
+ retval = -EINVAL;
+ goto exit_unlock;
+ }
+
+ /* IMPORTANT: Enable the interrupt only for the last buffer! */
+ desc->control = IOHUB_DMA_DESC_CTRL_FULL;
+ if (desc == fifo->last)
+ desc->control |= IOHUB_DMA_DESC_CTRL_WRAP;
+ if (desc == fifo->dma_last) {
+ desc->control |= (IOHUB_DMA_DESC_CTRL_INT |
+ IOHUB_DMA_DESC_CTRL_LAST);
+ break;
+ }
+ }
+
+ /* Get the index of the first FIFO descriptor for the DMA-controller */
+ fifo_index = fim_dma_get_index(&pic->tx_fifo, fifo->dma_first);
+ txctrl = readl(pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG);
+ txctrl |= IOHUB_TX_DMA_CTRL_INDEXEN | IOHUB_TX_DMA_CTRL_INDEX(fifo_index);
+ writel(txctrl, pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG);
+
+ /*
+ * If the TX-FIFO and the DMA-Channel have different index then we have
+ * an error, then normally they must have the same value.
+ * @FIXME: Workaround for this problem, or remove the paranoic sanity check
+ */
+ channel_index = readl(pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG) & 0x3FF;
+ if (fifo_index && (fifo_index != channel_index)) {
+ printk_warn("FIFO index 0x%X mismatch DMA index 0x%X\n",
+ fifo_index, channel_index);
+ } else {
+ printk_debug("DMA index %i for next DMA buffer\n", channel_index);
+ }
+
+ /*
+ * Check if an abort interrupt was executed in the time we have
+ * configured the DMA-descriptors. In that skip the channel restart
+ */
+ if (!atomic_read(&pic->tx_aborted)) {
+ writel(txctrl & ~IOHUB_TX_DMA_CTRL_CE,
+ pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG);
+ writel(txctrl | IOHUB_TX_DMA_CTRL_CE,
+ pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG);
+ } else
+ atomic_set(&pic->tx_tasked, 0);
+
+ exit_unlock:
+ spin_unlock(&pic->tx_lock);
+ return retval;
+}
+
+
+
+
+/* DONT poll with this function. We need another function for this purpose. */
+int fim_get_exp_reg(struct fim_driver *driver, int nr, unsigned int *value)
+{
+ struct pic_t *pic;
+
+ if (!driver)
+ return -EINVAL;
+
+ if (!(pic = get_pic_by_index(driver->picnr)))
+ return -EINVAL;
+
+ *value = readl(pic->reg_addr + NS92XX_FIM_EXP_REG(nr));
+
+ return 0;
+}
+
+
+
+/* Called when the PIC interrupts the ARM-processor */
+static void isr_from_pic(struct pic_t *pic, int irqnr)
+{
+ unsigned int status;
+ struct fim_driver *driver;
+ unsigned int rx_fifo;
+ unsigned int timeout;
+
+ status = readl(pic->reg_addr + NS92XX_FIM_GEN_CTRL_REG);
+ rx_fifo = readl(pic->iohub_addr + IOHUB_RX_DIR_FIFO_REG);
+
+ driver = pic->driver;
+ if (driver && driver->fim_isr)
+ driver->fim_isr(driver, pic->irq, status & 0xFF, rx_fifo);
+
+ /* @TEST */
+ writel(status, pic->reg_addr + NS92XX_FIM_CTRL7_REG);
+
+ writel(status | NS92XX_FIM_GEN_CTRL_INTACKWR,
+ pic->reg_addr + NS92XX_FIM_GEN_CTRL_REG);
+
+ timeout = 0xFFFF;
+ do {
+ timeout--;
+ status = readl(pic->reg_addr + NS92XX_FIM_GEN_CTRL_REG);
+ } while (timeout && (status & NS92XX_FIM_GEN_CTRL_INTFROMPIC));
+
+ /* @XXX: Should we stop the PIC for avoiding more timeout errors? */
+ if (!timeout) {
+ printk_err("FIM %i interrupt handshake timeout.\n", pic->index);
+ return;
+ }
+
+ writel(status & ~NS92XX_FIM_GEN_CTRL_INTACKWR, pic->reg_addr +
+ NS92XX_FIM_GEN_CTRL_REG);
+}
+
+
+
+static void isr_dma_tx(struct pic_t *pic, int irqnr)
+{
+ struct iohub_dma_fifo_t *fifo;
+ unsigned int ifs, txctrl, ictrl;
+ int cnt;
+ struct iohub_dma_desc_t *desc;
+ struct pic_dma_desc_t *pic_desc;
+ struct fim_driver *driver;
+ struct fim_buffer_t buffer= { 0, NULL, NULL };
+
+ fifo = &pic->tx_fifo;
+ ifs = readl(pic->iohub_addr + IOHUB_IFS_REG);
+ if (ifs & IOHUB_IFS_TXNRIP) {
+ /*
+ * From the Net+OS driver seems to be that the DMA controller is
+ * setting the TXNRIP status bit although no error happened. But for
+ * being sure that no error happened, we check the full control bit
+ * of the last buffer descriptor: If this bit is high then the
+ * last buffer was not transferred, ERROR!
+ */
+ txctrl = readl(pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG) - 1;
+ desc = fifo->dma_last;
+ if(desc->control & IOHUB_DMA_DESC_CTRL_FULL) {
+ ictrl = readl(pic->iohub_addr + IOHUB_TX_ICTRL_REG);
+ printk_err("TXNRIP: ICTRL 0x%08x | CTRL 0x%08x | DESC 0x%p\n",
+ ictrl, txctrl, desc);
+ }
+ }
+
+ /*
+ * The channel abort is normally being called for flushing the TX-buffers.
+ */
+ if (ifs & IOHUB_IFS_TXCAIP) {
+
+ atomic_set(&pic->tx_aborted, 1);
+ spin_lock(&pic->tx_lock);
+ printk_debug("TXCAIP: Freeing the TX-buffers\n");
+ pic_reset_tx_fifo(pic);
+
+ /* Reset the index of the TX-channel to zero */
+ writel(IOHUB_RX_DMA_CTRL_CA | IOHUB_TX_DMA_CTRL_INDEXEN,
+ pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG);
+ writel(0x00, pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG);
+
+ /* Reset the atomic variables for enabling the TX-requests */
+ atomic_set(&pic->tx_aborted, 0);
+ atomic_set(&pic->tx_tasked, 0);
+ spin_unlock(&pic->tx_lock);
+ return;
+ }
+
+
+ /*
+ * If no NC interrupt pending then return immediately.
+ * @FIXME: Is possible that a driver is waiting for the TXNCIP on this case?
+ */
+ if(!(ifs & IOHUB_IFS_TXNCIP))
+ return;
+
+ /* Now free the allocated PIC-DMA descriptors */
+ desc = fifo->dma_first;
+ driver = pic->driver;
+ do {
+ for(cnt=0; cnt < pic->tx_fifo.length; cnt++) {
+ pic_desc = pic->tx_desc + cnt;
+ if(pic_desc->src == desc->src) {
+ if(!buffer.data) {
+ buffer.data = phys_to_virt(desc->src);
+ buffer.private = pic_desc->private;
+ }
+
+ /*
+ * @BUG: The DMA-channel seems to reset the buffer length
+ * otherwise use: buffer.length += desc->length;
+ */
+ buffer.length = pic_desc->total_length;
+ desc->src = 0;
+ atomic_set(&pic_desc->tasked, 0);
+ break;
+ }
+ }
+ desc = fim_dma_get_next(fifo, desc);
+ } while (desc != fim_dma_get_next(fifo, fifo->dma_last));
+
+
+ /* Now give the control to the driver's callback function */
+ if (driver->dma_tx_isr)
+ driver->dma_tx_isr(driver, pic->irq, &buffer);
+
+ /* Free the DMA-controller */
+ fifo->dma_first = fim_dma_get_next(fifo, fifo->dma_last);
+ atomic_set(&pic->tx_tasked, 0);
+
+ /* Check if another descriptors are waiting in the FIFO */
+ if (fifo->dma_next != fifo->dma_last)
+ fim_start_tx_dma(pic);
+
+}
+
+
+
+/* Only check that no errors ocurred by the last buffer descriptor */
+static void isr_dma_rx(struct pic_t *pic, int irqnr)
+{
+ unsigned int ifs;
+ struct iohub_dma_fifo_t *fifo;
+ unsigned int rxctrl, ictrl;
+ unsigned long error;
+ int verbose;
+
+ /* Get the selected verbosity */
+ verbose = (pic->driver) ? (pic->driver->verbose) : (0);
+
+ /* If we have an error, then always restart the DMA-channel */
+ ifs = readl(pic->iohub_addr + IOHUB_IFS_REG);
+ error = 0;
+
+ if (ifs & IOHUB_IFS_RXNRIP) {
+ ictrl = readl(pic->iohub_addr + IOHUB_RX_DMA_ICTRL_REG);
+ rxctrl = readl(pic->iohub_addr + IOHUB_RX_DMA_CTRL_REG);
+
+ if (verbose && printk_ratelimit())
+ printk_warn("RXNRIP: ictrl 0x%08x | rxctrl 0x%08x\n", ictrl, rxctrl);
+ error |= IOHUB_IFS_RXNRIP;
+ }
+
+ if (!(ifs & IOHUB_IFS_RXNCIP)) {
+
+ if (verbose && printk_ratelimit())
+ printk_warn("RXNCIP: Unexpected state (ifs: %x)\n", ifs);
+ error |= IOHUB_IFS_RXNCIP;
+ }
+
+ /* If we have a failure, then reset the DMA-controller and -FIFO */
+ if (error) {
+ fifo = &pic->rx_fifo;
+ writel(fifo->phys_descs, pic->iohub_addr + IOHUB_RX_DMA_BUFPTR_REG);
+ writel(0x00, pic->iohub_addr + IOHUB_RX_DMA_CTRL_REG);
+ writel(ifs, pic->iohub_addr + IOHUB_IFS_REG);
+ writel(IOHUB_RX_DMA_CTRL_CE, pic->iohub_addr + IOHUB_RX_DMA_CTRL_REG);
+
+ /* This is for the RX tasklet that will inform the FIM-driver about this failure */
+ fifo->rx_error = error;
+ }
+
+ /*
+ * @IMPORTANT: The API of Net+OS is restarting the RX-channel after each
+ * transfer, but we don't know exactly why. Since our API is working
+ * without the restart, we don't touch the RX-channel.
+ */
+#if 0
+ rxctrl = readl(pic->iohub_addr + IOHUB_RX_DMA_CTRL_REG);
+ rxctrl &= ~IOHUB_RX_DMA_CTRL_CE;
+ writel(rxctrl, pic->iohub_addr + IOHUB_RX_DMA_CTRL_REG);
+ writel(rxctrl | IOHUB_RX_DMA_CTRL_CE, pic->iohub_addr + IOHUB_RX_DMA_CTRL_REG);
+#endif
+
+ /* Schedule the task which will free the RX-DMA buffer descriptors */
+ tasklet_hi_schedule(&pic->rx_tasklet);
+}
+
+
+/* This is the main ISR for the PIC-interrupts */
+static irqreturn_t pic_irq(int irq, void *tpic)
+{
+ unsigned int ifs;
+ struct pic_t *pic = (struct pic_t *)tpic;
+ unsigned long flags;
+
+ spin_lock_irqsave(&pic->lock, flags);
+ ifs = readl(pic->iohub_addr + IOHUB_IFS_REG);
+
+ if (ifs & IOHUB_IFS_MODIP)
+ isr_from_pic(pic, irq);
+ if (ifs & IOHUB_IFS_DMA_TX)
+ isr_dma_tx(pic, irq);
+ if (ifs & IOHUB_IFS_DMA_RX)
+ isr_dma_rx(pic, irq);
+
+ writel(ifs, pic->iohub_addr + IOHUB_IFS_REG);
+ spin_unlock_irqrestore(&pic->lock, flags);
+ return IRQ_HANDLED;
+}
+
+
+
+int fim_enable_irq(struct fim_driver *driver)
+{
+ struct pic_t *pic;
+
+ if (!driver)
+ return -EINVAL;
+
+ if (!(pic = get_pic_from_driver(driver)))
+ return -EINVAL;
+
+ /* IRQ enable if required */
+ if (!atomic_read(&pic->irq_enabled)) {
+ printk_debug("Enabling the PIC IRQ %i\n", pic->irq);
+ enable_irq(pic->irq);
+ atomic_set(&pic->irq_enabled, 1);
+ }
+ return 0;
+}
+
+
+int fim_disable_irq(struct fim_driver *driver)
+{
+ struct pic_t *pic;
+
+ if (!driver)
+ return -EINVAL;
+
+ if (!(pic = get_pic_from_driver(driver)))
+ return -EINVAL;
+
+ /* Disable the IRQ */
+ if (atomic_read(&pic->irq_enabled)) {
+ printk_debug("Disabling the PIC IRQ %i\n", pic->irq);
+ disable_irq(pic->irq);
+ atomic_set(&pic->irq_enabled, 0);
+ }
+ return 0;
+}
+
+/*
+ * Function for downloading the FIM-firmware
+ * IMPORTANT: This function will automatically stop the FIM if it's running,
+ * additionally it will (try) to restore the current state of the DMA-channels
+ */
+int fim_download_firmware(struct fim_driver *driver)
+{
+ struct pic_t *pic;
+ int retval;
+ const struct firmware *fw;
+ const unsigned char *fwbuf;
+ unsigned int txctrl, rxctrl;
+
+ if (!(pic = get_pic_from_driver(driver)))
+ return -EINVAL;
+
+ /* Stop the FIM first */
+ if (pic_is_running(pic)) {
+ retval = pic_stop_and_reset(pic);
+ if (retval) {
+ printk_err("Couldn't stop the PIC %i\n", pic->index);
+ return retval;
+ }
+ }
+
+ /*
+ * Now download the firmware using the firmware-subsystem or from
+ * the passed array with the firmware code
+ */
+ if (driver->fw_name) {
+ printk_debug("Requesting the firmware '%s'\n", driver->fw_name);
+ snprintf(pic->fw_name, FIM_MAX_FIRMWARE_NAME, "%s", driver->fw_name);
+ retval = request_firmware(&fw, driver->fw_name, pic->dev);
+ if (retval) {
+ printk_err("request_firmware() failed, %i\n", retval);
+ goto exit_download;
+ }
+ fwbuf = fw->data;
+ } else if (driver->fw_code) {
+ printk_debug("Using the built-in firmware code\n");
+ fwbuf = driver->fw_code;
+ } else {
+ printk_err("No code and no firmware name passed? Aborting\n");
+ retval = -EINVAL;
+ goto exit_download;
+ }
+
+ /*
+ * Since the firmware download should be executed on the fly, disable
+ * the DMA-channels first, otherwise the channels will run like a
+ * spree killer
+ */
+ spin_lock(&pic->tx_lock);
+ txctrl = readl(pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG);
+ rxctrl = readl(pic->iohub_addr + IOHUB_RX_DMA_CTRL_REG);
+ writel(txctrl & ~IOHUB_TX_DMA_CTRL_CE, pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG);
+ writel(rxctrl & ~IOHUB_RX_DMA_CTRL_CE, pic->iohub_addr + IOHUB_RX_DMA_CTRL_REG);
+ retval = pic_download_firmware(pic, fwbuf);
+ writel(txctrl, pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG);
+ writel(rxctrl, pic->iohub_addr + IOHUB_RX_DMA_CTRL_REG);
+ spin_unlock(&pic->tx_lock);
+
+ /* Release the obtained data from the firmware-subsystem */
+ if (driver->fw_name)
+ release_firmware(fw);
+
+ if (retval)
+ printk_err("Firmware install in the PIC %i failed.\n", pic->index);
+
+ exit_download:
+ return retval;
+}
+
+
+int fim_register_driver(struct fim_driver *driver)
+{
+ struct pic_t *pic;
+ int retval;
+ const struct firmware *fw;
+ const unsigned char *fwbuf;
+
+ /* Sanity checks */
+ if (!driver || !driver->driver.name)
+ return -EINVAL;
+
+ if (!(pic = get_pic_by_index(driver->picnr)))
+ return -ENODEV;
+
+ if (pic->driver || PIC_IS_LOCKED(pic)) {
+ printk_err("PIC %i already requested.\n", pic->index);
+ return -EBUSY;
+ }
+
+ /* Lock the PIC request */
+ PIC_LOCK_REQUEST(pic);
+
+ /* Stop the PIC before any other action */
+ retval = pic_stop_and_reset(pic);
+ if (retval) {
+ printk_err("Couldn't stop and reset the FIM.\n");
+ goto exit_free_pic;
+ }
+
+ /* Try to get the firmware from the user space */
+ if (driver->fw_name) {
+ printk_debug("Requesting the firmware '%s'\n", driver->fw_name);
+ snprintf(pic->fw_name, FIM_MAX_FIRMWARE_NAME, "%s", driver->fw_name);
+ retval = request_firmware(&fw, driver->fw_name, pic->dev);
+ if (retval) {
+ printk_err("request_firmware() failed, %i\n", retval);
+ goto exit_free_pic;
+ }
+ fwbuf = fw->data;
+ } else if (driver->fw_code) {
+ printk_debug("Using the built-in firmware code\n");
+ fwbuf = driver->fw_code;
+ } else {
+ printk_err("%s: Neither code nor firmware passed. Aborting request.\n",
+ driver->driver.name);
+ goto exit_free_pic;
+ }
+
+ retval = pic_download_firmware(pic, fwbuf);
+
+ /* Release the obtained data from the firmware-subsystem */
+ if (driver->fw_name)
+ release_firmware(fw);
+
+ if (retval) {
+ printk_err("%s: Firmware install by the FIM%i failed.\n",
+ driver->driver.name, pic->index);
+ goto exit_free_pic;
+ }
+
+ /* Start the PIC at zero */
+ retval = pic_start_at_zero(pic);
+ if (retval) {
+ printk_err("%s: FIM%i couldn't be started at zero.\n",
+ driver->driver.name, pic->index);
+ goto exit_free_pic;
+ }
+
+ /*
+ * The DMA channels will be controlled by the CPU. Pass the driver-config
+ * values to the init-function
+ */
+ retval = pic_dma_init(pic, driver->dma_cfg);
+ if (retval) {
+ printk_err("%s: DMA channel init failed for the FIM%i\n",
+ driver->driver.name, pic->index);
+ goto goto_stop_pic;
+ }
+
+ /*
+ * Enable the tasklet before requesting the IRQ, then in some cases the
+ * FIM sends an interrupt before its initialization through the FIM-driver.
+ */
+ pic->driver = driver;
+ driver->dev = pic->dev;
+ tasklet_init(&pic->rx_tasklet, pic_rx_tasklet_func, (unsigned long)pic);
+
+ /* Request the IRQ for this FIM */
+ retval = request_irq(pic->irq, pic_irq, 0, driver->driver.name, pic);
+ if (retval) {
+ printk_err("%s: Couldn't request the IRQ %i\n",
+ driver->driver.name, pic->irq);
+ goto exit_kill_tasklet;
+ }
+
+ /* Disable the IRQ and done */
+ disable_irq(pic->irq);
+ atomic_set(&pic->irq_enabled, 0);
+ printk_info("FIM%i registered for driver '%s' [IRQ %i]\n",
+ pic->index, driver->driver.name, pic->irq);
+
+ return 0;
+
+ exit_kill_tasklet:
+ tasklet_kill(&pic->rx_tasklet);
+
+ goto_stop_pic:
+ pic_stop_and_reset(pic);
+
+ exit_free_pic:
+ PIC_FREE_REQUEST(pic);
+
+ return retval;
+}
+
+
+int fim_unregister_driver(struct fim_driver *driver)
+{
+ int ret;
+ struct pic_t *pic;
+
+ if (!driver)
+ return -EINVAL;
+
+ if (!(pic = get_pic_from_driver(driver)))
+ return -EINVAL;
+
+ if (!pic->driver || !PIC_IS_LOCKED(pic))
+ return -EINVAL;
+
+ /* Stop the PIC but wait for another running operations first */
+ spin_lock(&pic->tx_lock);
+ ret = pic_stop_and_reset(pic);
+ if (ret)
+ printk_err("Couldn't stop the PIC %i\n", pic->index);
+
+ fim_disable_irq(pic->driver);
+
+ /* Free the requested IRQ */
+ printk_debug("Freeing the IRQ %i\n", pic->irq);
+ free_irq(pic->irq, pic);
+
+ /* Free the DMA-resources */
+ tasklet_kill(&pic->rx_tasklet);
+ pic_dma_stop(pic);
+
+ /* And free the driver field */
+ pic->driver = NULL;
+ PIC_FREE_REQUEST(pic);
+ spin_unlock(&pic->tx_lock);
+ return 0;
+}
+
+
+/*
+ * Return a PIC-pointer for low level operations
+ * The driver structure contains the number of the pointer
+ */
+struct pic_t *fim_request_pic(int picnr)
+{
+ struct pic_t *pic;
+
+ if (!(pic = get_pic_by_index(picnr)))
+ return NULL;
+
+ if (pic->driver || PIC_IS_LOCKED(pic))
+ return NULL;
+
+ PIC_LOCK_REQUEST(pic);
+ writel(0x00, pic->reg_addr + NS92XX_FIM_GEN_CTRL_REG);
+ printk_info("PIC %i successful requested\n", pic->index);
+ return pic;
+}
+
+
+/*
+ * Free the PIC that was requested with the above function fim_request_pic()
+ */
+void fim_free_pic(struct pic_t *pic)
+{
+ if (!pic)
+ return;
+
+ if (!(get_pic_by_index(pic->index)))
+ return;
+
+ PIC_FREE_REQUEST(pic);
+}
+
+
+/*
+ * This function can be used for downloading a firmware to the PIC.
+ * Please note that this function will reset the complete IOHUB-module, included the
+ * DMA-configuration registers. That's important when the FIM-driver
+ * is using the index of the DMA-channel, then this will have the index zero after
+ * the download.
+ */
+static int pic_download_firmware(struct pic_t *pic, const unsigned char *buffer)
+{
+ int mode, ret;
+ unsigned int status;
+ struct fim_program_t *program = (struct fim_program_t *)buffer;
+ int offset;
+
+ if (!program || !pic)
+ return -EINVAL;
+
+ if (!(FORMAT_TYPE_VALID(program->format)))
+ return -EINVAL;
+
+ printk_debug("Starting the download of the PIC firmware.\n");
+
+ /* Check if the PIC is running, before starting the firmware update */
+ if (pic_is_running(pic)) {
+ printk_err("The PIC %i is running. Aborting the download.\n",
+ pic->index);
+ return -EBUSY;
+ }
+
+ /* Check if the firmware has the correct header */
+ if (!(PROCESSOR_TYPE_VALID(program->processor))) {
+ printk_err("Invalid processor type. Aborting firmware download.\n");
+ return -EINVAL;
+ }
+
+ /* Enable the clock to IO processor and reset the module */
+ status = readl(SYS_CLOCK);
+ writel(status | (1 << (pic->index + FIM0_SHIFT)), SYS_CLOCK);
+ status = readl(SYS_RESET);
+ writel(status & ~(1 << (pic->index + FIM0_SHIFT)), SYS_RESET);
+ writel(status | (1 << (pic->index + FIM0_SHIFT)), SYS_RESET);
+
+ /* Configure the output clock */
+ ret = pic_config_output_clock_divisor(pic, program);
+ if (ret) {
+ printk_err("Couldn't set the clock output divisor.\n");
+ return ret;
+ }
+
+ switch (program->hw_mode) {
+ case FIM_HW_ASSIST_MODE_NONE:
+ mode = 0x00;
+ break;
+ case FIM_HW_ASSIST_MODE_GENERIC:
+ mode = 0x01;
+ break;
+ case FIM_HW_ASSIST_MODE_CAN:
+ mode = 0x02;
+ break;
+ default:
+ printk_err("Invalid HWA mode %i\n", program->hw_mode);
+ return -EINVAL;
+ }
+
+ status = readl(pic->hwa_addr + NS92XX_FIM_HWA_GEN_CONF_REG);
+ writel(mode | status, pic->hwa_addr + NS92XX_FIM_HWA_GEN_CONF_REG);
+
+ /* Update the HW assist config registers */
+ for (offset = 0; offset < FIM_NUM_HWA_CONF_REGS; offset++) {
+ status = program->hwa_cfg[offset];
+ writel(status, pic->hwa_addr + NS92XX_FIM_HWA_SIGNAL(offset));
+ }
+
+ /* Check for the maximal supported number of instructions */
+ if (program->length > FIM_NS9215_MAX_INSTRUCTIONS) {
+ printk_err("Invalid firmware length %i (too long)\n", program->length);
+ return -EINVAL;
+ }
+
+ /* Start programming the PIC (the program size is in 16bit-words) */
+ printk_debug("Starting to program the firmware (%i Bytes)\n",
+ program->length);
+ for (offset = 0; offset < program->length; offset++)
+ writel(program->data[offset] & NS92XX_FIM_INSTRUCTION_MASK,
+ pic->instr_addr + 4*offset);
+
+ /* Save the firmware length into the internal structure for the sysfs */
+ pic->fw_length = program->length;
+ return 0;
+}
+
+
+
+static int pic_start_at_zero(struct pic_t *pic)
+{
+ unsigned int regval;
+
+ if (!pic)
+ return -EINVAL;
+ if (pic->index < 0)
+ return -ENODEV;
+
+ printk_debug("Starting the PIC %i at zero.\n", pic->index);
+
+ regval = readl(pic->reg_addr + NS92XX_FIM_GEN_CTRL_REG);
+ writel(regval | NS92XX_FIM_GEN_CTRL_START_PIC, pic->reg_addr +
+ NS92XX_FIM_GEN_CTRL_REG);
+
+ /* Check if the PIC is really running */
+ if (!pic_is_running(pic)) {
+ printk_err("Unable to start the PIC %i\n", pic->index);
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+
+static int pic_stop_and_reset(struct pic_t *pic)
+{
+ unsigned int regval;
+ int retval;
+
+ if (!pic)
+ return -EINVAL;
+
+ if (pic_is_running(pic))
+ printk_debug("The PIC %i is running, need to stop it.\n", pic->index);
+
+ regval = readl(pic->reg_addr + NS92XX_FIM_GEN_CTRL_REG);
+
+ /* Clear the interrupt flags too! */
+ regval &= ~NS92XX_FIM_INT_MASK(0xff);
+
+ writel(regval & NS92XX_FIM_GEN_CTRL_STOP_PIC,
+ pic->reg_addr + NS92XX_FIM_GEN_CTRL_REG);
+
+ retval = 0;
+ if (pic_is_running(pic)) {
+ printk_err("Unabled to stop the PIC %i\n", pic->index);
+ retval = -EAGAIN;
+ }
+
+ /* Reset the HWA generial register too */
+ writel(0x00, pic->hwa_addr + NS92XX_FIM_HWA_GEN_CONF_REG);
+
+ return retval;
+}
+
+/*
+ * This function stops the FIM and check if the DMA-channel still has a buffer
+ * for the FIM.
+ */
+int fim_send_reset(struct fim_driver *driver)
+{
+ struct pic_t *pic;
+ int retval, cnt, fifo_reset;
+ struct iohub_dma_fifo_t *fifo;
+ struct iohub_dma_desc_t *desc;
+
+ if (!(pic = get_pic_by_index(driver->picnr)))
+ return -EINVAL;
+
+ retval = pic_stop_and_reset(pic);
+ if (retval)
+ return retval;
+
+ /*
+ * For avoiding errors by the restart of the FIM, check if a DMA buffer
+ * is waiting for being transmitted. In that case, reset the buffer and
+ * reset the index of the DMA-controller.
+ */
+ fifo = &pic->tx_fifo;
+ fifo_reset = 0;
+ for (desc = fifo->first, cnt = 0; cnt < fifo->length; cnt++) {
+
+ /*
+ * If we are stopping the FIM but have some DMA-buffers pending, then
+ * we need to reset the DMA-channel before the next restart.
+ */
+ if (!fifo_reset && (desc->control & IOHUB_DMA_DESC_CTRL_FULL))
+ fifo_reset = 1;
+
+ desc->control = 0;
+ desc = fim_dma_get_next(fifo, desc);
+ }
+
+ if (fifo_reset) {
+ unsigned long txctrl;
+
+ printk_warn("Stopping FIM %i with a full DMA buffer!\n",
+ pic->index);
+ fim_dma_reset_fifo(fifo);
+ txctrl = IOHUB_TX_DMA_CTRL_INDEXEN | IOHUB_TX_DMA_CTRL_INDEX(0);
+ writel(txctrl, pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG);
+ }
+
+ /*
+ * Reset the RX-channel too.
+ * IMPORTANT: Set the wrap control bit by the last DMA-buffer, otherwise
+ * the FIFO will overflow!
+ */
+ fifo = &pic->rx_fifo;
+ for (desc = fifo->first, cnt = 0; cnt < fifo->length; cnt++) {
+ desc->control = IOHUB_DMA_DESC_CTRL_INT;
+ desc = fim_dma_get_next(fifo, desc);
+ }
+ fifo->last->control |= IOHUB_DMA_DESC_CTRL_WRAP;
+ fim_dma_reset_fifo(fifo);
+
+ /* Free the internal resources */
+ atomic_set(&pic->tx_tasked, 0);
+ atomic_set(&pic->tx_aborted, 0);
+
+ return retval;
+}
+
+
+int fim_send_start(struct fim_driver *driver)
+{
+ ulong reg;
+
+ struct pic_t *pic;
+ if (!(pic = get_pic_by_index(driver->picnr)))
+ return -EINVAL;
+
+ /* Connect the clock to the FIM */
+ reg = readl(SYS_CLOCK);
+ writel(reg | (1 << (pic->index + FIM0_SHIFT)), SYS_CLOCK);
+
+ reg = readl(SYS_RESET);
+ writel(reg | (1 << (pic->index + FIM0_SHIFT)), SYS_RESET);
+
+ return pic_start_at_zero(pic);
+}
+
+int fim_send_stop(struct fim_driver *driver)
+{
+ struct pic_t *pic;
+ int ret;
+ ulong reg;
+
+ if (!(pic = get_pic_by_index(driver->picnr)))
+ return -EINVAL;
+
+ reg = readl(pic->reg_addr + NS92XX_FIM_GEN_CTRL_REG);
+
+ /* Clear the interrupt flags too! */
+ reg &= ~NS92XX_FIM_INT_MASK(0xff);
+
+ writel(reg & ~NS92XX_FIM_GEN_CTRL_START_PIC,
+ pic->reg_addr + NS92XX_FIM_GEN_CTRL_REG);
+
+ ret = 0;
+ if (pic_is_running(pic)) {
+ printk_err("Unabled to stop the PIC %i\n", pic->index);
+ ret = -EAGAIN;
+ }
+
+ /* Disable the clock and reset the module */
+ reg = readl(SYS_CLOCK);
+ writel(reg & ~(1 << (pic->index + FIM0_SHIFT)), SYS_CLOCK);
+ reg = readl(SYS_RESET);
+ writel(reg & ~(1 << (pic->index + FIM0_SHIFT)), SYS_RESET);
+
+ return ret;
+}
+
+int fim_send_interrupt2(struct fim_driver *driver, unsigned int code)
+{
+ struct pic_t *pic;
+ if (!driver || !(pic = get_pic_by_index(driver->picnr)))
+ return -EINVAL;
+
+ return pic_send_interrupt(pic, code);
+}
+
+
+/*
+ * This function provides the access to the control registers of the PICs
+ * reg : Number of the control register (from 0 to 15)
+ * val : Value to write into the control register
+ */
+void fim_set_ctrl_reg(struct fim_driver *driver, int reg, unsigned int val)
+{
+ struct pic_t *pic;
+
+ if (!(pic = get_pic_from_driver(driver)))
+ return;
+
+ writel(val, pic->reg_addr + NS92XX_FIM_CTRL_REG(reg));
+}
+
+
+/* Provides the read access to the control registers of the PICs */
+int fim_get_ctrl_reg(struct fim_driver *driver, int reg, unsigned int *val)
+{
+ struct pic_t *pic;
+
+ if (!(pic = get_pic_from_driver(driver)) || !val)
+ return -EINVAL;
+
+ if (NS92XX_FIM_CTRL_REG_CHECK(reg))
+ return -EINVAL;
+
+ *val = readl(pic->reg_addr + NS92XX_FIM_CTRL_REG(reg));
+ return 0;
+}
+
+
+
+int fim_get_stat_reg(struct fim_driver *driver, int reg, unsigned int *val)
+{
+ struct pic_t *pic;
+
+ if (!(pic = get_pic_from_driver(driver)) || !val)
+ return -EINVAL;
+
+ if (NS92XX_FIM_STAT_REG_CHECK(reg))
+ return -EINVAL;
+
+ *val = readl(pic->reg_addr + NS92XX_FIM_STAT_REG(reg));
+ return 0;
+}
+
+
+
+/* Interrupt the PIC sending the interrput with the number `code' */
+static int pic_send_interrupt(struct pic_t *pic, u32 code)
+{
+ unsigned int stopcnt;
+ u32 status;
+
+ if (!pic || !code || (code & ~0x7f))
+ return -EINVAL;
+
+ if (!pic_is_running(pic)) {
+ printk_err("The PIC %i isn't running. Aborting the IRQ request.\n",
+ pic->index);
+ return -EAGAIN;
+ }
+
+ code = NS92XX_FIM_INT_MASK(code);
+ status = readl(pic->reg_addr + NS92XX_FIM_GEN_CTRL_REG);
+ writel(status | code, pic->reg_addr + NS92XX_FIM_GEN_CTRL_REG);
+
+ /* This loop is perhaps problematic, exit with a timeout */
+ stopcnt = 0xFFFF;
+ do {
+ status = readl(pic->reg_addr + NS92XX_FIM_GEN_CTRL_REG);
+ stopcnt--;
+ } while (!(status & NS92XX_FIM_GEN_CTRL_INTACKRD) && stopcnt);
+
+ if (!stopcnt) {
+ printk_err("Timeout waiting for RDACK from the PIC %i\n", pic->index);
+ return -EINVAL;
+ }
+
+ /* Reset the interrupt bits for the PIC acknowledge */
+ status &= ~NS92XX_FIM_GEN_CTRL_INTTOPIC;
+ writel(status, pic->reg_addr + NS92XX_FIM_GEN_CTRL_REG);
+
+ stopcnt = 0xFFFF;
+ do {
+ status = readl(pic->reg_addr + NS92XX_FIM_GEN_CTRL_REG);
+ stopcnt--;
+ } while ((status & NS92XX_FIM_GEN_CTRL_INTACKRD) && stopcnt);
+
+ if (!stopcnt) {
+ printk_err("Timeout by the second RDACK from the PIC %i\n", pic->index);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+/* Set the HWA PIC clock (see PIC module specification, page 19) */
+static int pic_config_output_clock_divisor(struct pic_t *pic,
+ struct fim_program_t *program)
+{
+ int div;
+ int clkd;
+ unsigned int val;
+
+ if (!pic || !program) return -EINVAL;
+
+ div = program->clkdiv;
+ switch (div) {
+ case FIM_CLK_DIV_2:
+ clkd = FIM_HWA_GEN_CFG_CLKSEL_DIVIDE_BY_2;
+ break;
+
+ case FIM_CLK_DIV_4:
+ clkd = FIM_HWA_GEN_CFG_CLKSEL_DIVIDE_BY_4;
+ break;
+
+ case FIM_CLK_DIV_8:
+ clkd = FIM_HWA_GEN_CFG_CLKSEL_DIVIDE_BY_8;
+ break;
+
+ case FIM_CLK_DIV_16:
+ clkd = FIM_HWA_GEN_CFG_CLKSEL_DIVIDE_BY_16;
+ break;
+
+ case FIM_CLK_DIV_32:
+ clkd = FIM_HWA_GEN_CFG_CLKSEL_DIVIDE_BY_32;
+ break;
+
+ case FIM_CLK_DIV_64:
+ clkd = FIM_HWA_GEN_CFG_CLKSEL_DIVIDE_BY_64;
+ break;
+
+ case FIM_CLK_DIV_128:
+ clkd = FIM_HWA_GEN_CFG_CLKSEL_DIVIDE_BY_128;
+ break;
+
+ case FIM_CLK_DIV_256:
+ clkd = FIM_HWA_GEN_CFG_CLKSEL_DIVIDE_BY_256;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ val = readl(pic->hwa_addr + NS92XX_FIM_HWA_GEN_CONF_REG);
+ writel(val | clkd, pic->hwa_addr + NS92XX_FIM_HWA_GEN_CONF_REG);
+ return 0;
+}
+
+
+static int fim_probe(struct device *dev)
+{
+ int i, cnt, retval;
+ struct pic_t *pic;
+
+ if (!dev)
+ return -EINVAL;
+
+ printk_debug("Starting the FIM-probe of the device `%s'\n", dev->bus_id);
+
+ /* Use the get_pic_by_index function for verifying that we have a FIM attached */
+ pic = (struct pic_t *)dev->driver_data;
+ pic = get_pic_by_index(pic->index);
+ if (!pic) {
+ printk_err("Invalid PIC index %i\n", pic->index);
+ return -ENODEV;
+ }
+
+ i = pic->index;
+ pic->dev = dev;
+ pic->reg_addr = ioremap(NS92XX_FIM_REG_BASE_PA + i*NS92XX_FIM_REG_OFFSET,
+ NS92XX_FIM_REG_SIZE);
+ pic->instr_addr = ioremap(NS92XX_FIM_INSTR_BASE_PA + i*NS92XX_FIM_INSTR_OFFSET,
+ NS92XX_FIM_INSTR_SIZE);
+ pic->hwa_addr = ioremap(NS92XX_FIM_HWA_BASE_PA + i*NS92XX_FIM_HWA_OFFSET,
+ NS92XX_FIM_HWA_SIZE);
+ pic->iohub_addr = ioremap(NS92XX_FIM_IOHUB_BASE_PA + i*NS92XX_FIM_IOHUB_OFFSET,
+ NS92XX_FIM_IOHUB_SIZE);
+
+ /* @FIXME: Unmap the already registered memory addresses */
+ if (!pic->reg_addr || !pic->instr_addr || !pic->hwa_addr || !pic->iohub_addr) {
+ printk_err("ioremap() failed by the PIC %i\n", pic->index);
+ retval = -EAGAIN;
+ goto exit_unmap;
+ }
+
+ spin_lock_init(&pic->lock);
+
+ /* Create the file attributes under the sysfs */
+ retval = 0;
+ for (cnt=0; cnt < ARRAY_SIZE(fim_sysfs_attrs); cnt++) {
+ retval = sysfs_create_bin_file(&dev->kobj, &fim_sysfs_attrs[cnt]);
+ if (retval) {
+ while (cnt)
+ sysfs_remove_bin_file(&dev->kobj,
+ &fim_sysfs_attrs[--cnt]);
+ goto exit_unmap;
+ }
+ }
+
+ /* Set the function pointers for providing low level access to the PICs */
+ pic->is_running = pic_is_running;
+ pic->start_at_zero = pic_start_at_zero;
+ pic->stop_and_reset = pic_stop_and_reset;
+ pic->download_firmware = pic_download_firmware;
+ pic->get_ctrl_reg = pic_get_ctrl_reg;
+ pic->set_ctrl_reg = pic_set_ctrl_reg;
+ pic->send_interrupt = pic_send_interrupt;
+ pic->ack_interrupt = isr_from_pic;
+ return 0;
+
+ exit_unmap:
+ if (pic->reg_addr) iounmap(pic->reg_addr);
+ if (pic->instr_addr) iounmap(pic->instr_addr);
+ if (pic->hwa_addr) iounmap(pic->hwa_addr);
+ if (pic->iohub_addr) iounmap(pic->iohub_addr);
+ pic->reg_addr = pic->instr_addr = pic->hwa_addr = pic->iohub_addr = NULL;
+
+
+ return retval;
+}
+
+
+/* This function will be called for each PIC-device (but not for the parent device) */
+static int fim_remove(struct device *dev)
+{
+ int retval, cnt;
+ struct pic_t *pic = (struct pic_t *)dev->driver_data;
+ if (!pic)
+ return -EINVAL;
+
+ printk_info("Starting to remove the PIC %s\n", dev->bus_id);
+
+ if (pic->driver) {
+ retval = fim_unregister_driver(pic->driver);
+ if (retval) {
+ printk_err("Can unregister the PIC %i, %i\n", pic->index,
+ retval);
+ }
+ pic->driver = NULL;
+ }
+
+ /* Remove the created sysfs attributes */
+ for (cnt=0; cnt < ARRAY_SIZE(fim_sysfs_attrs); cnt++)
+ sysfs_remove_bin_file(&dev->kobj, &fim_sysfs_attrs[cnt]);
+
+ /* Free the mapped pages of the IO memory */
+ if (pic->reg_addr) iounmap(pic->reg_addr);
+ if (pic->instr_addr) iounmap(pic->instr_addr);
+ if (pic->hwa_addr) iounmap(pic->hwa_addr);
+ if (pic->iohub_addr) iounmap(pic->iohub_addr);
+ pic->dev = NULL;
+ return 0;
+}
+
+
+/* This function returns a non-zero value if the device correspond to our fim-bus */
+static int fim_bus_match(struct device *dev, struct device_driver *driver)
+{
+ printk_debug("@TODO: Create a match mechanism for the FIMs.\n");
+ return 1;
+}
+
+
+struct bus_type fim_bus_type = {
+ .name = FIM_BUS_TYPE_NAME,
+ .match = fim_bus_match,
+};
+
+
+/*
+ * Bus parent that will be registered without calling the probe function
+ * the sys file is under: /sys/devices/fims/
+ */
+struct device fim_bus_dev = {
+ .bus_id = "fims",
+ .release = fim_pic_release
+};
+
+
+static struct device_driver fims_driver = {
+ .probe = fim_probe,
+ .remove = fim_remove,
+ .name = FIM_DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .bus = &fim_bus_type,
+};
+
+
+
+/*
+ * These are the two PIC devices that we currently have in the NS9215
+ * With the help of the `bus type' the probe function will be called when
+ * the devices are registered
+ * Sys path: /sys/bus/fim-bus/devices/fim[01..]
+ */
+static struct device fim_pics[] = {
+ {
+ .bus_id = "fim0",
+ .release = fim_pic_release,
+ .bus = &fim_bus_type,
+ .parent = &fim_bus_dev,
+ },
+ {
+ .bus_id = "fim1",
+ .release = fim_pic_release,
+ .bus = &fim_bus_type,
+ .parent = &fim_bus_dev,
+ }
+};
+
+
+/*
+ * If no configuration for the DMA-buffer descriptors is passed (cfg = NULL),
+ * then use the default values. These are defined under the header file 'fim.h'
+ */
+inline static int pic_dma_check_config(struct pic_t *pic, struct fim_dma_cfg_t *cfg)
+{
+ if (cfg) {
+ if (cfg->rxnr <= 0 || cfg->rxnr > IOHUB_MAX_DMA_BUFFERS ||
+ cfg->txnr <= 0 || cfg->txnr > IOHUB_MAX_DMA_BUFFERS) {
+ printk_err("Invalid number of RX/TX-DMA buffers (%i | %i)\n",
+ cfg->rxnr, cfg->txnr);
+ return -EINVAL;
+ }
+
+ if (cfg->rxsz > IOHUB_MAX_DMA_LENGTH || cfg->rxsz <= 0 ||
+ cfg->txsz > IOHUB_MAX_DMA_LENGTH || cfg->txsz <= 0) {
+ printk_err("Invalid DMA-buffer length (%i | %i)\n",
+ cfg->rxsz, cfg->txsz);
+ return -EINVAL;
+ }
+ }
+
+ pic->dma_cfg.rxnr = (!cfg) ? PIC_DMA_RX_BUFFERS : cfg->rxnr;
+ pic->dma_cfg.txnr = (!cfg) ? PIC_DMA_TX_BUFFERS : cfg->txnr;
+ pic->dma_cfg.rxsz = (!cfg) ? PIC_DMA_BUFFER_SIZE : cfg->rxsz;
+ pic->dma_cfg.txsz = (!cfg) ? PIC_DMA_BUFFER_SIZE : cfg->txsz;
+ return 0;
+}
+
+
+/*
+ * This function starts the DMA-buffers and -fifos for the passed PIC
+ * rxs : Number of RX-buffer descriptors to use
+ * txs : Number of TX-buffer descriptors to create
+ * rxlen : Length of each RX-DMA buffer
+ * txlen : Length of each TX-DMA buffer
+ * @IMPORTANT: No sanity check will be executed in this function
+ */
+static int pic_dma_init(struct pic_t *pic, struct fim_dma_cfg_t *cfg)
+{
+ int retval;
+ struct iohub_dma_desc_t *desc;
+ struct pic_dma_desc_t *pic_desc;
+ int cnt;
+ dma_addr_t phys_buffers;
+
+ if (!pic || !pic->dev)
+ return -EINVAL;
+
+ /* Sanity checks for the passed DMA-descriptors configuration */
+ if ((retval = pic_dma_check_config(pic, cfg)))
+ return retval;
+
+ /* Get the DMA-memory for the RX- and TX-buffers */
+ cfg = &pic->dma_cfg;
+ pic->dma_size = (cfg->rxnr * cfg->rxsz) + (cfg->txnr * cfg->txsz);
+ pic->dma_virt = dma_alloc_coherent(NULL, pic->dma_size,
+ &pic->dma_phys, GFP_KERNEL);
+ if (!pic->dma_virt)
+ return -ENOMEM;
+
+ /*
+ * Get the DMA-memory for the RX-descriptors (FIFOs)
+ */
+ pic->rx_fifo.descs = dma_alloc_coherent(NULL,
+ cfg->rxnr * IOHUB_DMA_DESC_LENGTH,
+ &pic->rx_fifo.phys_descs,
+ GFP_KERNEL);
+ if (!pic->rx_fifo.descs) {
+ retval = -ENOMEM;
+ goto free_dma_bufs;
+ }
+
+ fim_dma_init_fifo(&pic->rx_fifo, cfg->rxnr, pic->rx_fifo.descs);
+ printk_debug("RX FIFO descriptors range is 0x%p to 0x%p [0x%08x]\n",
+ pic->rx_fifo.first, pic->rx_fifo.last, pic->rx_fifo.phys_descs);
+
+ /* Setup the RX-descriptors with the corresponding DMA-buffers */
+ phys_buffers = pic->dma_phys;
+ desc = pic->rx_fifo.first;
+ do {
+ desc->length = cfg->rxsz;
+ desc->src = phys_buffers;
+ desc->control = IOHUB_DMA_DESC_CTRL_INT;
+ printk_debug("[ RX %p ] 0x%p (0x%08x) | 0x%08x | 0x%04x\n",
+ desc, phys_to_virt(desc->src), desc->src, desc->length,
+ desc->control);
+ phys_buffers += cfg->rxsz;
+ desc = fim_dma_get_next(&pic->rx_fifo, desc);
+ } while (desc != pic->rx_fifo.first);
+ pic->rx_fifo.last->control |= IOHUB_DMA_DESC_CTRL_WRAP;
+
+ /*
+ * Now create the DMA-memory for the TX-buffer descriptors
+ */
+ pic->tx_fifo.descs = dma_alloc_coherent(NULL,
+ cfg->txnr * IOHUB_DMA_DESC_LENGTH,
+ &pic->tx_fifo.phys_descs,
+ GFP_KERNEL);
+ if (!pic->tx_fifo.descs) {
+ retval = -ENOMEM;
+ goto free_dma_rxfifo;
+ }
+
+ fim_dma_init_fifo(&pic->tx_fifo, cfg->txnr, pic->tx_fifo.descs);
+ printk_debug("TX FIFO descriptors range is 0x%p to 0x%p [0x%08x]\n",
+ pic->tx_fifo.first, pic->tx_fifo.last, pic->tx_fifo.phys_descs);
+ if(!(pic->tx_desc = kzalloc(cfg->txsz *
+ sizeof(struct pic_dma_desc_t), GFP_KERNEL))) {
+ printk_err("Couldn't create the TX-dma descriptors\n");
+ retval = -ENOMEM;
+ goto free_dma_rxfifo;
+ }
+
+ /* Setup the TX-buffers and -descriptors */
+ desc = pic->tx_fifo.first;
+ cnt = 0;
+ do {
+ desc->length = cfg->txsz;
+ desc->src = phys_buffers;
+ desc->control = 0;
+ pic_desc = pic->tx_desc + cnt++;
+ pic_desc->src = phys_buffers;
+ pic_desc->length = cfg->txsz;
+ printk_debug("[ TX %p ] 0x%p (0x%08x) | 0x%08x | 0x%04x\n",
+ desc, phys_to_virt(desc->src), desc->src, desc->length,
+ desc->control);
+
+ phys_buffers += cfg->txsz;
+ desc = fim_dma_get_next(&pic->tx_fifo, desc);
+ } while (desc != pic->tx_fifo.first);
+
+ /* Set the last buffer descriptor with the WRAP bit */
+ pic->tx_fifo.last->control = IOHUB_DMA_DESC_CTRL_WRAP;
+
+ /*
+ * Enable the interrupts for the TX-DMA channel and set the buf desc pointer
+ * TXCAIP : used for flushing the TX-buffers
+ */
+ spin_lock_init(&pic->tx_lock);
+ writel(0x00, pic->iohub_addr + IOHUB_TX_ICTRL_REG);
+ writel(IOHUB_TX_DMA_CTRL_CA, pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG);
+ writel(0x00, pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG);
+ writel(IOHUB_ICTRL_TXNCIE | IOHUB_ICTRL_TXNRIE | IOHUB_ICTRL_TXECIE |
+ IOHUB_IFS_TXCAIP, pic->iohub_addr + IOHUB_TX_ICTRL_REG);
+ writel(pic->tx_fifo.phys_descs, pic->iohub_addr + IOHUB_TX_DMA_BUFPTR_REG);
+
+ /* Enable the interrupts for the RX-DMA channel */
+ spin_lock_init(&pic->rx_lock);
+ writel(0x00, pic->iohub_addr + IOHUB_RX_DMA_ICTRL_REG);
+ writel(IOHUB_RX_DMA_CTRL_CA, pic->iohub_addr + IOHUB_RX_DMA_CTRL_REG);
+ writel(IOHUB_ICTRL_RXNCIE | IOHUB_ICTRL_RXNRIE | IOHUB_ICTRL_RXPCIE,
+ pic->iohub_addr + IOHUB_RX_DMA_ICTRL_REG);
+ writel(pic->rx_fifo.phys_descs, pic->iohub_addr + IOHUB_RX_DMA_BUFPTR_REG);
+ writel(IOHUB_RX_DMA_CTRL_CE, pic->iohub_addr + IOHUB_RX_DMA_CTRL_REG);
+
+ return 0;
+
+ free_dma_rxfifo:
+ dma_free_coherent(NULL, pic->rx_fifo.length, pic->rx_fifo.descs,
+ pic->rx_fifo.phys_descs);
+ pic->rx_fifo.descs = NULL;
+ pic->rx_fifo.phys_descs = 0;
+
+ free_dma_bufs:
+ dma_free_coherent(NULL, pic->dma_size, pic->dma_virt, pic->dma_phys);
+ pic->dma_virt = NULL;
+ pic->dma_phys = 0;
+
+ return retval;
+}
+
+
+static void pic_dma_stop(struct pic_t *pic)
+{
+ int cnt;
+ struct pic_dma_desc_t *pic_desc;
+
+ if (!pic || !pic->dev)
+ return;
+
+ printk_debug("Freeing the DMA resources of the PIC %i\n", pic->index);
+
+ /* Disable all the interrupts and abort all the DMA-transfers */
+ writel(0x00, pic->iohub_addr + IOHUB_TX_ICTRL_REG);
+ writel(IOHUB_TX_DMA_CTRL_CA, pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG);
+ writel(0x00, pic->iohub_addr + IOHUB_TX_DMA_CTRL_REG);
+
+ writel(0x00, pic->iohub_addr + IOHUB_RX_DMA_ICTRL_REG);
+ writel(IOHUB_RX_DMA_CTRL_CA, pic->iohub_addr + IOHUB_RX_DMA_CTRL_REG);
+ writel(0x00, pic->iohub_addr + IOHUB_RX_DMA_CTRL_REG);
+
+ /* Reset the DMA-channel data */
+ atomic_set(&pic->tx_tasked, 0);
+ for (cnt=0; cnt < pic->tx_fifo.length; cnt++) {
+ pic_desc = pic->tx_desc + cnt;
+ atomic_set(&pic_desc->tasked, 0);
+ }
+
+ /* Free the internal TX-descriptors */
+ if (pic->tx_desc) {
+ kfree(pic->tx_desc);
+ pic->tx_desc = NULL;
+ }
+
+ /* Free the FIFOs */
+ if (pic->tx_fifo.descs && pic->tx_fifo.phys_descs) {
+ dma_free_coherent(NULL, pic->tx_fifo.length, pic->tx_fifo.descs,
+ pic->tx_fifo.phys_descs);
+ pic->tx_fifo.descs = NULL;
+ pic->tx_fifo.phys_descs = 0;
+ }
+
+ if (pic->rx_fifo.descs && pic->rx_fifo.phys_descs) {
+ dma_free_coherent(NULL, pic->rx_fifo.length, pic->rx_fifo.descs,
+ pic->rx_fifo.phys_descs);
+ pic->rx_fifo.descs = NULL;
+ pic->rx_fifo.phys_descs = 0;
+ }
+
+ /* Free the DMA-buffers */
+ if (pic->dma_virt && pic->dma_phys) {
+ dma_free_coherent(NULL, pic->dma_size, pic->dma_virt, pic->dma_phys);
+ pic->dma_virt = NULL;
+ pic->dma_phys = 0;
+ }
+}
+
+int fim_dma_stop(struct fim_driver *fim)
+{
+ struct pic_t *pic;
+
+ if (!(pic = get_pic_from_driver(fim)))
+ return -ENODEV;
+
+ pic_dma_stop(pic);
+ return 0;
+}
+
+int fim_dma_start(struct fim_driver *fim, struct fim_dma_cfg_t *cfg)
+{
+ struct pic_t *pic;
+
+ if (!(pic = get_pic_from_driver(fim)))
+ return -ENODEV;
+
+ /* Per default Use the already configured DMA configuration */
+ if (!cfg)
+ cfg = &pic->dma_cfg;
+
+ return pic_dma_init(pic, cfg);
+}
+
+/*
+ * @TODO: We wanted originally to separate the PICs and the virtual devices
+ * initialization, but now we have a part of the PICs-init in this init-function
+ */
+static int __devinit fim_init_module(void)
+{
+ int ret, i;
+ struct pic_t *pic;
+
+ printk_info("Starting to register the FIMs module.\n");
+
+ if (the_fims)
+ return -EINVAL;
+ if (!(the_fims = kzalloc(sizeof(struct fims_t), GFP_KERNEL))) {
+ printk_err("kmalloc() failed.\n");
+ return -ENOMEM;
+ }
+
+ /* Register the internal bus type */
+ ret = bus_register(&fim_bus_type);
+ if (ret) {
+ printk_err("Registering the PICs bus, %i\n", ret);
+ goto goto_free_mem;
+ }
+
+ /* Register the bus parent */
+ ret = device_register(&fim_bus_dev);
+ if (ret) {
+ printk_err( "Couldn't register the parent PIC device, %i\n", ret);
+ goto goto_driver_unreg;
+ }
+
+ ret = driver_register(&fims_driver);
+ if (ret) {
+ printk_err("Couldn't register the FIMs driver, %i\n", ret);
+ goto goto_bus_unreg;
+ }
+
+ /* Start registering the PIC devices */
+ for (i=0; i <= FIM_MAX_PIC_INDEX; i++) {
+ pic = &the_fims->pics[i];
+ pic->index = i;
+ pic->irq = IRQ_NS921X_PIC0 + i;
+ fim_pics[i].driver_data = pic;
+ ret = device_register(&fim_pics[i]);
+ if (ret) {
+ printk_err("Registering the PIC device %i, %i\n", i, ret);
+ while(i) device_unregister(&fim_pics[--i]);
+ goto goto_parent_unreg;
+ }
+ }
+
+ the_fims->driver = &fims_driver;
+ the_fims->bus_dev = &fim_bus_dev;
+ the_fims->bus_type = &fim_bus_type;
+ printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION "\n");
+ return 0;
+
+ goto_parent_unreg:
+ device_unregister(&fim_bus_dev);
+
+ goto_driver_unreg:
+ driver_unregister(&fims_driver);
+
+ goto_bus_unreg:
+ bus_unregister(&fim_bus_type);
+
+ goto_free_mem:
+ if(the_fims) kfree(the_fims);
+
+ return ret;
+}
+
+
+static void __devexit fim_exit_module(void)
+{
+ int i;
+
+ printk_info("Unregistering the FIMs module.\n");
+ for (i=0; i <= FIM_MAX_PIC_INDEX; i++) {
+ device_unregister(&fim_pics[i]);
+ }
+
+ device_unregister(&fim_bus_dev);
+ driver_unregister(&fims_driver);
+
+ bus_unregister(the_fims->bus_type);
+ kfree(the_fims);
+ the_fims = NULL;
+}
+
+
+module_init(fim_init_module);
+module_exit(fim_exit_module);
+
+
+EXPORT_SYMBOL(fim_register_driver);
+EXPORT_SYMBOL(fim_unregister_driver);
+EXPORT_SYMBOL(fim_get_exp_reg);
+EXPORT_SYMBOL(fim_send_interrupt2);
+EXPORT_SYMBOL(fim_enable_irq);
+EXPORT_SYMBOL(fim_disable_irq);
+EXPORT_SYMBOL(fim_send_buffer);
+EXPORT_SYMBOL(fim_tx_buffers_room);
+EXPORT_SYMBOL(fim_tx_buffers_level);
+EXPORT_SYMBOL(fim_number_pics);
+EXPORT_SYMBOL(fim_send_reset);
+EXPORT_SYMBOL(fim_send_start);
+EXPORT_SYMBOL(fim_send_stop);
+EXPORT_SYMBOL(fim_flush_rx);
+EXPORT_SYMBOL(fim_flush_tx);
+EXPORT_SYMBOL(fim_alloc_buffer);
+EXPORT_SYMBOL(fim_free_buffer);
+EXPORT_SYMBOL(fim_set_ctrl_reg);
+EXPORT_SYMBOL(fim_get_ctrl_reg);
+EXPORT_SYMBOL(fim_get_stat_reg);
+EXPORT_SYMBOL(fim_request_pic);
+EXPORT_SYMBOL(fim_free_pic);
+EXPORT_SYMBOL(fim_download_firmware);
+EXPORT_SYMBOL(fim_is_running);
+EXPORT_SYMBOL(fim_dma_stop);
+EXPORT_SYMBOL(fim_dma_start);