[PATCH 12/12] dma: Flexcard DMA ringbuffer demux driver

From: Holger Dengler
Date: Tue Dec 13 2016 - 19:14:40 EST


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.spranger@xxxxxxxxxxxxx>
Signed-off-by: Holger Dengler <dengler@xxxxxxxxxxxxx>
cc: Vinod Koul <vinod.koul@xxxxxxxxx>
---
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.spranger@xxxxxxxxxxxxx>
+ * Holger Dengler <dengler@xxxxxxxxxxxxx>
+ *
+ * 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 <dengler@xxxxxxxxxxxxx>");
+MODULE_AUTHOR("Benedikt Spranger <b.spranger@xxxxxxxxxxxxx>");
+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.spranger@xxxxxxxxxxxxx>
+ * Holger Dengler <dengler@xxxxxxxxxxxxx>
+ *
+ * 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.spranger@xxxxxxxxxxxxx>
+ * Holger Dengler <dengler@xxxxxxxxxxxxx>
+ *
+ * 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