The Flexcard interface design split packet receive and transmit. All
received packets and card status information are multiplexed with a
Flexcard specific protocol and handled through a DMA capable ringbuffer.
The TX path has to poke each available component separate.

Add a Flexcard DMA ringbuffer driver and packet demultiplexer.

Signed-off-by: Benedikt Spranger <b.spran...@linutronix.de>
Signed-off-by: Holger Dengler <deng...@linutronix.de>
cc: Vinod Koul <vinod.k...@intel.com>
---
 drivers/dma/Kconfig                 |   9 ++
 drivers/dma/Makefile                |   1 +
 drivers/dma/flexcard/Makefile       |   2 +
 drivers/dma/flexcard/core.c         | 292 ++++++++++++++++++++++++++++++++++++
 drivers/dma/flexcard/flexcard-dma.h | 218 +++++++++++++++++++++++++++
 drivers/dma/flexcard/parser.c       | 227 ++++++++++++++++++++++++++++
 drivers/mfd/Kconfig                 |   1 +
 include/linux/mfd/flexcard.h        |   4 +
 8 files changed, 754 insertions(+)
 create mode 100644 drivers/dma/flexcard/Makefile
 create mode 100644 drivers/dma/flexcard/core.c
 create mode 100644 drivers/dma/flexcard/flexcard-dma.h
 create mode 100644 drivers/dma/flexcard/parser.c

diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index 141aefb..b158544 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -40,6 +40,15 @@ config ASYNC_TX_ENABLE_CHANNEL_SWITCH
 config ARCH_HAS_ASYNC_TX_FIND_CHANNEL
        bool
 
+config FLEXCARD_DMA
+       tristate "DMA support for Eberspaecher Flexcard PMC II Carrier Board"
+       depends on MFD_FLEXCARD
+       help
+         The Eberspaecher Flexcard PMC (PCI Mezzanine Card) II carrier
+         board support one DMA capable receive ringbuffer for all devices.
+         A card specific protocol is used to multiplex the received packets
+         through the ringbuffer. Enable DMA and Packet parser.
+
 config DMA_ENGINE
        bool
 
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index e4dc9ca..a9a5b3f 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_DMA_SUN4I) += sun4i-dma.o
 obj-$(CONFIG_DMA_SUN6I) += sun6i-dma.o
 obj-$(CONFIG_DW_DMAC_CORE) += dw/
 obj-$(CONFIG_EP93XX_DMA) += ep93xx_dma.o
+obj-$(CONFIG_FLEXCARD_DMA) += flexcard/
 obj-$(CONFIG_FSL_DMA) += fsldma.o
 obj-$(CONFIG_FSL_EDMA) += fsl-edma.o
 obj-$(CONFIG_FSL_RAID) += fsl_raid.o
diff --git a/drivers/dma/flexcard/Makefile b/drivers/dma/flexcard/Makefile
new file mode 100644
index 0000000..62ae627
--- /dev/null
+++ b/drivers/dma/flexcard/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_FLEXCARD_DMA) += flexcard_dma.o
+flexcard_dma-objs := core.o parser.o
diff --git a/drivers/dma/flexcard/core.c b/drivers/dma/flexcard/core.c
new file mode 100644
index 0000000..6809840
--- /dev/null
+++ b/drivers/dma/flexcard/core.c
@@ -0,0 +1,292 @@
+/*
+ * Eberspächer Flexcard PMC II Carrier Board PCI Driver - DMA controller
+ *
+ * Copyright (c) 2014 - 2016, Linutronix GmbH
+ * Author: Benedikt Spranger <b.spran...@linutronix.de>
+ *         Holger Dengler <deng...@linutronix.de>
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/flexcard.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/flexcard.h>
+
+#include "flexcard-dma.h"
+
+/*
+ * Allocate twice the size of FLEXCARD_DMA_BUF_SIZE for the receiving
+ * ring buffer to easily handle wrap-arounds.
+ */
+#define DMA_TOTAL_BUF_SIZE             (2*FLEXCARD_DMA_BUF_SIZE)
+
+static int flexcard_dma_stop(struct flexcard_dma *dma)
+{
+       u32 __iomem *dma_ctrl = &dma->reg->dma_ctrl;
+       u32 __iomem *dma_stat = &dma->reg->dma_stat;
+       int retry;
+
+       writel(FLEXCARD_DMA_CTRL_STOP_REQ, dma_ctrl);
+
+       /*
+        * DMA_IDLE bit reads 1 when the DMA state machine is in idle state
+        * after a stop request, otherwise 0. DMA stop should complete in at
+        * least 200us.
+        */
+       retry = 200;
+       while (!(readl(dma_ctrl) & FLEXCARD_DMA_CTRL_DMA_IDLE) && retry--)
+               udelay(1);
+       if (!retry)
+               return -EBUSY;
+
+       /*
+        * Check for max. 200us, if there are DMA jobs in progress.
+        */
+       retry = 200;
+       while ((readl(dma_stat) & FLEXCARD_DMA_STAT_BUSY) && retry--)
+               udelay(1);
+
+       return retry ? 0 : -EBUSY;
+}
+
+static int flexcard_dma_reset(struct flexcard_dma *dma)
+{
+       u32 __iomem *dma_ctrl = &dma->reg->dma_ctrl;
+       int retry = 500;
+
+       writel(FLEXCARD_DMA_CTRL_RST_DMA, dma_ctrl);
+
+       /*
+        * DMA_IDLE bit reads 1 when the DMA state machine is in idle state
+        * after a reset request, otherwise 0. DMA reset should complete in
+        * at least 5ms.
+        */
+       while (!(readl(dma_ctrl) & FLEXCARD_DMA_CTRL_DMA_IDLE) && retry--)
+               udelay(10);
+
+       return retry ? 0 : -EIO;
+}
+
+static int flexcard_dma_setup(struct flexcard_dma *dma)
+{
+       int ret;
+
+       ret = flexcard_dma_reset(dma);
+       if (ret)
+               return ret;
+
+       writel(0x0, &dma->reg->dma_rptr);
+       writel(0x0, &dma->reg->dma_wptr);
+       writel(0x0, &dma->reg->dma_ctrl);
+
+       writeq(dma->phys, &dma->reg->dma_cba);
+       writel(FLEXCARD_DMA_BUF_SIZE, &dma->reg->dma_cbs);
+
+       return ret;
+}
+
+static irqreturn_t flexcard_dma_isr(int irq, void *dev_id)
+{
+       struct platform_device *pdev = dev_id;
+       struct flexcard_dma *dma = platform_get_drvdata(pdev);
+       u32 avail, parsed, rptr = dma->rptr;
+
+       avail = readl(&dma->reg->dma_cblr);
+       if (!avail)
+               return IRQ_NONE;
+
+       do {
+               u32 tocp = rptr + FLEXCARD_MAX_PAKET_SIZE;
+               /*
+                * For simplicity the parser always looks at contiguous
+                * buffer space.
+                *
+                * We ensure that by copying the eventually wrapped
+                * bytes of the next message from the bottom of the
+                * dma buffer to the space right after the dma buffer
+                * which has been allocated just for that reason.
+                */
+               if (tocp > FLEXCARD_DMA_BUF_SIZE) {
+                       tocp &= FLEXCARD_DMA_BUF_MASK;
+                       memcpy(dma->buf + FLEXCARD_DMA_BUF_SIZE,
+                              dma->buf, tocp);
+               }
+
+               parsed = flexcard_parse_packet(dma->buf + rptr, avail, dma);
+               if (parsed > avail) {
+                       dev_err(&pdev->dev, "Parser overrun\n");
+                       rptr = (rptr + parsed) & FLEXCARD_DMA_BUF_MASK;
+                       break;
+               }
+               avail -= parsed;
+               rptr = (rptr + parsed) & FLEXCARD_DMA_BUF_MASK;
+       } while (parsed && avail);
+
+       /* Update the read pointer in the device if we processed data */
+       if (dma->rptr != rptr) {
+               dma->rptr = rptr;
+               writel(rptr, &dma->reg->dma_rptr);
+       } else {
+               /* This may happen if no packets has been parsed */
+               dev_err_ratelimited(&pdev->dev, "rptr unchanged\n");
+               return IRQ_NONE;
+       }
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t flexcard_dma_ovr(int irq, void *dev_id)
+{
+       struct platform_device *pdev = dev_id;
+       struct flexcard_dma *dma = platform_get_drvdata(pdev);
+       u32 stat;
+
+       /* check overflow flag */
+       stat = readl(&dma->reg->dma_stat);
+       if (!(stat & FLEXCARD_DMA_STAT_OFL))
+               return IRQ_NONE;
+
+       dev_err(&pdev->dev, "DMA buffer overflow\n");
+
+       writel(0x0, &dma->reg->dma_rptr);
+
+       /* reset overflow flag */
+       writel(FLEXCARD_DMA_STAT_OFL, &dma->reg->dma_stat);
+
+       return IRQ_HANDLED;
+}
+
+static int flexcard_dma_resource(struct platform_device *pdev)
+{
+       struct flexcard_dma *dma = platform_get_drvdata(pdev);
+       struct resource *res;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res)
+               return -ENXIO;
+
+       dma->reg = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+       if (!dma->reg) {
+               dev_err(&pdev->dev, "failed to map DMA register\n");
+               return -ENOMEM;
+       }
+
+       dma->irq = platform_get_irq(pdev, 0);
+       if (dma->irq < 0) {
+               dev_err(&pdev->dev, "failed to get CBL IRQ\n");
+               return -ENXIO;
+       }
+
+       dma->irq_ovr = platform_get_irq(pdev, 1);
+       if (dma->irq_ovr < 0) {
+               dev_err(&pdev->dev, "failed to get CO IRQ\n");
+               return -ENXIO;
+       }
+       return 0;
+}
+
+static int flexcard_dma_probe(struct platform_device *pdev)
+{
+       const struct mfd_cell *cell;
+       struct flexcard_dma *dma;
+       int ret;
+
+       cell = mfd_get_cell(pdev);
+       if (!cell)
+               return -ENODEV;
+
+       dma = devm_kzalloc(&pdev->dev, sizeof(*dma), GFP_KERNEL);
+       if (!dma)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, dma);
+
+       dma->buf = dma_alloc_coherent(&pdev->dev, DMA_TOTAL_BUF_SIZE,
+                                     &dma->phys, GFP_KERNEL);
+       if (!dma->buf) {
+               dev_err(&pdev->dev, "could not allocate DMA memory\n");
+               return -ENOMEM;
+       }
+
+       ret = flexcard_dma_resource(pdev);
+       if (ret)
+               goto out_free_buf;
+
+       ret = flexcard_dma_setup(dma);
+       if (ret) {
+               dev_err(&pdev->dev, "could not setup Flexcard DMA: %d\n", ret);
+               goto out_free_buf;
+       }
+
+       ret = devm_request_threaded_irq(&pdev->dev, dma->irq, NULL,
+                                       flexcard_dma_isr, IRQF_ONESHOT,
+                                       "flexcard-CBL", pdev);
+       if (ret) {
+               dev_err(&pdev->dev, "could not request Flexcard DMA CBL IRQ\n");
+               goto out_free_buf;
+       }
+
+       ret = devm_request_irq(&pdev->dev, dma->irq_ovr, flexcard_dma_ovr, 0,
+                         "flexcard-CO", pdev);
+       if (ret) {
+               dev_err(&pdev->dev, "could not request Flexcard DMA CO IRQ\n");
+               goto out_free_irq;
+       }
+
+       writel(FLEXCARD_DMA_CTRL_DMA_ENA, &dma->reg->dma_ctrl);
+       writel(0x300, &dma->reg->dma_cbcr);
+
+       dev_info(&pdev->dev, "Flexcard DMA registered");
+
+       return 0;
+
+out_free_irq:
+       writel(0x0, &dma->reg->dma_ctrl);
+out_free_buf:
+       dma_free_coherent(&pdev->dev, DMA_TOTAL_BUF_SIZE,
+                         dma->buf, dma->phys);
+       return ret;
+}
+
+static int flexcard_dma_remove(struct platform_device *pdev)
+{
+       struct flexcard_dma *dma = platform_get_drvdata(pdev);
+       int ret;
+
+       ret = flexcard_dma_stop(dma);
+       if (ret) {
+               dev_err(&pdev->dev, "could not stop DMA state machine\n");
+               return ret;
+       }
+
+       dma_free_coherent(&pdev->dev, DMA_TOTAL_BUF_SIZE,
+                         dma->buf, dma->phys);
+
+       return ret;
+}
+
+static struct platform_driver flexcard_dma_driver = {
+       .probe          = flexcard_dma_probe,
+       .remove         = flexcard_dma_remove,
+       .driver         = {
+               .name   = "flexcard-dma",
+       }
+};
+
+module_platform_driver(flexcard_dma_driver);
+
+MODULE_AUTHOR("Holger Dengler <deng...@linutronix.de>");
+MODULE_AUTHOR("Benedikt Spranger <b.spran...@linutronix.de>");
+MODULE_DESCRIPTION("Eberspaecher Flexcard PMC II DMA Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:flexcard-dma");
diff --git a/drivers/dma/flexcard/flexcard-dma.h 
b/drivers/dma/flexcard/flexcard-dma.h
new file mode 100644
index 0000000..6fc4ccf
--- /dev/null
+++ b/drivers/dma/flexcard/flexcard-dma.h
@@ -0,0 +1,218 @@
+/*
+ * Eberspächer Flexcard PMC II Carrier Board PCI Driver - DMA controller
+ *
+ * Copyright (c) 2014 - 2016, Linutronix GmbH
+ * Author: Benedikt Spranger <b.spran...@linutronix.de>
+ *         Holger Dengler <deng...@linutronix.de>
+ *
+ * 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.
+ */
+#ifndef __FLEXCARD_DMA_H
+#define __FLEXCARD_DMA_H
+
+#define FLEXCARD_DMA_BUF_SIZE          0x200000
+#define FLEXCARD_DMA_BUF_MASK          (FLEXCARD_DMA_BUF_SIZE - 1)
+
+#define FLEXCARD_DMA_CTRL_DMA_ENA      (1 << 0)
+#define FLEXCARD_DMA_CTRL_MAN_ENA      (1 << 1)
+#define FLEXCARD_DMA_CTRL_STOP_REQ     (1 << 16)
+#define FLEXCARD_DMA_CTRL_DMA_IDLE     (1 << 17)
+#define FLEXCARD_DMA_CTRL_RST_DMA      (1 << 31)
+
+#define FLEXCARD_DMA_STAT_BUSY         (1 << 15)
+#define FLEXCARD_DMA_STAT_OFL          (1 << 31)
+
+#define FLEXCARD_MAX_PAKET_SIZE                0x200
+
+#define FLEXCARD_BUF_HEADER_LEN_SHIFT  15
+#define FLEXCARD_BUF_HEADER_LEN_MASK   0xfe
+
+#define FLEXCARD_CANIF_OFFSET          0x20
+
+struct flexcard_dma_reg {
+       u32 dma_ctrl;
+       u32 dma_stat;
+       u32 r1[2];
+       u64 dma_cba;
+       u32 dma_cbs;
+       u32 dma_txr;
+       u32 dma_irer;
+       u32 dma_irsr;
+       u32 r2[10];
+       u32 dma_cbcr;
+       u32 dma_cblr;
+       u32 r3[2];
+       u32 dma_itcr;
+       u32 dma_itr;
+       u32 r4[2];
+       u32 dma_wptr;
+       u32 dma_rptr;
+       u32 r5[2];
+} __packed;
+
+struct flexcard_dma {
+       int irq;
+       int irq_ovr;
+       u32 rptr;
+       void *buf;
+       dma_addr_t phys;
+       int nr_eray;
+       struct flexcard_dma_reg __iomem *reg;
+};
+
+enum fc_packet_type {
+       FC_PACKET_TYPE_INFO = 1,
+       FC_PACKET_TYPE_FLEXRAY_FRAME = 2,
+       FC_PACKET_TYPE_ERROR = 3,
+       FC_PACKET_TYPE_STATUS = 4,
+       FC_PACKET_TYPE_TRIGGER = 5,
+       FC_PACKET_TYPE_TX_ACK = 6,
+       FC_PACKET_TYPE_NMV_VECTOR = 7,
+       FC_PACKET_TYPE_NOTIFICATION = 8,
+       FC_PACKET_TYPE_TRIGGER_EX = 9,
+       FC_PACKET_TYPE_CAN = 10,
+       FC_PACKET_TYPE_CAN_ERROR = 11,
+};
+
+struct fc_packet {
+       __u32 type;
+       __u32 p_packet;
+       __u32 p_next_packet;
+} __packed;
+
+struct fc_info_packet {
+       __u32 current_cycle;
+       __u32 timestamp;
+       __u32 offset_rate_correction;
+       __u32 pta_ccf_count;
+       __u32 cc;
+} __packed;
+
+struct fc_flexray_frame {
+       __u32 header;
+       __u32 header_crc;
+       __u32 pdata;
+       __u32 channel;
+       __u32 frame_crc;
+       __u32 timestamp;
+       __u32 cc;
+} __packed;
+
+struct fc_error_packet {
+       __u32 flag;
+       __u32 timestamp;
+       __u32 cycle_count;
+       __u64 additional_info;
+       __u32 cc;
+       __u32 reserved;
+} __packed;
+
+struct fc_status_packet {
+       __u32 flag;
+       __u32 timestamp;
+       __u32 cycle_count;
+       __u32 additional_info;
+       __u32 cc;
+       __u32 reserved[2];
+} __packed;
+
+struct fc_tx_ack_packet {
+       __u32 bufferid;
+       __u32 timestamp;
+       __u32 cycle_count;
+       __u32 header;
+       __u32 header_crc;
+       __u32 pdata;
+       __u32 channel;
+       __u32 cc;
+} __packed;
+
+struct fc_nm_vector_packet {
+       __u32 timestamp;
+       __u32 cycle_count;
+       __u32 nmv_vector_length;
+       __u32 nmv_vector[3];
+       __u32 cc;
+       __u32 reserved;
+} __packed;
+
+struct fc_notification_packet {
+       __u32 timestamp;
+       __u32 sequence_count;
+       __u32 reserved;
+} __packed;
+
+struct fc_trigger_ex_info_packet {
+       __u32 condition;
+       __u32 timestamp;
+       __u32 sequence_count;
+       __u32 reserved1;
+       __u64 performance_counter;
+       __u32 edge;
+       __u32 trigger_line;
+       __u32 reserved[4];
+} __packed;
+
+struct fc_can_packet {
+       __u32 id;
+       __u32 timestamp;
+       __u32 flags;
+       __u32 reserved;
+       __u32 cc;
+       __u8 data[8];
+} __packed;
+
+struct fc_can_error_packet {
+       __u32 type;
+       __u32 state;
+       __u32 timestamp;
+       __u32 rx_error_counter;
+       __u32 tx_error_counter;
+       __u32 cc;
+       __u32 reserved[2];
+} __packed;
+
+enum fc_can_cc_state {
+       fc_can_state_unknown = 0,
+       fc_can_state_config,
+       fc_can_state_normalActive,
+       fc_can_state_warning,
+       fc_can_state_error_passive,
+       fc_can_state_bus_off,
+};
+
+enum fc_can_error_type {
+       fc_can_error_none = 0,
+       fc_can_error_stuff,
+       fc_can_error_form,
+       fc_can_error_acknowledge,
+       fc_can_error_bit1,
+       fc_can_error_bit0,
+       fc_can_error_crc,
+       fc_can_error_parity,
+};
+
+union fc_packet_types {
+       struct fc_info_packet           info_packet;
+       struct fc_flexray_frame         flexray_frame;
+       struct fc_error_packet          error_packet;
+       struct fc_status_packet         status_packet;
+       struct fc_tx_ack_packet         tx_ack_packet;
+       struct fc_nm_vector_packet      nm_vector_packet;
+       struct fc_notification_packet   notification_packet;
+       struct fc_trigger_ex_info_packet ex_info_packet;
+       struct fc_can_packet            can_packet;
+       struct fc_can_error_packet      can_error_packet;
+};
+
+struct fc_packet_buf {
+       struct  fc_packet       header;
+       union   fc_packet_types packet;
+} __packed;
+
+u32 flexcard_parse_packet(struct fc_packet_buf *pb, u32 avail,
+                         struct flexcard_dma *dma);
+
+#endif /* __FLEXCARD_DMA_H */
diff --git a/drivers/dma/flexcard/parser.c b/drivers/dma/flexcard/parser.c
new file mode 100644
index 0000000..2450229
--- /dev/null
+++ b/drivers/dma/flexcard/parser.c
@@ -0,0 +1,227 @@
+/*
+ * Eberspächer Flexcard PMC II Carrier Board PCI Driver - packet parser/mux
+ *
+ * Copyright (c) 2014 - 2016, Linutronix GmbH
+ * Author: Benedikt Spranger <b.spran...@linutronix.de>
+ *         Holger Dengler <deng...@linutronix.de>
+ *
+ * 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.
+ */
+
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/flexcard.h>
+#include "flexcard-dma.h"
+
+static LIST_HEAD(rx_cb_list);
+static DEFINE_SPINLOCK(rx_cb_lock);
+
+struct fc_rx_cb {
+       struct list_head list;
+       int (*rx_cb)(void *priv, void *data, size_t len);
+       int cc;
+       void *priv;
+};
+
+/**
+ * flexcard_register_rx_cb() - Registers a callback for received packages
+ * @cc:                communication controller id
+ * @priv:      pointer to private data of the cc
+ * @rx_cp:     pionter to the receive callback
+ *
+ * Registers a callback for a communication controller specific handling for
+ * received packages. The callback is called by the generic parser, if the
+ * communication controller id inside of the received package matches the cc
+ * of the callback owner.
+ *
+ * Return: 0 is returned on success and a negative errno code for failure.
+ */
+int flexcard_register_rx_cb(int cc, void *priv,
+                           int (*rx_cb)(void *priv, void *data, size_t len))
+{
+       unsigned long flags;
+       struct fc_rx_cb *cb, *next;
+
+       if (!rx_cb)
+               return -EINVAL;
+
+       cb = kmalloc(sizeof(*cb), GFP_KERNEL);
+       if (!cb)
+               return -ENOMEM;
+
+       cb->cc = cc;
+       cb->priv = priv;
+       cb->rx_cb = rx_cb;
+
+       spin_lock_irqsave(&rx_cb_lock, flags);
+       list_for_each_entry(next, &rx_cb_list, list)
+               if (next->cc == cc)
+                       goto out;
+
+       list_add_tail(&cb->list, &rx_cb_list);
+       spin_unlock_irqrestore(&rx_cb_lock, flags);
+
+       return 0;
+out:
+       spin_unlock_irqrestore(&rx_cb_lock, flags);
+       kfree(cb);
+
+       return -EBUSY;
+}
+EXPORT_SYMBOL_GPL(flexcard_register_rx_cb);
+
+/**
+ * flexcard_unregister_rx_cb() - Unregisters a callback for received packages
+ * @cc:                communication controller id
+ *
+ * Unregisters a callback for a communication controller specific handling for
+ * received packages.
+ *
+ * Return: 0 is returned on success and a negative errno code for failure.
+ */
+void flexcard_unregister_rx_cb(int cc)
+{
+       unsigned long flags;
+       struct fc_rx_cb *cur, *next;
+       int found = 0;
+
+       spin_lock_irqsave(&rx_cb_lock, flags);
+       list_for_each_entry_safe(cur, next, &rx_cb_list, list) {
+               if (cur->cc == cc) {
+                       list_del(&cur->list);
+                       kfree(cur);
+                       found = 1;
+                       break;
+               }
+       }
+
+       if (!found)
+               pr_err("no callback registered for cc %d\n", cc);
+
+       spin_unlock_irqrestore(&rx_cb_lock, flags);
+}
+EXPORT_SYMBOL_GPL(flexcard_unregister_rx_cb);
+
+static int flexcard_queue_rx(int cc, void *buf, size_t len)
+{
+       struct fc_rx_cb *next;
+       unsigned long flags;
+       int ret = -ENODEV;
+
+       spin_lock_irqsave(&rx_cb_lock, flags);
+       list_for_each_entry(next, &rx_cb_list, list)
+               if (next->cc == cc)
+                       ret = next->rx_cb(next->priv, buf, len);
+       spin_unlock_irqrestore(&rx_cb_lock, flags);
+
+       return ret;
+}
+
+static u32 flexcard_get_packet_len(u32 header)
+{
+       u32 len;
+
+       /*
+        * header contains the number of transmitted 16bit words in bits 30-16.
+        * if the number is odd the DMA engine padded with zero to 32bit.
+        * calculate the number of transmitted bytes.
+        */
+
+       len = le32_to_cpu(header);
+
+       len >>= FLEXCARD_BUF_HEADER_LEN_SHIFT;
+       len &= FLEXCARD_BUF_HEADER_LEN_MASK;
+
+       len = roundup(len, 4);
+
+       return len;
+}
+
+/**
+ * selfsync_cc - adjust the cc number for self-sync packages
+ * @dma:       pointer to dma structure
+ * @cc:                package cc
+ *
+ * Some Flexcards has support for self-synci bus configurations. With this
+ * feature it is possible to get a synchronized bus configuration with a
+ * single card.
+ * Indication for a self-sync package is eray_nr == 1 and cc == 1. The
+ * packages are always handled by communication controller 0.
+ */
+static inline u32 selfsync_cc(struct flexcard_dma *dma, u32 cc)
+{
+               if ((dma->nr_eray == 1) && (cc == 1))
+                       return 0;
+               return cc;
+}
+
+u32 flexcard_parse_packet(struct fc_packet_buf *pb, u32 avail,
+                         struct flexcard_dma *dma)
+{
+       u32 l, cc, len = sizeof(struct fc_packet);
+       union fc_packet_types *pt = &pb->packet;
+
+       switch (le32_to_cpu(pb->header.type)) {
+       case FC_PACKET_TYPE_INFO:
+               len += sizeof(struct fc_info_packet);
+               cc = pt->info_packet.cc;
+               break;
+       case FC_PACKET_TYPE_ERROR:
+               len += sizeof(struct fc_error_packet);
+               cc = pt->error_packet.cc;
+               break;
+       case FC_PACKET_TYPE_STATUS:
+               len += sizeof(struct fc_status_packet);
+               cc = selfsync_cc(dma, pt->status_packet.cc);
+               break;
+       case FC_PACKET_TYPE_NMV_VECTOR:
+               len += sizeof(struct fc_nm_vector_packet);
+               cc = pt->nm_vector_packet.cc;
+               break;
+       case FC_PACKET_TYPE_NOTIFICATION:
+               len += sizeof(struct fc_notification_packet);
+               cc = 0;
+               break;
+       case FC_PACKET_TYPE_TRIGGER_EX:
+               len += sizeof(struct fc_trigger_ex_info_packet);
+               cc = 0;
+               break;
+       case FC_PACKET_TYPE_CAN:
+               len += sizeof(struct fc_can_packet);
+               cc = FLEXCARD_CANIF_OFFSET + pt->can_packet.cc;
+               break;
+       case FC_PACKET_TYPE_CAN_ERROR:
+               len += sizeof(struct fc_can_error_packet);
+               cc = FLEXCARD_CANIF_OFFSET + pt->can_error_packet.cc;
+               break;
+       case FC_PACKET_TYPE_FLEXRAY_FRAME:
+               len += sizeof(struct fc_flexray_frame);
+               pt->flexray_frame.pdata = len;
+               l = flexcard_get_packet_len(pt->flexray_frame.header);
+               len += l;
+               cc = pt->flexray_frame.cc;
+               break;
+       case FC_PACKET_TYPE_TX_ACK:
+               len += sizeof(struct fc_tx_ack_packet);
+               pt->tx_ack_packet.pdata = len;
+               l = flexcard_get_packet_len(pt->tx_ack_packet.header);
+               len += l;
+               cc = selfsync_cc(dma, pt->tx_ack_packet.cc);
+               break;
+       case FC_PACKET_TYPE_TRIGGER:
+       default:
+               pr_debug("pkt->type = %08x\n", pb->header.type);
+               return 0;
+       }
+
+       if (len > avail)
+               return 0;
+
+       flexcard_queue_rx(cc, pb, len);
+
+       return len;
+}
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 490b435..cb87c27 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -303,6 +303,7 @@ config MFD_FLEXCARD
        tristate "Eberspaecher Flexcard PMC II Carrier Board"
        select MFD_CORE
        select IRQ_DOMAIN
+       select FLEXCARD_DMA
        select FLEXCARD_MISC
        select FLEXCARD_PCLOCK
        depends on PCI
diff --git a/include/linux/mfd/flexcard.h b/include/linux/mfd/flexcard.h
index 819c6ef..eeabc9e 100644
--- a/include/linux/mfd/flexcard.h
+++ b/include/linux/mfd/flexcard.h
@@ -109,4 +109,8 @@ struct flexcard_device {
 int flexcard_setup_irq(struct pci_dev *pdev);
 void flexcard_remove_irq(struct pci_dev *pdev);
 
+int flexcard_register_rx_cb(int cc, void *priv,
+                           int (*rx_cb)(void *priv, void *data, size_t len));
+void flexcard_unregister_rx_cb(int cc);
+
 #endif /* _LINUX_FLEXCARD_H */
-- 
2.1.4

Reply via email to