--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * QMC driver
+ *
+ * Copyright 2022 CS GROUP France
+ *
+ * Author: Herve Codina <herve.codina@bootlin.com>
+ */
+
+#include <soc/fsl/qe/qmc.h>
+#include <linux/dma-mapping.h>
+#include <linux/hdlc.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <soc/fsl/cpm.h>
+#include <sysdev/fsl_soc.h>
+#include "tsa.h"
+
+/* SCC general mode register high (32 bits) */
+#define SCC_GSMRL      0x00
+#define SCC_GSMRL_ENR          (1 << 5)
+#define SCC_GSMRL_ENT          (1 << 4)
+#define SCC_GSMRL_MODE_QMC     (0x0A << 0)
+
+/* SCC general mode register low (32 bits) */
+#define SCC_GSMRH      0x04
+#define   SCC_GSMRH_CTSS       (1 << 7)
+#define   SCC_GSMRH_CDS                (1 << 8)
+#define   SCC_GSMRH_CTSP       (1 << 9)
+#define   SCC_GSMRH_CDP                (1 << 10)
+
+/* SCC event register (16 bits) */
+#define SCC_SCCE       0x10
+#define   SCC_SCCE_IQOV                (1 << 3)
+#define   SCC_SCCE_GINT                (1 << 2)
+#define   SCC_SCCE_GUN         (1 << 1)
+#define   SCC_SCCE_GOV         (1 << 0)
+
+/* SCC mask register (16 bits) */
+#define SCC_SCCM       0x14
+/* Multichannel base pointer (32 bits) */
+#define QMC_GBL_MCBASE         0x00
+/* Multichannel controller state (16 bits) */
+#define QMC_GBL_QMCSTATE       0x04
+/* Maximum receive buffer length (16 bits) */
+#define QMC_GBL_MRBLR          0x06
+/* Tx time-slot assignment table pointer (16 bits) */
+#define QMC_GBL_TX_S_PTR       0x08
+/* Rx pointer (16 bits) */
+#define QMC_GBL_RXPTR          0x0A
+/* Global receive frame threshold (16 bits) */
+#define QMC_GBL_GRFTHR         0x0C
+/* Global receive frame count (16 bits) */
+#define QMC_GBL_GRFCNT         0x0E
+/* Multichannel interrupt base address (32 bits) */
+#define QMC_GBL_INTBASE                0x10
+/* Multichannel interrupt pointer (32 bits) */
+#define QMC_GBL_INTPTR         0x14
+/* Rx time-slot assignment table pointer (16 bits) */
+#define QMC_GBL_RX_S_PTR       0x18
+/* Tx pointer (16 bits) */
+#define QMC_GBL_TXPTR          0x1A
+/* CRC constant (32 bits) */
+#define QMC_GBL_C_MASK32       0x1C
+/* Time slot assignment table Rx (32 x 16 bits) */
+#define QMC_GBL_TSATRX         0x20
+/* Time slot assignment table Tx (32 x 16 bits) */
+#define QMC_GBL_TSATTX         0x60
+/* CRC constant (16 bits) */
+#define QMC_GBL_C_MASK16       0xA0
+
+/* TSA entry (16bit entry in TSATRX and TSATTX) */
+#define QMC_TSA_VALID          (1 << 15)
+#define QMC_TSA_WRAP           (1 << 14)
+#define QMC_TSA_MASK           (0x303F)
+#define QMC_TSA_CHANNEL(x)     ((x) << 6)
+
+/* Tx buffer descriptor base address (16 bits, offset from MCBASE) */
+#define QMC_SPE_TBASE  0x00
+
+/* Channel mode register (16 bits) */
+#define QMC_SPE_CHAMR  0x02
+#define   QMC_SPE_CHAMR_MODE_HDLC      (1 << 15)
+#define   QMC_SPE_CHAMR_MODE_TRANSP    ((0 << 15) | (1 << 13))
+#define   QMC_SPE_CHAMR_ENT            (1 << 12)
+#define   QMC_SPE_CHAMR_POL            (1 << 8)
+#define   QMC_SPE_CHAMR_HDLC_IDLM      (1 << 13)
+#define   QMC_SPE_CHAMR_HDLC_CRC       (1 << 7)
+#define   QMC_SPE_CHAMR_HDLC_NOF       (0x0f << 0)
+#define   QMC_SPE_CHAMR_TRANSP_RD      (1 << 14)
+#define   QMC_SPE_CHAMR_TRANSP_SYNC    (1 << 10)
+
+/* Tx internal state (32 bits) */
+#define QMC_SPE_TSTATE 0x04
+/* Tx buffer descriptor pointer (16 bits) */
+#define QMC_SPE_TBPTR  0x0C
+/* Zero-insertion state (32 bits) */
+#define QMC_SPE_ZISTATE        0x14
+/* Channel’s interrupt mask flags (16 bits) */
+#define QMC_SPE_INTMSK 0x1C
+/* Rx buffer descriptor base address (16 bits, offset from MCBASE) */
+#define QMC_SPE_RBASE  0x20
+/* HDLC: Maximum frame length register (16 bits) */
+#define QMC_SPE_MFLR   0x22
+/* TRANSPARENT: Transparent maximum receive length (16 bits) */
+#define QMC_SPE_TMRBLR 0x22
+/* Rx internal state (32 bits) */
+#define QMC_SPE_RSTATE 0x24
+/* Rx buffer descriptor pointer (16 bits) */
+#define QMC_SPE_RBPTR  0x2C
+/* Packs 4 bytes to 1 long word before writing to buffer (32 bits) */
+#define QMC_SPE_RPACK  0x30
+/* Zero deletion state (32 bits) */
+#define QMC_SPE_ZDSTATE        0x34
+
+/* Transparent synchronization (16 bits) */
+#define QMC_SPE_TRNSYNC 0x3C
+#define   QMC_SPE_TRNSYNC_RX(x)        ((x) << 8)
+#define   QMC_SPE_TRNSYNC_TX(x)        ((x) << 0)
+
+/* Interrupt related registers bits */
+#define QMC_INT_V              (1 << 15)
+#define QMC_INT_W              (1 << 14)
+#define QMC_INT_NID            (1 << 13)
+#define QMC_INT_IDL            (1 << 12)
+#define QMC_INT_GET_CHANNEL(x) (((x) & 0x0FC0) >> 6)
+#define QMC_INT_MRF            (1 << 5)
+#define QMC_INT_UN             (1 << 4)
+#define QMC_INT_RXF            (1 << 3)
+#define QMC_INT_BSY            (1 << 2)
+#define QMC_INT_TXB            (1 << 1)
+#define QMC_INT_RXB            (1 << 0)
+
+/* BD related registers bits */
+#define QMC_BD_RX_E    (1 << 15)
+#define QMC_BD_RX_W    (1 << 13)
+#define QMC_BD_RX_I    (1 << 12)
+#define QMC_BD_RX_L    (1 << 11)
+#define QMC_BD_RX_F    (1 << 10)
+#define QMC_BD_RX_CM   (1 << 9)
+#define QMC_BD_RX_UB   (1 << 7)
+#define QMC_BD_RX_LG   (1 << 5)
+#define QMC_BD_RX_NO   (1 << 4)
+#define QMC_BD_RX_AB   (1 << 3)
+#define QMC_BD_RX_CR   (1 << 2)
+
+#define QMC_BD_TX_R    (1 << 15)
+#define QMC_BD_TX_W    (1 << 13)
+#define QMC_BD_TX_I    (1 << 12)
+#define QMC_BD_TX_L    (1 << 11)
+#define QMC_BD_TX_TC   (1 << 10)
+#define QMC_BD_TX_CM   (1 << 9)
+#define QMC_BD_TX_UB   (1 << 7)
+#define QMC_BD_TX_PAD  (0x0f << 0)
+
+/* Numbers of BDs and interrupt items */
+#define QMC_NB_TXBDS   8
+#define QMC_NB_RXBDS   8
+#define QMC_NB_INTS    128
+
+struct qmc_xfer_desc {
+       union {
+               void (*tx_complete)(void *context);
+               void (*rx_complete)(void *context, size_t length);
+       };
+       void *context;
+};
+
+struct qmc_chan {
+       struct list_head list;
+       unsigned int id;
+       struct qmc *qmc;
+       void *__iomem s_param;
+       enum qmc_mode mode;
+       u64     tx_ts_mask;
+       u64     rx_ts_mask;
+       bool is_reverse_data;
+
+       spinlock_t      tx_lock;
+       cbd_t __iomem *txbds;
+       cbd_t __iomem *txbd_free;
+       cbd_t __iomem *txbd_done;
+       struct qmc_xfer_desc tx_desc[QMC_NB_TXBDS];
+       u64     nb_tx_underrun;
+       bool    is_tx_stopped;
+
+       spinlock_t      rx_lock;
+       cbd_t __iomem *rxbds;
+       cbd_t __iomem *rxbd_free;
+       cbd_t __iomem *rxbd_done;
+       struct qmc_xfer_desc rx_desc[QMC_NB_RXBDS];
+       u64     nb_rx_busy;
+       int     rx_pending;
+       bool    is_rx_halted;
+       bool    is_rx_stopped;
+};
+
+struct qmc {
+       struct device *dev;
+       struct tsa_serial *tsa_serial;
+       void *__iomem scc_regs;
+       void *__iomem scc_pram;
+       void *__iomem dpram;
+       u16 scc_pram_offset;
+       cbd_t __iomem *bd_table;
+       dma_addr_t bd_dma_addr;
+       size_t bd_size;
+       u16 __iomem *int_table;
+       u16 __iomem *int_curr;
+       dma_addr_t int_dma_addr;
+       size_t int_size;
+       struct list_head chan_head;
+       struct qmc_chan *chans[64];
+};
+
+static inline void qmc_write16(void *__iomem addr, u16 val)
+{
+       iowrite16be(val, addr);
+}
+
+static inline u16 qmc_read16(void *__iomem addr)
+{
+       return ioread16be(addr);
+}
+
+static inline void qmc_setbits16(void *__iomem addr, u16 set)
+{
+       qmc_write16(addr, qmc_read16(addr) | set);
+}
+
+static inline void qmc_clrbits16(void *__iomem addr, u16 clr)
+{
+       qmc_write16(addr, qmc_read16(addr) & ~clr);
+}
+
+static inline void qmc_write32(void *__iomem addr, u32 val)
+{
+       iowrite32be(val, addr);
+}
+
+static inline u32 qmc_read32(void *__iomem addr)
+{
+       return ioread32be(addr);
+}
+
+static inline void qmc_setbits32(void *__iomem addr, u32 set)
+{
+       qmc_write32(addr, qmc_read32(addr) | set);
+}
+
+
+int qmc_chan_get_info(struct qmc_chan *chan, struct qmc_chan_info *info)
+{
+       struct tsa_serial_info tsa_info;
+       int ret;
+
+       /* Retrieve info from the TSA related serial */
+       ret = tsa_serial_get_info(chan->qmc->tsa_serial, &tsa_info);
+       if (ret)
+               return ret;
+
+       info->mode = chan->mode;
+       info->rx_fs_rate = tsa_info.rx_fs_rate;
+       info->rx_bit_rate = tsa_info.rx_bit_rate;
+       info->nb_tx_ts = hweight64(chan->tx_ts_mask);
+       info->tx_fs_rate = tsa_info.tx_fs_rate;
+       info->tx_bit_rate = tsa_info.tx_bit_rate;
+       info->nb_rx_ts = hweight64(chan->rx_ts_mask);
+
+       return 0;
+}
+EXPORT_SYMBOL(qmc_chan_get_info);
+
+int qmc_chan_set_param(struct qmc_chan *chan, const struct qmc_chan_param *param)
+{
+       if (param->mode != chan->mode)
+               return -EINVAL;
+
+       switch (param->mode) {
+       case QMC_HDLC:
+               if ((param->hdlc.max_rx_buf_size % 4) ||
+                   (param->hdlc.max_rx_buf_size < 8))
+                       return -EINVAL;
+
+               qmc_write16(chan->qmc->scc_pram + QMC_GBL_MRBLR,
+                           param->hdlc.max_rx_buf_size - 8);
+               qmc_write16(chan->s_param + QMC_SPE_MFLR,
+                           param->hdlc.max_rx_frame_size);
+               if (param->hdlc.is_crc32) {
+                       qmc_setbits16(chan->s_param + QMC_SPE_CHAMR,
+                                     QMC_SPE_CHAMR_HDLC_CRC);
+               } else {
+                       qmc_clrbits16(chan->s_param + QMC_SPE_CHAMR,
+                                     QMC_SPE_CHAMR_HDLC_CRC);
+               }
+               break;
+
+       case QMC_TRANSPARENT:
+               qmc_write16(chan->s_param + QMC_SPE_TMRBLR,
+                           param->transp.max_rx_buf_size);
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(qmc_chan_set_param);
+
+int qmc_chan_write_submit(struct qmc_chan *chan, dma_addr_t addr, size_t length,
+                         void (*complete)(void *context), void *context)
+{
+       struct qmc_xfer_desc *xfer_desc;
+       unsigned long flags;
+       cbd_t *__iomem bd;
+       u16 ctrl;
+       int ret;
+
+       /*
+        * R bit  UB bit
+        *   0       0  : The BD is free
+        *   1       1  : The BD is in used, waiting for transfer
+        *   0       1  : The BD is in used, waiting for completion
+        *   1       0  : Should not append
+        */
+
+       spin_lock_irqsave(&chan->tx_lock, flags);
+       bd = chan->txbd_free;
+
+       ctrl = qmc_read16(&bd->cbd_sc);
+       if (ctrl & (QMC_BD_TX_R | QMC_BD_TX_UB)) {
+               /* We are full ... */
+               ret = -EBUSY;
+               goto end;
+       }
+
+       qmc_write16(&bd->cbd_datlen, length);
+       qmc_write32(&bd->cbd_bufaddr, addr);
+
+       xfer_desc = &chan->tx_desc[bd - chan->txbds];
+       xfer_desc->tx_complete = complete;
+       xfer_desc->context = context;
+
+       /* Activate the descriptor */
+       ctrl |= (QMC_BD_TX_R | QMC_BD_TX_UB);
+       wmb(); /* Be sure to flush the descriptor before control update */
+       qmc_write16(&bd->cbd_sc, ctrl);
+
+       if (!chan->is_tx_stopped)
+               qmc_setbits16(chan->s_param + QMC_SPE_CHAMR, QMC_SPE_CHAMR_POL);
+
+       if (ctrl & QMC_BD_TX_W)
+               chan->txbd_free = chan->txbds;
+       else
+               chan->txbd_free++;
+
+       ret = 0;
+
+end:
+       spin_unlock_irqrestore(&chan->tx_lock, flags);
+       return ret;
+}
+EXPORT_SYMBOL(qmc_chan_write_submit);
+
+static void qmc_chan_write_done(struct qmc_chan *chan)
+{
+       struct qmc_xfer_desc *xfer_desc;
+       void (*complete)(void *context);
+       unsigned long flags;
+       void *context;
+       cbd_t *__iomem bd;
+       u16 ctrl;
+
+       /*
+        * R bit  UB bit
+        *   0       0  : The BD is free
+        *   1       1  : The BD is in used, waiting for transfer
+        *   0       1  : The BD is in used, waiting for completion
+        *   1       0  : Should not append
+        */
+
+       spin_lock_irqsave(&chan->tx_lock, flags);
+       bd = chan->txbd_done;
+
+       ctrl = qmc_read16(&bd->cbd_sc);
+       while (!(ctrl & QMC_BD_TX_R)) {
+               if (!(ctrl & QMC_BD_TX_UB))
+                       goto end;
+
+               xfer_desc = &chan->tx_desc[bd - chan->txbds];
+               complete = xfer_desc->tx_complete;
+               context = xfer_desc->context;
+               xfer_desc->tx_complete = NULL;
+               xfer_desc->context = NULL;
+
+               qmc_write16(&bd->cbd_sc, ctrl & ~QMC_BD_TX_UB);
+
+               if (ctrl & QMC_BD_TX_W)
+                       chan->txbd_done = chan->txbds;
+               else
+                       chan->txbd_done++;
+
+               if (complete) {
+                       spin_unlock_irqrestore(&chan->tx_lock, flags);
+                       complete(context);
+                       spin_lock_irqsave(&chan->tx_lock, flags);
+               }
+
+               bd = chan->txbd_done;
+               ctrl = qmc_read16(&bd->cbd_sc);
+       }
+
+end:
+       spin_unlock_irqrestore(&chan->tx_lock, flags);
+}
+
+int qmc_chan_read_submit(struct qmc_chan *chan, dma_addr_t addr, size_t length,
+                        void (*complete)(void *context, size_t length), void *context)
+{
+       struct qmc_xfer_desc *xfer_desc;
+       unsigned long flags;
+       cbd_t *__iomem bd;
+       u16 ctrl;
+       int ret;
+
+       /*
+        * E bit  UB bit
+        *   0       0  : The BD is free
+        *   1       1  : The BD is in used, waiting for transfer
+        *   0       1  : The BD is in used, waiting for completion
+        *   1       0  : Should not append
+        */
+
+       spin_lock_irqsave(&chan->rx_lock, flags);
+       bd = chan->rxbd_free;
+
+       ctrl = qmc_read16(&bd->cbd_sc);
+       if (ctrl & (QMC_BD_RX_E | QMC_BD_RX_UB)) {
+               /* We are full ... */
+               ret = -EBUSY;
+               goto end;
+       }
+
+       qmc_write16(&bd->cbd_datlen, 0); /* data length is updated by the QMC */
+       qmc_write32(&bd->cbd_bufaddr, addr);
+
+       xfer_desc = &chan->rx_desc[bd - chan->rxbds];
+       xfer_desc->rx_complete = complete;
+       xfer_desc->context = context;
+
+       /* Activate the descriptor */
+       ctrl |= (QMC_BD_RX_E | QMC_BD_RX_UB);
+       wmb(); /* Be sure to flush data before descriptor activation */
+       qmc_write16(&bd->cbd_sc, ctrl);
+
+       /* Restart receiver if needed */
+       if (chan->is_rx_halted && !chan->is_rx_stopped) {
+               /* Restart receiver */
+               if (chan->mode == QMC_TRANSPARENT)
+                       qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x18000080);
+               else
+                       qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x00000080);
+               qmc_write32(chan->s_param + QMC_SPE_RSTATE, 0x31000000);
+               chan->is_rx_halted = false;
+       }
+       chan->rx_pending++;
+
+       if (ctrl & QMC_BD_RX_W)
+               chan->rxbd_free = chan->rxbds;
+       else
+               chan->rxbd_free++;
+
+       ret = 0;
+end:
+       spin_unlock_irqrestore(&chan->rx_lock, flags);
+       return ret;
+}
+EXPORT_SYMBOL(qmc_chan_read_submit);
+
+static void qmc_chan_read_done(struct qmc_chan *chan)
+{
+       void (*complete)(void *context, size_t size);
+       struct qmc_xfer_desc *xfer_desc;
+       unsigned long flags;
+       cbd_t *__iomem bd;
+       void *context;
+       u16 datalen;
+       u16 ctrl;
+
+       /*
+        * E bit  UB bit
+        *   0       0  : The BD is free
+        *   1       1  : The BD is in used, waiting for transfer
+        *   0       1  : The BD is in used, waiting for completion
+        *   1       0  : Should not append
+        */
+
+       spin_lock_irqsave(&chan->rx_lock, flags);
+       bd = chan->rxbd_done;
+
+       ctrl = qmc_read16(&bd->cbd_sc);
+       while (!(ctrl & QMC_BD_RX_E)) {
+               if (!(ctrl & QMC_BD_RX_UB))
+                       goto end;
+
+               xfer_desc = &chan->rx_desc[bd - chan->rxbds];
+               complete = xfer_desc->rx_complete;
+               context = xfer_desc->context;
+               xfer_desc->rx_complete = NULL;
+               xfer_desc->context = NULL;
+
+               datalen = qmc_read16(&bd->cbd_datlen);
+               qmc_write16(&bd->cbd_sc, ctrl & ~QMC_BD_RX_UB);
+
+               if (ctrl & QMC_BD_RX_W)
+                       chan->rxbd_done = chan->rxbds;
+               else
+                       chan->rxbd_done++;
+
+               chan->rx_pending--;
+
+               if (complete) {
+                       spin_unlock_irqrestore(&chan->rx_lock, flags);
+                       complete(context, datalen);
+                       spin_lock_irqsave(&chan->rx_lock, flags);
+               }
+
+               bd = chan->rxbd_done;
+               ctrl = qmc_read16(&bd->cbd_sc);
+       }
+
+end:
+       spin_unlock_irqrestore(&chan->rx_lock, flags);
+}
+
+static int qmc_chan_command(struct qmc_chan *chan, u8 qmc_opcode)
+{
+       return cpm_command(chan->id << 2, (qmc_opcode << 4) | 0x0E);
+}
+
+static int qmc_chan_stop_rx(struct qmc_chan *chan)
+{
+       unsigned long flags;
+       int ret;
+
+       spin_lock_irqsave(&chan->rx_lock, flags);
+
+       /* Send STOP RECEIVE command */
+       ret = qmc_chan_command(chan, 0x0);
+       if (ret) {
+               dev_err(chan->qmc->dev, "chan %u: Send STOP RECEIVE failed (%d)\n",
+                       chan->id, ret);
+               goto end;
+       }
+
+       chan->is_rx_stopped = true;
+
+end:
+       spin_unlock_irqrestore(&chan->rx_lock, flags);
+       return ret;
+}
+
+static int qmc_chan_stop_tx(struct qmc_chan *chan)
+{
+       unsigned long flags;
+       int ret;
+
+       spin_lock_irqsave(&chan->tx_lock, flags);
+
+       /* Send STOP TRANSMIT command */
+       ret = qmc_chan_command(chan, 0x1);
+       if (ret) {
+               dev_err(chan->qmc->dev, "chan %u: Send STOP TRANSMIT failed (%d)\n",
+                       chan->id, ret);
+               goto end;
+       }
+
+       chan->is_tx_stopped = true;
+
+end:
+       spin_unlock_irqrestore(&chan->tx_lock, flags);
+       return ret;
+}
+
+int qmc_chan_stop(struct qmc_chan *chan, int direction)
+{
+       int ret;
+
+       if (direction & QMC_CHAN_READ) {
+               ret = qmc_chan_stop_rx(chan);
+               if (ret)
+                       return ret;
+       }
+
+       if (direction & QMC_CHAN_WRITE) {
+               ret = qmc_chan_stop_tx(chan);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(qmc_chan_stop);
+
+static void qmc_chan_start_rx(struct qmc_chan *chan)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&chan->rx_lock, flags);
+
+       /* Restart the receiver */
+       if (chan->mode == QMC_TRANSPARENT)
+               qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x18000080);
+       else
+               qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x00000080);
+       qmc_write32(chan->s_param + QMC_SPE_RSTATE, 0x31000000);
+       chan->is_rx_halted = false;
+
+       chan->is_rx_stopped = false;
+
+       spin_unlock_irqrestore(&chan->rx_lock, flags);
+}
+
+static void qmc_chan_start_tx(struct qmc_chan *chan)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&chan->tx_lock, flags);
+
+       /*
+        * Enable channel transmitter as it could be disabled if
+        * qmc_chan_reset() was called.
+        */
+       qmc_setbits16(chan->s_param + QMC_SPE_CHAMR, QMC_SPE_CHAMR_ENT);
+
+       /* Set the POL bit in the channel mode register */
+       qmc_setbits16(chan->s_param + QMC_SPE_CHAMR, QMC_SPE_CHAMR_POL);
+
+       chan->is_tx_stopped = false;
+
+       spin_unlock_irqrestore(&chan->tx_lock, flags);
+}
+
+int qmc_chan_start(struct qmc_chan *chan, int direction)
+{
+       if (direction & QMC_CHAN_READ)
+               qmc_chan_start_rx(chan);
+
+       if (direction & QMC_CHAN_WRITE)
+               qmc_chan_start_tx(chan);
+
+       return 0;
+}
+EXPORT_SYMBOL(qmc_chan_start);
+
+static void qmc_chan_reset_rx(struct qmc_chan *chan)
+{
+       struct qmc_xfer_desc *xfer_desc;
+       unsigned long flags;
+       cbd_t *__iomem bd;
+       u16 ctrl;
+
+       spin_lock_irqsave(&chan->rx_lock, flags);
+       bd = chan->rxbds;
+       do {
+               ctrl = qmc_read16(&bd->cbd_sc);
+               qmc_write16(&bd->cbd_sc, ctrl & ~(QMC_BD_RX_UB | QMC_BD_RX_E));
+
+               xfer_desc = &chan->rx_desc[bd - chan->rxbds];
+               xfer_desc->rx_complete = NULL;
+               xfer_desc->context = NULL;
+
+               bd++;
+       } while (!(ctrl & QMC_BD_RX_W));
+
+       chan->rxbd_free = chan->rxbds;
+       chan->rxbd_done = chan->rxbds;
+       qmc_write16(chan->s_param + QMC_SPE_RBPTR,
+                   qmc_read16(chan->s_param + QMC_SPE_RBASE));
+
+       chan->rx_pending = 0;
+       chan->is_rx_stopped = false;
+
+       spin_unlock_irqrestore(&chan->rx_lock, flags);
+}
+
+static void qmc_chan_reset_tx(struct qmc_chan *chan)
+{
+       struct qmc_xfer_desc *xfer_desc;
+       unsigned long flags;
+       cbd_t *__iomem bd;
+       u16 ctrl;
+
+       spin_lock_irqsave(&chan->tx_lock, flags);
+
+       /* Disable transmitter. It will be re-enable on qmc_chan_start() */
+       qmc_clrbits16(chan->s_param + QMC_SPE_CHAMR, QMC_SPE_CHAMR_ENT);
+
+       bd = chan->txbds;
+       do {
+               ctrl = qmc_read16(&bd->cbd_sc);
+               qmc_write16(&bd->cbd_sc, ctrl & ~(QMC_BD_TX_UB | QMC_BD_TX_R));
+
+               xfer_desc = &chan->tx_desc[bd - chan->txbds];
+               xfer_desc->tx_complete = NULL;
+               xfer_desc->context = NULL;
+
+               bd++;
+       } while (!(ctrl & QMC_BD_TX_W));
+
+       chan->txbd_free = chan->txbds;
+       chan->txbd_done = chan->txbds;
+       qmc_write16(chan->s_param + QMC_SPE_TBPTR,
+                   qmc_read16(chan->s_param + QMC_SPE_TBASE));
+
+       /* Reset TSTATE and ZISTATE to their initial value */
+       qmc_write32(chan->s_param + QMC_SPE_TSTATE, 0x30000000);
+       qmc_write32(chan->s_param + QMC_SPE_ZISTATE, 0x00000100);
+
+       spin_unlock_irqrestore(&chan->tx_lock, flags);
+}
+
+int qmc_chan_reset(struct qmc_chan *chan, int direction)
+{
+       if (direction & QMC_CHAN_READ)
+               qmc_chan_reset_rx(chan);
+
+       if (direction & QMC_CHAN_WRITE)
+               qmc_chan_reset_tx(chan);
+
+       return 0;
+}
+EXPORT_SYMBOL(qmc_chan_reset);
+
+static int qmc_check_chans(struct qmc *qmc)
+{
+       struct tsa_serial_info info;
+       bool is_one_table = false;
+       struct qmc_chan *chan;
+       u64 tx_ts_mask = 0;
+       u64 rx_ts_mask = 0;
+       u64 tx_ts_assigned_mask;
+       u64 rx_ts_assigned_mask;
+       int ret;
+
+       /* Retrieve info from the TSA related serial */
+       ret = tsa_serial_get_info(qmc->tsa_serial, &info);
+       if (ret)
+               return ret;
+
+       /*
+        * If more than 32 TS are assigned to this serial, one common table is
+        * used for Tx and Rx and so masks must be equal for all channels.
+        */
+       if ((info.nb_tx_ts > 32) || (info.nb_rx_ts > 32)) {
+               if (info.nb_tx_ts != info.nb_rx_ts) {
+                       dev_err(qmc->dev, "Number of TSA Tx/Rx TS assigned are not equal\n");
+                       return -EINVAL;
+               }
+               is_one_table = true;
+       }
+
+
+       tx_ts_assigned_mask = (((u64)1) << info.nb_tx_ts) - 1;
+       rx_ts_assigned_mask = (((u64)1) << info.nb_rx_ts) - 1;
+
+       list_for_each_entry(chan, &qmc->chan_head, list) {
+               if (chan->tx_ts_mask > tx_ts_assigned_mask) {
+                       dev_err(qmc->dev, "chan %u uses TSA unassigned Tx TS\n", chan->id);
+                       return -EINVAL;
+               }
+               if (tx_ts_mask & chan->tx_ts_mask) {
+                       dev_err(qmc->dev, "chan %u uses an already used Tx TS\n", chan->id);
+                       return -EINVAL;
+               }
+
+               if (chan->rx_ts_mask > rx_ts_assigned_mask) {
+                       dev_err(qmc->dev, "chan %u uses TSA unassigned Rx TS\n", chan->id);
+                       return -EINVAL;
+               }
+               if (rx_ts_mask & chan->rx_ts_mask) {
+                       dev_err(qmc->dev, "chan %u uses an already used Rx TS\n", chan->id);
+                       return -EINVAL;
+               }
+
+               if (is_one_table && (chan->tx_ts_mask != chan->rx_ts_mask)) {
+                       dev_err(qmc->dev, "chan %u uses different Rx and Tx TS\n", chan->id);
+                       return -EINVAL;
+               }
+
+               tx_ts_mask |= chan->tx_ts_mask;
+               rx_ts_mask |= chan->rx_ts_mask;
+       }
+
+       return 0;
+}
+
+static unsigned int qmc_nb_chans(struct qmc *qmc)
+{
+       unsigned int count = 0;
+       struct qmc_chan *chan;
+
+       list_for_each_entry(chan, &qmc->chan_head, list)
+               count++;
+
+       return count;
+}
+
+static int qmc_of_parse_chans(struct qmc *qmc, struct device_node *np)
+{
+       struct device_node *chan_np;
+       struct qmc_chan *chan;
+       const char *mode;
+       u32 chan_id;
+       u64 ts_mask;
+       int ret;
+
+       for_each_available_child_of_node(np, chan_np) {
+               ret = of_property_read_u32(chan_np, "reg", &chan_id);
+               if (ret) {
+                       dev_err(qmc->dev, "%pOF: failed to read reg\n", chan_np);
+                       of_node_put(chan_np);
+                       return ret;
+               }
+               if (chan_id > 63) {
+                       dev_err(qmc->dev, "%pOF: Invalid chan_id\n", chan_np);
+                       of_node_put(chan_np);
+                       return -EINVAL;
+               }
+
+               chan = devm_kzalloc(qmc->dev, sizeof(*chan), GFP_KERNEL);
+               if (!chan) {
+                       of_node_put(chan_np);
+                       return -ENOMEM;
+               }
+
+               chan->id = chan_id;
+               spin_lock_init(&chan->rx_lock);
+               spin_lock_init(&chan->tx_lock);
+
+               ret = of_property_read_u64(chan_np, "fsl,tx-ts-mask", &ts_mask);
+               if (ret) {
+                       dev_err(qmc->dev, "%pOF: failed to read fsl,tx-ts-mask\n",
+                               chan_np);
+                       of_node_put(chan_np);
+                       return ret;
+               }
+               chan->tx_ts_mask = ts_mask;
+
+               ret = of_property_read_u64(chan_np, "fsl,rx-ts-mask", &ts_mask);
+               if (ret) {
+                       dev_err(qmc->dev, "%pOF: failed to read fsl,rx-ts-mask\n",
+                               chan_np);
+                       of_node_put(chan_np);
+                       return ret;
+               }
+               chan->rx_ts_mask = ts_mask;
+
+               mode = "transparent";
+               ret = of_property_read_string(chan_np, "fsl,operational-mode", &mode);
+               if (ret && ret != -EINVAL) {
+                       dev_err(qmc->dev, "%pOF: failed to read fsl,operational-mode\n",
+                               chan_np);
+                       of_node_put(chan_np);
+                       return ret;
+               }
+               if (!strcmp(mode, "transparent")) {
+                       chan->mode = QMC_TRANSPARENT;
+               } else if (!strcmp(mode, "hdlc")) {
+                       chan->mode = QMC_HDLC;
+               } else {
+                       dev_err(qmc->dev, "%pOF: Invalid fsl,operational-mode (%s)\n",
+                               chan_np, mode);
+                       of_node_put(chan_np);
+                       return -EINVAL;
+               }
+
+               chan->is_reverse_data = of_property_read_bool(chan_np,
+                                                             "fsl,reverse-data");
+
+               list_add_tail(&chan->list, &qmc->chan_head);
+               qmc->chans[chan->id] = chan;
+       }
+
+       return qmc_check_chans(qmc);
+}
+
+static int qmc_setup_tsa_64rxtx(struct qmc *qmc, const struct tsa_serial_info *info)
+{
+       struct qmc_chan *chan;
+       unsigned int i;
+       u16 val;
+
+       /*
+        * Use a common Tx/Rx 64 entries table.
+        * Everything was previously checked, Tx and Rx related stuffs are
+        * identical -> Used Rx related stuff to build the table
+        */
+
+       /* Invalidate all entries */
+       for (i = 0; i < 64; i++)
+               qmc_write16(qmc->scc_pram + QMC_GBL_TSATRX + (i * 2), 0x0000);
+
+       /* Set entries based on Rx stuff*/
+       list_for_each_entry(chan, &qmc->chan_head, list) {
+               for (i = 0; i < info->nb_rx_ts; i++) {
+                       if (!(chan->rx_ts_mask & (((u64)1) << i)))
+                               continue;
+
+                       val = QMC_TSA_VALID | QMC_TSA_MASK |
+                             QMC_TSA_CHANNEL(chan->id);
+                       qmc_write16(qmc->scc_pram + QMC_GBL_TSATRX + (i * 2), val);
+               }
+       }
+
+       /* Set Wrap bit on last entry */
+       qmc_setbits16(qmc->scc_pram + QMC_GBL_TSATRX + ((info->nb_rx_ts - 1) * 2),
+                     QMC_TSA_WRAP);
+
+       /* Init pointers to the table */
+       val = qmc->scc_pram_offset + QMC_GBL_TSATRX;
+       qmc_write16(qmc->scc_pram + QMC_GBL_RX_S_PTR, val);
+       qmc_write16(qmc->scc_pram + QMC_GBL_RXPTR, val);
+       qmc_write16(qmc->scc_pram + QMC_GBL_TX_S_PTR, val);
+       qmc_write16(qmc->scc_pram + QMC_GBL_TXPTR, val);
+
+       return 0;
+}
+
+static int qmc_setup_tsa_32rx_32tx(struct qmc *qmc, const struct tsa_serial_info *info)
+{
+       struct qmc_chan *chan;
+       unsigned int i;
+       u16 val;
+
+       /*
+        * Use a Tx 32 entries table and a Rx 32 entries table.
+        * Everything was previously checked.
+        */
+
+       /* Invalidate all entries */
+       for (i = 0; i < 32; i++) {
+               qmc_write16(qmc->scc_pram + QMC_GBL_TSATRX + (i * 2), 0x0000);
+               qmc_write16(qmc->scc_pram + QMC_GBL_TSATTX + (i * 2), 0x0000);
+       }
+
+       /* Set entries based on Rx and Tx stuff*/
+       list_for_each_entry(chan, &qmc->chan_head, list) {
+               /* Rx part */
+               for (i = 0; i < info->nb_rx_ts; i++) {
+                       if (!(chan->rx_ts_mask & (((u64)1) << i)))
+                               continue;
+
+                       val = QMC_TSA_VALID | QMC_TSA_MASK |
+                             QMC_TSA_CHANNEL(chan->id);
+                       qmc_write16(qmc->scc_pram + QMC_GBL_TSATRX + (i * 2), val);
+               }
+               /* Tx part */
+               for (i = 0; i < info->nb_tx_ts; i++) {
+                       if (!(chan->tx_ts_mask & (((u64)1) << i)))
+                               continue;
+
+                       val = QMC_TSA_VALID | QMC_TSA_MASK |
+                             QMC_TSA_CHANNEL(chan->id);
+                       qmc_write16(qmc->scc_pram + QMC_GBL_TSATTX + (i * 2), val);
+               }
+       }
+
+       /* Set Wrap bit on last entries */
+       qmc_setbits16(qmc->scc_pram + QMC_GBL_TSATRX + ((info->nb_rx_ts - 1) * 2),
+                     QMC_TSA_WRAP);
+       qmc_setbits16(qmc->scc_pram + QMC_GBL_TSATTX + ((info->nb_tx_ts - 1) * 2),
+                     QMC_TSA_WRAP);
+
+       /* Init Rx pointers ...*/
+       val = qmc->scc_pram_offset + QMC_GBL_TSATRX;
+       qmc_write16(qmc->scc_pram + QMC_GBL_RX_S_PTR, val);
+       qmc_write16(qmc->scc_pram + QMC_GBL_RXPTR, val);
+
+       /* ... and Tx pointers */
+       val = qmc->scc_pram_offset + QMC_GBL_TSATTX;
+       qmc_write16(qmc->scc_pram + QMC_GBL_TX_S_PTR, val);
+       qmc_write16(qmc->scc_pram + QMC_GBL_TXPTR, val);
+
+       return 0;
+}
+
+static int qmc_setup_tsa(struct qmc *qmc)
+{
+       struct tsa_serial_info info;
+       int ret;
+
+       /* Retrieve info from the TSA related serial */
+       ret = tsa_serial_get_info(qmc->tsa_serial, &info);
+       if (ret)
+               return ret;
+
+       /*
+        * Setup one common 64 entries table or two 32 entries (one for Tx and
+        * one for Tx) according to assigned TS numbers.
+        */
+       return ((info.nb_tx_ts > 32) || (info.nb_rx_ts > 32)) ?
+               qmc_setup_tsa_64rxtx(qmc, &info) :
+               qmc_setup_tsa_32rx_32tx(qmc, &info);
+}
+
+static int qmc_setup_chan_trnsync(struct qmc *qmc, struct qmc_chan *chan)
+{
+       struct tsa_serial_info info;
+       u16 first_rx, last_tx;
+       u16 trnsync;
+       int ret;
+
+       /* Retrieve info from the TSA related serial */
+       ret = tsa_serial_get_info(chan->qmc->tsa_serial, &info);
+       if (ret)
+               return ret;
+
+       /* Find the first Rx TS allocated to the channel */
+       first_rx = chan->rx_ts_mask ? __ffs64(chan->rx_ts_mask) + 1 : 0;
+
+       /* Find the last Tx TS allocated to the channel */
+       last_tx = fls64(chan->tx_ts_mask);
+
+       trnsync = 0;
+       if (info.nb_rx_ts)
+               trnsync |= QMC_SPE_TRNSYNC_RX((first_rx % info.nb_rx_ts) * 2);
+       if (info.nb_tx_ts)
+               trnsync |= QMC_SPE_TRNSYNC_TX((last_tx % info.nb_tx_ts) * 2);
+
+       qmc_write16(chan->s_param + QMC_SPE_TRNSYNC, trnsync);
+
+       dev_dbg(qmc->dev, "chan %u: trnsync=0x%04x, rx %u/%u 0x%llx, tx %u/%u 0x%llx\n",
+               chan->id, trnsync,
+               first_rx, info.nb_rx_ts, chan->rx_ts_mask,
+               last_tx, info.nb_tx_ts, chan->tx_ts_mask);
+
+       return 0;
+}
+
+static int qmc_setup_chan(struct qmc *qmc, struct qmc_chan *chan)
+{
+       unsigned int i;
+       cbd_t __iomem *bd;
+       int ret;
+       u16 val;
+
+       chan->qmc = qmc;
+
+       /* Set channel specific parameter base address */
+       chan->s_param = qmc->dpram + (chan->id * 64);
+       /* 16 bd per channel (8 rx and 8 tx) */
+       chan->txbds = qmc->bd_table + (chan->id * (QMC_NB_TXBDS + QMC_NB_RXBDS));
+       chan->rxbds = qmc->bd_table + (chan->id * (QMC_NB_TXBDS + QMC_NB_RXBDS)) + QMC_NB_TXBDS;
+
+       chan->txbd_free = chan->txbds;
+       chan->txbd_done = chan->txbds;
+       chan->rxbd_free = chan->rxbds;
+       chan->rxbd_done = chan->rxbds;
+
+       /* TBASE and TBPTR*/
+       val = chan->id * (QMC_NB_TXBDS + QMC_NB_RXBDS) * sizeof(cbd_t);
+       qmc_write16(chan->s_param + QMC_SPE_TBASE, val);
+       qmc_write16(chan->s_param + QMC_SPE_TBPTR, val);
+
+       /* RBASE and RBPTR*/
+       val = ((chan->id * (QMC_NB_TXBDS + QMC_NB_RXBDS)) + QMC_NB_TXBDS) * sizeof(cbd_t);
+       qmc_write16(chan->s_param + QMC_SPE_RBASE, val);
+       qmc_write16(chan->s_param + QMC_SPE_RBPTR, val);
+       qmc_write32(chan->s_param + QMC_SPE_TSTATE, 0x30000000);
+       qmc_write32(chan->s_param + QMC_SPE_RSTATE, 0x31000000);
+       qmc_write32(chan->s_param + QMC_SPE_ZISTATE, 0x00000100);
+       if (chan->mode == QMC_TRANSPARENT) {
+               qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x18000080);
+               qmc_write16(chan->s_param + QMC_SPE_TMRBLR, 60);
+               val = QMC_SPE_CHAMR_MODE_TRANSP | QMC_SPE_CHAMR_TRANSP_SYNC;
+               if (chan->is_reverse_data)
+                       val |= QMC_SPE_CHAMR_TRANSP_RD;
+               qmc_write16(chan->s_param + QMC_SPE_CHAMR, val);
+               ret = qmc_setup_chan_trnsync(qmc, chan);
+               if (ret)
+                       return ret;
+       } else {
+               qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x00000080);
+               qmc_write16(chan->s_param + QMC_SPE_MFLR, 60);
+               qmc_write16(chan->s_param + QMC_SPE_CHAMR,
+                       QMC_SPE_CHAMR_MODE_HDLC | QMC_SPE_CHAMR_HDLC_IDLM);
+       }
+
+       /* Do not enable interrupts now. They will be enabled later */
+       qmc_write16(chan->s_param + QMC_SPE_INTMSK, 0x0000);
+
+       /* Init Rx BDs and set Wrap bit on last descriptor */
+       BUILD_BUG_ON(QMC_NB_RXBDS == 0);
+       val = QMC_BD_RX_I;
+       for (i = 0; i < QMC_NB_RXBDS; i++) {
+               bd = chan->rxbds + i;
+               qmc_write16(&bd->cbd_sc, val);
+       }
+       bd = chan->rxbds + QMC_NB_RXBDS - 1;
+       qmc_write16(&bd->cbd_sc, val | QMC_BD_RX_W);
+
+       /* Init Tx BDs and set Wrap bit on last descriptor */
+       BUILD_BUG_ON(QMC_NB_TXBDS == 0);
+       val = QMC_BD_TX_I;
+       if (chan->mode == QMC_HDLC)
+               val |= QMC_BD_TX_L | QMC_BD_TX_TC;
+       for (i = 0; i < QMC_NB_TXBDS; i++) {
+               bd = chan->txbds + i;
+               qmc_write16(&bd->cbd_sc, val);
+       }
+       bd = chan->txbds + QMC_NB_TXBDS - 1;
+       qmc_write16(&bd->cbd_sc, val | QMC_BD_TX_W);
+
+       return 0;
+}
+
+static int qmc_setup_chans(struct qmc *qmc)
+{
+       struct qmc_chan *chan;
+       int ret;
+
+       list_for_each_entry(chan, &qmc->chan_head, list) {
+               ret = qmc_setup_chan(qmc, chan);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static int qmc_finalize_chans(struct qmc *qmc)
+{
+       struct qmc_chan *chan;
+       int ret;
+
+       list_for_each_entry(chan, &qmc->chan_head, list) {
+               /* Unmask channel interrupts */
+               if (chan->mode == QMC_HDLC) {
+                       qmc_write16(chan->s_param + QMC_SPE_INTMSK,
+                                   QMC_INT_NID | QMC_INT_IDL | QMC_INT_MRF |
+                                   QMC_INT_UN | QMC_INT_RXF | QMC_INT_BSY |
+                                   QMC_INT_TXB | QMC_INT_RXB);
+               } else {
+                       qmc_write16(chan->s_param + QMC_SPE_INTMSK,
+                                   QMC_INT_UN | QMC_INT_BSY |
+                                   QMC_INT_TXB | QMC_INT_RXB);
+               }
+
+               /* Forced stop the channel */
+               ret = qmc_chan_stop(chan, QMC_CHAN_ALL);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static int qmc_setup_ints(struct qmc *qmc)
+{
+       unsigned int i;
+       u16 __iomem *last;
+
+       /* Raz all entries */
+       for (i = 0; i < (qmc->int_size / sizeof(u16)); i++)
+               qmc_write16(qmc->int_table + i, 0x0000);
+
+       /* Set Wrap bit on last entry */
+       if (qmc->int_size >= sizeof(u16)) {
+               last = qmc->int_table + (qmc->int_size / sizeof(u16)) - 1;
+               qmc_write16(last, QMC_INT_W);
+       }
+
+       return 0;
+}
+
+static void qmc_irq_gint(struct qmc *qmc)
+{
+       struct qmc_chan *chan;
+       unsigned int chan_id;
+       unsigned long flags;
+       u16 int_entry;
+
+       int_entry = qmc_read16(qmc->int_curr);
+       while (int_entry & QMC_INT_V) {
+               /* Clear all but the Wrap bit */
+               qmc_write16(qmc->int_curr, int_entry & QMC_INT_W);
+
+               chan_id = QMC_INT_GET_CHANNEL(int_entry);
+               chan = qmc->chans[chan_id];
+               if (!chan) {
+                       dev_err(qmc->dev, "interrupt on invalid chan %u\n", chan_id);
+                       goto int_next;
+               }
+
+               if (int_entry & QMC_INT_TXB)
+                       qmc_chan_write_done(chan);
+
+               if (int_entry & QMC_INT_UN) {
+                       dev_info(qmc->dev, "intr chan %u, 0x%04x (UN)\n", chan_id,
+                                int_entry);
+                       chan->nb_tx_underrun++;
+               }
+
+               if (int_entry & QMC_INT_BSY) {
+                       dev_info(qmc->dev, "intr chan %u, 0x%04x (BSY)\n", chan_id,
+                                int_entry);
+                       chan->nb_rx_busy++;
+                       /* Restart the receiver if needed */
+                       spin_lock_irqsave(&chan->rx_lock, flags);
+                       if (chan->rx_pending && !chan->is_rx_stopped) {
+                               if (chan->mode == QMC_TRANSPARENT)
+                                       qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x18000080);
+                               else
+                                       qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x00000080);
+                               qmc_write32(chan->s_param + QMC_SPE_RSTATE, 0x31000000);
+                               chan->is_rx_halted = false;
+                       } else {
+                               chan->is_rx_halted = true;
+                       }
+                       spin_unlock_irqrestore(&chan->rx_lock, flags);
+               }
+
+               if (int_entry & QMC_INT_RXB)
+                       qmc_chan_read_done(chan);
+
+int_next:
+               if (int_entry & QMC_INT_W)
+                       qmc->int_curr = qmc->int_table;
+               else
+                       qmc->int_curr++;
+               int_entry = qmc_read16(qmc->int_curr);
+       }
+}
+
+static irqreturn_t qmc_irq_handler(int irq, void *priv)
+{
+       struct qmc *qmc = (struct qmc *)priv;
+       u16 scce;
+
+       scce = qmc_read16(qmc->scc_regs + SCC_SCCE);
+       qmc_write16(qmc->scc_regs + SCC_SCCE, scce);
+
+       if (unlikely(scce & SCC_SCCE_IQOV))
+               dev_info(qmc->dev, "IRQ queue overflow\n");
+
+       if (unlikely(scce & SCC_SCCE_GUN))
+               dev_err(qmc->dev, "Global transmitter underrun\n");
+
+       if (unlikely(scce & SCC_SCCE_GOV))
+               dev_err(qmc->dev, "Global receiver overrun\n");
+
+       /* normal interrupt */
+       if (likely(scce & SCC_SCCE_GINT))
+               qmc_irq_gint(qmc);
+
+       return IRQ_HANDLED;
+}
+
+static int qmc_probe(struct platform_device *pdev)
+{
+       struct device_node *np = pdev->dev.of_node;
+       unsigned int nb_chans;
+       struct resource *res;
+       struct qmc *qmc;
+       int irq;
+       int ret;
+
+       qmc = devm_kzalloc(&pdev->dev, sizeof(*qmc), GFP_KERNEL);
+       if (!qmc)
+               return -ENOMEM;
+
+       qmc->dev = &pdev->dev;
+       INIT_LIST_HEAD(&qmc->chan_head);
+
+       qmc->scc_regs = devm_platform_ioremap_resource_byname(pdev, "scc_regs");
+       if (IS_ERR(qmc->scc_regs))
+               return PTR_ERR(qmc->scc_regs);
+
+
+       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "scc_pram");
+       if (!res)
+               return -EINVAL;
+       qmc->scc_pram_offset = res->start - get_immrbase();
+       qmc->scc_pram = devm_ioremap_resource(qmc->dev, res);
+       if (IS_ERR(qmc->scc_pram))
+               return PTR_ERR(qmc->scc_pram);
+
+       qmc->dpram  = devm_platform_ioremap_resource_byname(pdev, "dpram");
+       if (IS_ERR(qmc->dpram))
+               return PTR_ERR(qmc->dpram);
+
+       qmc->tsa_serial = devm_tsa_serial_get_byphandle(qmc->dev, np, "fsl,tsa-serial");
+       if (IS_ERR(qmc->tsa_serial)) {
+               return dev_err_probe(qmc->dev, PTR_ERR(qmc->tsa_serial),
+                                    "Failed to get TSA serial\n");
+       }
+
+       /* Connect the serial (SCC) to TSA */
+       ret = tsa_serial_connect(qmc->tsa_serial);
+       if (ret) {
+               dev_err(qmc->dev, "Failed to connect TSA serial\n");
+               return ret;
+       }
+
+       /* Parse channels informationss */
+       ret = qmc_of_parse_chans(qmc, np);
+       if (ret)
+               goto err_tsa_serial_disconnect;
+
+       nb_chans = qmc_nb_chans(qmc);
+
+       /* Init GMSR_H and GMSR_L registers */
+       qmc_write32(qmc->scc_regs + SCC_GSMRH,
+                   SCC_GSMRH_CDS | SCC_GSMRH_CTSS | SCC_GSMRH_CDP | SCC_GSMRH_CTSP);
+
+       /* enable QMC mode */
+       qmc_write32(qmc->scc_regs + SCC_GSMRL, SCC_GSMRL_MODE_QMC);
+
+       /*
+        * Allocate the buffer descriptor table
+        * 8 rx and 8 tx descriptors per channel
+        */
+       qmc->bd_size = (nb_chans * (QMC_NB_TXBDS + QMC_NB_RXBDS)) * sizeof(cbd_t);
+       qmc->bd_table = dmam_alloc_coherent(qmc->dev, qmc->bd_size,
+               &qmc->bd_dma_addr, GFP_KERNEL);
+       if (!qmc->bd_table) {
+               dev_err(qmc->dev, "Failed to allocate bd table\n");
+               ret = -ENOMEM;
+               goto err_tsa_serial_disconnect;
+       }
+       memset(qmc->bd_table, 0, qmc->bd_size);
+
+       qmc_write32(qmc->scc_pram + QMC_GBL_MCBASE, qmc->bd_dma_addr);
+
+       /* Allocate the interrupt table */
+       qmc->int_size = QMC_NB_INTS * sizeof(u16);
+       qmc->int_table = dmam_alloc_coherent(qmc->dev, qmc->int_size,
+               &qmc->int_dma_addr, GFP_KERNEL);
+       if (!qmc->int_table) {
+               dev_err(qmc->dev, "Failed to allocate interrupt table\n");
+               ret = -ENOMEM;
+               goto err_tsa_serial_disconnect;
+       }
+       memset(qmc->int_table, 0, qmc->int_size);
+
+       qmc->int_curr = qmc->int_table;
+       qmc_write32(qmc->scc_pram + QMC_GBL_INTBASE, qmc->int_dma_addr);
+       qmc_write32(qmc->scc_pram + QMC_GBL_INTPTR, qmc->int_dma_addr);
+
+       /* Set MRBLR (valid for HDLC only) max MRU + max CRC */
+       qmc_write16(qmc->scc_pram + QMC_GBL_MRBLR, HDLC_MAX_MRU + 4);
+
+       qmc_write16(qmc->scc_pram + QMC_GBL_GRFTHR, 1);
+       qmc_write16(qmc->scc_pram + QMC_GBL_GRFCNT, 1);
+
+       qmc_write32(qmc->scc_pram + QMC_GBL_C_MASK32, 0xDEBB20E3);
+       qmc_write16(qmc->scc_pram + QMC_GBL_C_MASK16, 0xF0B8);
+
+       ret = qmc_setup_tsa(qmc);
+       if (ret)
+               goto err_tsa_serial_disconnect;
+
+       qmc_write16(qmc->scc_pram + QMC_GBL_QMCSTATE, 0x8000);
+
+       ret = qmc_setup_chans(qmc);
+       if (ret)
+               goto err_tsa_serial_disconnect;
+
+       /* Init interrupts table */
+       ret = qmc_setup_ints(qmc);
+       if (ret)
+               goto err_tsa_serial_disconnect;
+
+       /* Disable and clear interrupts,  set the irq handler */
+       qmc_write16(qmc->scc_regs + SCC_SCCM, 0x0000);
+       qmc_write16(qmc->scc_regs + SCC_SCCE, 0x000F);
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0)
+               goto err_tsa_serial_disconnect;
+       ret = devm_request_irq(qmc->dev, irq, qmc_irq_handler, 0, "qmc", qmc);
+       if (ret < 0)
+               goto err_tsa_serial_disconnect;
+
+       /* Enable interrupts */
+       qmc_write16(qmc->scc_regs + SCC_SCCM,
+               SCC_SCCE_IQOV | SCC_SCCE_GINT | SCC_SCCE_GUN | SCC_SCCE_GOV);
+
+       ret = qmc_finalize_chans(qmc);
+       if (ret < 0)
+               goto err_disable_intr;
+
+       /* Enable transmiter and receiver */
+       qmc_setbits32(qmc->scc_regs + SCC_GSMRL, SCC_GSMRL_ENR | SCC_GSMRL_ENT);
+
+       platform_set_drvdata(pdev, qmc);
+
+       return 0;
+
+err_disable_intr:
+       qmc_write16(qmc->scc_regs + SCC_SCCM, 0);
+
+err_tsa_serial_disconnect:
+       tsa_serial_disconnect(qmc->tsa_serial);
+       return ret;
+}
+
+static int qmc_remove(struct platform_device *pdev)
+{
+       struct qmc *qmc = platform_get_drvdata(pdev);
+
+       /* Disable transmiter and receiver */
+       qmc_setbits32(qmc->scc_regs + SCC_GSMRL, 0);
+
+       /* Disable interrupts */
+       qmc_write16(qmc->scc_regs + SCC_SCCM, 0);
+
+       /* Disconnect the serial from TSA */
+       tsa_serial_disconnect(qmc->tsa_serial);
+
+       return 0;
+}
+
+static const struct of_device_id qmc_id_table[] = {
+       { .compatible = "fsl,cpm1-scc-qmc" },
+       {} /* sentinel */
+};
+MODULE_DEVICE_TABLE(of, qmc_id_table);
+
+static struct platform_driver qmc_driver = {
+       .driver = {
+               .name = "fsl-qmc",
+               .of_match_table = of_match_ptr(qmc_id_table),
+       },
+       .probe = qmc_probe,
+       .remove = qmc_remove,
+};
+module_platform_driver(qmc_driver);
+
+struct qmc_chan *qmc_chan_get_byphandle(struct device_node *np, const char *phandle_name)
+{
+       struct of_phandle_args out_args;
+       struct platform_device *pdev;
+       struct qmc_chan *qmc_chan;
+       struct qmc *qmc;
+       int ret;
+
+       ret = of_parse_phandle_with_fixed_args(np, phandle_name, 1, 0,
+                                              &out_args);
+       if (ret < 0)
+               return ERR_PTR(ret);
+
+       if (!of_match_node(qmc_driver.driver.of_match_table, out_args.np)) {
+               of_node_put(out_args.np);
+               return ERR_PTR(-EINVAL);
+       }
+
+       pdev = of_find_device_by_node(out_args.np);
+       of_node_put(out_args.np);
+       if (!pdev)
+               return ERR_PTR(-ENODEV);
+
+       qmc = platform_get_drvdata(pdev);
+       if (!qmc) {
+               platform_device_put(pdev);
+               return ERR_PTR(-EPROBE_DEFER);
+       }
+
+       if (out_args.args_count != 1) {
+               platform_device_put(pdev);
+               return ERR_PTR(-EINVAL);
+       }
+
+       if (out_args.args[0] >= ARRAY_SIZE(qmc->chans)) {
+               platform_device_put(pdev);
+               return ERR_PTR(-EINVAL);
+       }
+
+       qmc_chan = qmc->chans[out_args.args[0]];
+       if (!qmc_chan) {
+               platform_device_put(pdev);
+               return ERR_PTR(-ENOENT);
+       }
+
+       return qmc_chan;
+}
+EXPORT_SYMBOL(qmc_chan_get_byphandle);
+
+void qmc_chan_put(struct qmc_chan *chan)
+{
+       put_device(chan->qmc->dev);
+}
+EXPORT_SYMBOL(qmc_chan_put);
+
+static void devm_qmc_chan_release(struct device *dev, void *res)
+{
+       struct qmc_chan **qmc_chan = res;
+
+       qmc_chan_put(*qmc_chan);
+}
+
+struct qmc_chan *devm_qmc_chan_get_byphandle(struct device *dev,
+                                            struct device_node *np,
+                                            const char *phandle_name)
+{
+       struct qmc_chan *qmc_chan;
+       struct qmc_chan **dr;
+
+       dr = devres_alloc(devm_qmc_chan_release, sizeof(*dr), GFP_KERNEL);
+       if (!dr)
+               return ERR_PTR(-ENOMEM);
+
+       qmc_chan = qmc_chan_get_byphandle(np, phandle_name);
+       if (!IS_ERR(qmc_chan)) {
+               *dr = qmc_chan;
+               devres_add(dev, dr);
+       } else {
+               devres_free(dr);
+       }
+
+       return qmc_chan;
+}
+EXPORT_SYMBOL(devm_qmc_chan_get_byphandle);
+
+MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
+MODULE_DESCRIPTION("CPM QMC driver");
+MODULE_LICENSE("GPL");