diff mbox series

[v2,34/36] soc: fsl: cpm1: qmc: Add support for QUICC Engine (QE) implementation

Message ID 20240808071132.149251-35-herve.codina@bootlin.com (mailing list archive)
State New, archived
Headers show
Series soc: fsl: Add support for QUICC Engine TSA and QMC | expand

Commit Message

Herve Codina Aug. 8, 2024, 7:11 a.m. UTC
Add support for the QMC (QUICC Multichannel Controller) available in
some PowerQUICC SoC that uses a QUICC Engine (QE) block such as MPC8321.

This QE QMC is similar to the CPM QMC except that it uses UCCs (Unified
Communication Controllers) instead of SCCs (Serial Communication
Controllers). Also, compared against the CPM QMC, this QE QMC does not
use a fixed area for the UCC/SCC parameters area but it uses a dynamic
area allocated and provided to the hardware at runtime.

Signed-off-by: Herve Codina <herve.codina@bootlin.com>
---
 drivers/soc/fsl/qe/Kconfig |   9 +-
 drivers/soc/fsl/qe/qmc.c   | 209 +++++++++++++++++++++++++++++++++++--
 2 files changed, 204 insertions(+), 14 deletions(-)

Comments

Christophe Leroy Aug. 23, 2024, 8:14 a.m. UTC | #1
Le 08/08/2024 à 09:11, Herve Codina a écrit :
> Add support for the QMC (QUICC Multichannel Controller) available in
> some PowerQUICC SoC that uses a QUICC Engine (QE) block such as MPC8321.
> 
> This QE QMC is similar to the CPM QMC except that it uses UCCs (Unified
> Communication Controllers) instead of SCCs (Serial Communication
> Controllers). Also, compared against the CPM QMC, this QE QMC does not
> use a fixed area for the UCC/SCC parameters area but it uses a dynamic
> area allocated and provided to the hardware at runtime.
> 
> Signed-off-by: Herve Codina <herve.codina@bootlin.com>

Reviewed-by: Christophe Leroy <christophe.leroy@csgroup.eu>

> ---
>   drivers/soc/fsl/qe/Kconfig |   9 +-
>   drivers/soc/fsl/qe/qmc.c   | 209 +++++++++++++++++++++++++++++++++++--
>   2 files changed, 204 insertions(+), 14 deletions(-)
> 
> diff --git a/drivers/soc/fsl/qe/Kconfig b/drivers/soc/fsl/qe/Kconfig
> index 734744874730..5e3c996eb19e 100644
> --- a/drivers/soc/fsl/qe/Kconfig
> +++ b/drivers/soc/fsl/qe/Kconfig
> @@ -17,7 +17,7 @@ config QUICC_ENGINE
>   
>   config UCC_SLOW
>   	bool
> -	default y if SERIAL_QE
> +	default y if SERIAL_QE || (CPM_QMC && QUICC_ENGINE)
>   	help
>   	  This option provides qe_lib support to UCC slow
>   	  protocols: UART, BISYNC, QMC
> @@ -46,12 +46,13 @@ config CPM_TSA
>   	  controller
>   
>   config CPM_QMC
> -	tristate "CPM QMC support"
> +	tristate "CPM/QE QMC support"
>   	depends on OF && HAS_IOMEM
> -	depends on CPM1 || (FSL_SOC && CPM && COMPILE_TEST)
> +	depends on CPM1 || QUICC_ENGINE || \
> +		   (FSL_SOC && (CPM || QUICC_ENGINE) && COMPILE_TEST)
>   	depends on CPM_TSA
>   	help
> -	  Freescale CPM QUICC Multichannel Controller
> +	  Freescale CPM/QE QUICC Multichannel Controller
>   	  (QMC)
>   
>   	  This option enables support for this
> diff --git a/drivers/soc/fsl/qe/qmc.c b/drivers/soc/fsl/qe/qmc.c
> index 8ff7eaaa4c50..b3a9534441ee 100644
> --- a/drivers/soc/fsl/qe/qmc.c
> +++ b/drivers/soc/fsl/qe/qmc.c
> @@ -19,24 +19,29 @@
>   #include <linux/platform_device.h>
>   #include <linux/slab.h>
>   #include <soc/fsl/cpm.h>
> +#include <soc/fsl/qe/ucc_slow.h>
> +#include <soc/fsl/qe/qe.h>
>   #include <sysdev/fsl_soc.h>
>   #include "tsa.h"
>   
> -/* SCC general mode register high (32 bits) */
> +/* SCC general mode register low (32 bits) (GUMR_L in QE) */
>   #define SCC_GSMRL	0x00
>   #define SCC_GSMRL_ENR		BIT(5)
>   #define SCC_GSMRL_ENT		BIT(4)
>   #define SCC_GSMRL_MODE_MASK	GENMASK(3, 0)
>   #define SCC_CPM1_GSMRL_MODE_QMC	FIELD_PREP_CONST(SCC_GSMRL_MODE_MASK, 0x0A)
> +#define SCC_QE_GSMRL_MODE_QMC	FIELD_PREP_CONST(SCC_GSMRL_MODE_MASK, 0x02)
>   
> -/* SCC general mode register low (32 bits) */
> +/* SCC general mode register high (32 bits) (identical to GUMR_H in QE) */
>   #define SCC_GSMRH	0x04
>   #define   SCC_GSMRH_CTSS	BIT(7)
>   #define   SCC_GSMRH_CDS		BIT(8)
>   #define   SCC_GSMRH_CTSP	BIT(9)
>   #define   SCC_GSMRH_CDP		BIT(10)
> +#define   SCC_GSMRH_TTX		BIT(11)
> +#define   SCC_GSMRH_TRX		BIT(12)
>   
> -/* SCC event register (16 bits) */
> +/* SCC event register (16 bits) (identical to UCCE in QE) */
>   #define SCC_SCCE	0x10
>   #define   SCC_SCCE_IQOV		BIT(3)
>   #define   SCC_SCCE_GINT		BIT(2)
> @@ -45,6 +50,10 @@
>   
>   /* SCC mask register (16 bits) */
>   #define SCC_SCCM	0x14
> +
> +/* UCC Extended Mode Register (8 bits, QE only) */
> +#define SCC_QE_UCC_GUEMR	0x90
> +
>   /* Multichannel base pointer (32 bits) */
>   #define QMC_GBL_MCBASE		0x00
>   /* Multichannel controller state (16 bits) */
> @@ -75,6 +84,15 @@
>   #define QMC_GBL_TSATTX		0x60
>   /* CRC constant (16 bits) */
>   #define QMC_GBL_C_MASK16	0xA0
> +/* Rx framer base pointer (16 bits, QE only) */
> +#define QMC_QE_GBL_RX_FRM_BASE	0xAC
> +/* Tx framer base pointer (16 bits, QE only) */
> +#define QMC_QE_GBL_TX_FRM_BASE	0xAE
> +/* A reserved area (0xB0 -> 0xC3) that must be initialized to 0 (QE only) */
> +#define QMC_QE_GBL_RSV_B0_START	0xB0
> +#define QMC_QE_GBL_RSV_B0_SIZE	0x14
> +/* QMC Global Channel specific base (32 bits, QE only) */
> +#define QMC_QE_GBL_GCSBASE	0xC4
>   
>   /* TSA entry (16bit entry in TSATRX and TSATTX) */
>   #define QMC_TSA_VALID		BIT(15)
> @@ -217,6 +235,7 @@ struct qmc_chan {
>   
>   enum qmc_version {
>   	QMC_CPM1,
> +	QMC_QE,
>   };
>   
>   struct qmc_data {
> @@ -237,6 +256,8 @@ struct qmc {
>   	void __iomem *scc_pram;
>   	void __iomem *dpram;
>   	u16 scc_pram_offset;
> +	u32 dpram_offset;
> +	u32 qe_subblock;
>   	cbd_t __iomem *bd_table;
>   	dma_addr_t bd_dma_addr;
>   	size_t bd_size;
> @@ -249,6 +270,11 @@ struct qmc {
>   	struct qmc_chan *chans[64];
>   };
>   
> +static void qmc_write8(void __iomem *addr, u8 val)
> +{
> +	iowrite8(val, addr);
> +}
> +
>   static void qmc_write16(void __iomem *addr, u16 val)
>   {
>   	iowrite16be(val, addr);
> @@ -289,6 +315,14 @@ static void qmc_setbits32(void __iomem *addr, u32 set)
>   	qmc_write32(addr, qmc_read32(addr) | set);
>   }
>   
> +static bool qmc_is_qe(const struct qmc *qmc)
> +{
> +	if (IS_ENABLED(CONFIG_QUICC_ENGINE) && IS_ENABLED(CONFIG_CPM))
> +		return qmc->data->version == QMC_QE;
> +
> +	return IS_ENABLED(CONFIG_QUICC_ENGINE);
> +}
> +
>   int qmc_chan_get_info(struct qmc_chan *chan, struct qmc_chan_info *info)
>   {
>   	struct tsa_serial_info tsa_info;
> @@ -806,6 +840,13 @@ static int qmc_chan_cpm1_command(struct qmc_chan *chan, u8 qmc_opcode)
>   	return cpm_command(chan->id << 2, (qmc_opcode << 4) | 0x0E);
>   }
>   
> +static int qmc_chan_qe_command(struct qmc_chan *chan, u32 cmd)
> +{
> +	if (!qe_issue_cmd(cmd, chan->qmc->qe_subblock, chan->id, 0))
> +		return -EIO;
> +	return 0;
> +}
> +
>   static int qmc_chan_stop_rx(struct qmc_chan *chan)
>   {
>   	unsigned long flags;
> @@ -820,7 +861,9 @@ static int qmc_chan_stop_rx(struct qmc_chan *chan)
>   	}
>   
>   	/* Send STOP RECEIVE command */
> -	ret = qmc_chan_cpm1_command(chan, 0x0);
> +	ret = qmc_is_qe(chan->qmc) ?
> +		qmc_chan_qe_command(chan, QE_QMC_STOP_RX) :
> +		qmc_chan_cpm1_command(chan, 0x0);
>   	if (ret) {
>   		dev_err(chan->qmc->dev, "chan %u: Send STOP RECEIVE failed (%d)\n",
>   			chan->id, ret);
> @@ -857,7 +900,9 @@ static int qmc_chan_stop_tx(struct qmc_chan *chan)
>   	}
>   
>   	/* Send STOP TRANSMIT command */
> -	ret = qmc_chan_cpm1_command(chan, 0x1);
> +	ret = qmc_is_qe(chan->qmc) ?
> +		qmc_chan_qe_command(chan, QE_QMC_STOP_TX) :
> +		qmc_chan_cpm1_command(chan, 0x1);
>   	if (ret) {
>   		dev_err(chan->qmc->dev, "chan %u: Send STOP TRANSMIT failed (%d)\n",
>   			chan->id, ret);
> @@ -1627,9 +1672,62 @@ static int qmc_cpm1_init_resources(struct qmc *qmc, struct platform_device *pdev
>   	return 0;
>   }
>   
> +static int qmc_qe_init_resources(struct qmc *qmc, struct platform_device *pdev)
> +{
> +	struct resource *res;
> +	int ucc_num;
> +	s32 info;
> +
> +	qmc->scc_regs = devm_platform_ioremap_resource_byname(pdev, "ucc_regs");
> +	if (IS_ERR(qmc->scc_regs))
> +		return PTR_ERR(qmc->scc_regs);
> +
> +	ucc_num = tsa_serial_get_num(qmc->tsa_serial);
> +	if (ucc_num < 0)
> +		return dev_err_probe(qmc->dev, ucc_num, "Failed to get UCC num\n");
> +
> +	qmc->qe_subblock = ucc_slow_get_qe_cr_subblock(ucc_num);
> +	if (qmc->qe_subblock == QE_CR_SUBBLOCK_INVALID) {
> +		dev_err(qmc->dev, "Unsupported ucc num %u\n", ucc_num);
> +		return -EINVAL;
> +	}
> +	/* Allocate the 'Global Multichannel Parameters' and the
> +	 * 'Framer parameters' areas. The 'Framer parameters' area
> +	 * is located right after the 'Global Multichannel Parameters'.
> +	 * The 'Framer parameters' need 1 byte per receive and transmit
> +	 * channel. The maximum number of receive or transmit channel
> +	 * is 64. So reserve 2 * 64 bytes for the 'Framer parameters'.
> +	 */
> +	info = devm_qe_muram_alloc(qmc->dev, UCC_SLOW_PRAM_SIZE + 2 * 64,
> +				   ALIGNMENT_OF_UCC_SLOW_PRAM);
> +	if (IS_ERR_VALUE(info)) {
> +		dev_err(qmc->dev, "cannot allocate MURAM for PRAM");
> +		return -ENOMEM;
> +	}
> +	if (!qe_issue_cmd(QE_ASSIGN_PAGE_TO_DEVICE, qmc->qe_subblock,
> +			  QE_CR_PROTOCOL_UNSPECIFIED, info)) {
> +		dev_err(qmc->dev, "QE_ASSIGN_PAGE_TO_DEVICE cmd failed");
> +		return -EIO;
> +	}
> +	qmc->scc_pram = qe_muram_addr(info);
> +	qmc->scc_pram_offset = info;
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dpram");
> +	if (!res)
> +		return -EINVAL;
> +	qmc->dpram_offset = res->start - qe_muram_dma(qe_muram_addr(0));
> +	qmc->dpram = devm_ioremap_resource(qmc->dev, res);
> +	if (IS_ERR(qmc->scc_pram))
> +		return PTR_ERR(qmc->scc_pram);
> +
> +	return 0;
> +}
> +
>   static int qmc_init_resources(struct qmc *qmc, struct platform_device *pdev)
>   {
> -	return qmc_cpm1_init_resources(qmc, pdev);
> +	return qmc_is_qe(qmc) ?
> +		qmc_qe_init_resources(qmc, pdev) :
> +		qmc_cpm1_init_resources(qmc, pdev);
>   }
>   
>   static int qmc_cpm1_init_scc(struct qmc *qmc)
> @@ -1656,9 +1754,69 @@ static int qmc_cpm1_init_scc(struct qmc *qmc)
>   	return 0;
>   }
>   
> +static int qmc_qe_init_ucc(struct qmc *qmc)
> +{
> +	u32 val;
> +	int ret;
> +
> +	/* Set the UCC in slow mode */
> +	qmc_write8(qmc->scc_regs + SCC_QE_UCC_GUEMR,
> +		   UCC_GUEMR_SET_RESERVED3 | UCC_GUEMR_MODE_SLOW_RX | UCC_GUEMR_MODE_SLOW_TX);
> +
> +	/* Connect the serial (UCC) to TSA */
> +	ret = tsa_serial_connect(qmc->tsa_serial);
> +	if (ret)
> +		return dev_err_probe(qmc->dev, ret, "Failed to connect TSA serial\n");
> +
> +	/* Initialize the QMC tx startup addresses */
> +	if (!qe_issue_cmd(QE_PUSHSCHED, qmc->qe_subblock,
> +			  QE_CR_PROTOCOL_UNSPECIFIED, 0x80)) {
> +		dev_err(qmc->dev, "QE_CMD_PUSH_SCHED tx cmd failed");
> +		ret = -EIO;
> +		goto err_tsa_serial_disconnect;
> +	}
> +
> +	/* Initialize the QMC rx startup addresses */
> +	if (!qe_issue_cmd(QE_PUSHSCHED, qmc->qe_subblock | 0x00020000,
> +			  QE_CR_PROTOCOL_UNSPECIFIED, 0x82)) {
> +		dev_err(qmc->dev, "QE_CMD_PUSH_SCHED rx cmd failed");
> +		ret = -EIO;
> +		goto err_tsa_serial_disconnect;
> +	}
> +
> +	/* Re-init RXPTR and TXPTR with the content of RX_S_PTR and
> +	 * TX_S_PTR (RX_S_PTR and TX_S_PTR are initialized during
> +	 * qmc_setup_tsa() call
> +	 */
> +	val = qmc_read16(qmc->scc_pram + QMC_GBL_RX_S_PTR);
> +	qmc_write16(qmc->scc_pram + QMC_GBL_RXPTR, val);
> +	val = qmc_read16(qmc->scc_pram + QMC_GBL_TX_S_PTR);
> +	qmc_write16(qmc->scc_pram + QMC_GBL_TXPTR, val);
> +
> +	/* Init GUMR_H and GUMR_L registers (SCC GSMR_H and GSMR_L) */
> +	val = SCC_GSMRH_CDS | SCC_GSMRH_CTSS | SCC_GSMRH_CDP | SCC_GSMRH_CTSP |
> +	      SCC_GSMRH_TRX | SCC_GSMRH_TTX;
> +	qmc_write32(qmc->scc_regs + SCC_GSMRH, val);
> +
> +	/* enable QMC mode */
> +	qmc_write32(qmc->scc_regs + SCC_GSMRL, SCC_QE_GSMRL_MODE_QMC);
> +
> +	/* Disable and clear interrupts */
> +	qmc_write16(qmc->scc_regs + SCC_SCCM, 0x0000);
> +	qmc_write16(qmc->scc_regs + SCC_SCCE, 0x000F);
> +
> +	return 0;
> +
> +err_tsa_serial_disconnect:
> +	tsa_serial_disconnect(qmc->tsa_serial);
> +	return ret;
> +}
> +
>   static int qmc_init_xcc(struct qmc *qmc)
>   {
> -	return qmc_cpm1_init_scc(qmc);
> +	return qmc_is_qe(qmc) ?
> +		qmc_qe_init_ucc(qmc) :
> +		qmc_cpm1_init_scc(qmc);
>   }
>   
>   static void qmc_exit_xcc(struct qmc *qmc)
> @@ -1742,6 +1900,22 @@ static int qmc_probe(struct platform_device *pdev)
>   	qmc_write32(qmc->scc_pram + QMC_GBL_C_MASK32, 0xDEBB20E3);
>   	qmc_write16(qmc->scc_pram + QMC_GBL_C_MASK16, 0xF0B8);
>   
> +	if (qmc_is_qe(qmc)) {
> +		/* Zeroed the reserved area */
> +		memset_io(qmc->scc_pram + QMC_QE_GBL_RSV_B0_START, 0,
> +			  QMC_QE_GBL_RSV_B0_SIZE);
> +
> +		qmc_write32(qmc->scc_pram + QMC_QE_GBL_GCSBASE, qmc->dpram_offset);
> +
> +		/* Init 'framer parameters' area and set the base addresses */
> +		memset_io(qmc->scc_pram + UCC_SLOW_PRAM_SIZE, 0x01, 64);
> +		memset_io(qmc->scc_pram + UCC_SLOW_PRAM_SIZE + 64, 0x01, 64);
> +		qmc_write16(qmc->scc_pram + QMC_QE_GBL_RX_FRM_BASE,
> +			    qmc->scc_pram_offset + UCC_SLOW_PRAM_SIZE);
> +		qmc_write16(qmc->scc_pram + QMC_QE_GBL_TX_FRM_BASE,
> +			    qmc->scc_pram_offset + UCC_SLOW_PRAM_SIZE + 64);
> +	}
> +
>   	ret = qmc_init_tsa(qmc);
>   	if (ret)
>   		return ret;
> @@ -1757,7 +1931,7 @@ static int qmc_probe(struct platform_device *pdev)
>   	if (ret)
>   		return ret;
>   
> -	/* Init SCC */
> +	/* Init SCC (CPM1) or UCC (QE) */
>   	ret = qmc_init_xcc(qmc);
>   	if (ret)
>   		return ret;
> @@ -1811,7 +1985,7 @@ static void qmc_remove(struct platform_device *pdev)
>   	/* Disable interrupts */
>   	qmc_write16(qmc->scc_regs + SCC_SCCM, 0);
>   
> -	/* Exit SCC */
> +	/* Exit SCC (CPM1) or UCC (QE) */
>   	qmc_exit_xcc(qmc);
>   }
>   
> @@ -1825,8 +1999,23 @@ static const struct qmc_data qmc_data_cpm1 = {
>   	.rpack = 0x00000000,
>   };
>   
> +static const struct qmc_data qmc_data_qe = {
> +	.version = QMC_QE,
> +	.tstate = 0x30000000,
> +	.rstate = 0x30000000,
> +	.zistate = 0x00000200,
> +	.zdstate_hdlc = 0x80FFFFE0,
> +	.zdstate_transp = 0x003FFFE2,
> +	.rpack = 0x80000000,
> +};
> +
>   static const struct of_device_id qmc_id_table[] = {
> +#if IS_ENABLED(CONFIG_CPM1)
>   	{ .compatible = "fsl,cpm1-scc-qmc", .data = &qmc_data_cpm1 },
> +#endif
> +#if IS_ENABLED(CONFIG_QUICC_ENGINE)
> +	{ .compatible = "fsl,qe-ucc-qmc", .data = &qmc_data_qe },
> +#endif
>   	{} /* sentinel */
>   };
>   MODULE_DEVICE_TABLE(of, qmc_id_table);
> @@ -1986,5 +2175,5 @@ struct qmc_chan *devm_qmc_chan_get_bychild(struct device *dev,
>   EXPORT_SYMBOL(devm_qmc_chan_get_bychild);
>   
>   MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
> -MODULE_DESCRIPTION("CPM QMC driver");
> +MODULE_DESCRIPTION("CPM/QE QMC driver");
>   MODULE_LICENSE("GPL");
diff mbox series

Patch

diff --git a/drivers/soc/fsl/qe/Kconfig b/drivers/soc/fsl/qe/Kconfig
index 734744874730..5e3c996eb19e 100644
--- a/drivers/soc/fsl/qe/Kconfig
+++ b/drivers/soc/fsl/qe/Kconfig
@@ -17,7 +17,7 @@  config QUICC_ENGINE
 
 config UCC_SLOW
 	bool
-	default y if SERIAL_QE
+	default y if SERIAL_QE || (CPM_QMC && QUICC_ENGINE)
 	help
 	  This option provides qe_lib support to UCC slow
 	  protocols: UART, BISYNC, QMC
@@ -46,12 +46,13 @@  config CPM_TSA
 	  controller
 
 config CPM_QMC
-	tristate "CPM QMC support"
+	tristate "CPM/QE QMC support"
 	depends on OF && HAS_IOMEM
-	depends on CPM1 || (FSL_SOC && CPM && COMPILE_TEST)
+	depends on CPM1 || QUICC_ENGINE || \
+		   (FSL_SOC && (CPM || QUICC_ENGINE) && COMPILE_TEST)
 	depends on CPM_TSA
 	help
-	  Freescale CPM QUICC Multichannel Controller
+	  Freescale CPM/QE QUICC Multichannel Controller
 	  (QMC)
 
 	  This option enables support for this
diff --git a/drivers/soc/fsl/qe/qmc.c b/drivers/soc/fsl/qe/qmc.c
index 8ff7eaaa4c50..b3a9534441ee 100644
--- a/drivers/soc/fsl/qe/qmc.c
+++ b/drivers/soc/fsl/qe/qmc.c
@@ -19,24 +19,29 @@ 
 #include <linux/platform_device.h>
 #include <linux/slab.h>
 #include <soc/fsl/cpm.h>
+#include <soc/fsl/qe/ucc_slow.h>
+#include <soc/fsl/qe/qe.h>
 #include <sysdev/fsl_soc.h>
 #include "tsa.h"
 
-/* SCC general mode register high (32 bits) */
+/* SCC general mode register low (32 bits) (GUMR_L in QE) */
 #define SCC_GSMRL	0x00
 #define SCC_GSMRL_ENR		BIT(5)
 #define SCC_GSMRL_ENT		BIT(4)
 #define SCC_GSMRL_MODE_MASK	GENMASK(3, 0)
 #define SCC_CPM1_GSMRL_MODE_QMC	FIELD_PREP_CONST(SCC_GSMRL_MODE_MASK, 0x0A)
+#define SCC_QE_GSMRL_MODE_QMC	FIELD_PREP_CONST(SCC_GSMRL_MODE_MASK, 0x02)
 
-/* SCC general mode register low (32 bits) */
+/* SCC general mode register high (32 bits) (identical to GUMR_H in QE) */
 #define SCC_GSMRH	0x04
 #define   SCC_GSMRH_CTSS	BIT(7)
 #define   SCC_GSMRH_CDS		BIT(8)
 #define   SCC_GSMRH_CTSP	BIT(9)
 #define   SCC_GSMRH_CDP		BIT(10)
+#define   SCC_GSMRH_TTX		BIT(11)
+#define   SCC_GSMRH_TRX		BIT(12)
 
-/* SCC event register (16 bits) */
+/* SCC event register (16 bits) (identical to UCCE in QE) */
 #define SCC_SCCE	0x10
 #define   SCC_SCCE_IQOV		BIT(3)
 #define   SCC_SCCE_GINT		BIT(2)
@@ -45,6 +50,10 @@ 
 
 /* SCC mask register (16 bits) */
 #define SCC_SCCM	0x14
+
+/* UCC Extended Mode Register (8 bits, QE only) */
+#define SCC_QE_UCC_GUEMR	0x90
+
 /* Multichannel base pointer (32 bits) */
 #define QMC_GBL_MCBASE		0x00
 /* Multichannel controller state (16 bits) */
@@ -75,6 +84,15 @@ 
 #define QMC_GBL_TSATTX		0x60
 /* CRC constant (16 bits) */
 #define QMC_GBL_C_MASK16	0xA0
+/* Rx framer base pointer (16 bits, QE only) */
+#define QMC_QE_GBL_RX_FRM_BASE	0xAC
+/* Tx framer base pointer (16 bits, QE only) */
+#define QMC_QE_GBL_TX_FRM_BASE	0xAE
+/* A reserved area (0xB0 -> 0xC3) that must be initialized to 0 (QE only) */
+#define QMC_QE_GBL_RSV_B0_START	0xB0
+#define QMC_QE_GBL_RSV_B0_SIZE	0x14
+/* QMC Global Channel specific base (32 bits, QE only) */
+#define QMC_QE_GBL_GCSBASE	0xC4
 
 /* TSA entry (16bit entry in TSATRX and TSATTX) */
 #define QMC_TSA_VALID		BIT(15)
@@ -217,6 +235,7 @@  struct qmc_chan {
 
 enum qmc_version {
 	QMC_CPM1,
+	QMC_QE,
 };
 
 struct qmc_data {
@@ -237,6 +256,8 @@  struct qmc {
 	void __iomem *scc_pram;
 	void __iomem *dpram;
 	u16 scc_pram_offset;
+	u32 dpram_offset;
+	u32 qe_subblock;
 	cbd_t __iomem *bd_table;
 	dma_addr_t bd_dma_addr;
 	size_t bd_size;
@@ -249,6 +270,11 @@  struct qmc {
 	struct qmc_chan *chans[64];
 };
 
+static void qmc_write8(void __iomem *addr, u8 val)
+{
+	iowrite8(val, addr);
+}
+
 static void qmc_write16(void __iomem *addr, u16 val)
 {
 	iowrite16be(val, addr);
@@ -289,6 +315,14 @@  static void qmc_setbits32(void __iomem *addr, u32 set)
 	qmc_write32(addr, qmc_read32(addr) | set);
 }
 
+static bool qmc_is_qe(const struct qmc *qmc)
+{
+	if (IS_ENABLED(CONFIG_QUICC_ENGINE) && IS_ENABLED(CONFIG_CPM))
+		return qmc->data->version == QMC_QE;
+
+	return IS_ENABLED(CONFIG_QUICC_ENGINE);
+}
+
 int qmc_chan_get_info(struct qmc_chan *chan, struct qmc_chan_info *info)
 {
 	struct tsa_serial_info tsa_info;
@@ -806,6 +840,13 @@  static int qmc_chan_cpm1_command(struct qmc_chan *chan, u8 qmc_opcode)
 	return cpm_command(chan->id << 2, (qmc_opcode << 4) | 0x0E);
 }
 
+static int qmc_chan_qe_command(struct qmc_chan *chan, u32 cmd)
+{
+	if (!qe_issue_cmd(cmd, chan->qmc->qe_subblock, chan->id, 0))
+		return -EIO;
+	return 0;
+}
+
 static int qmc_chan_stop_rx(struct qmc_chan *chan)
 {
 	unsigned long flags;
@@ -820,7 +861,9 @@  static int qmc_chan_stop_rx(struct qmc_chan *chan)
 	}
 
 	/* Send STOP RECEIVE command */
-	ret = qmc_chan_cpm1_command(chan, 0x0);
+	ret = qmc_is_qe(chan->qmc) ?
+		qmc_chan_qe_command(chan, QE_QMC_STOP_RX) :
+		qmc_chan_cpm1_command(chan, 0x0);
 	if (ret) {
 		dev_err(chan->qmc->dev, "chan %u: Send STOP RECEIVE failed (%d)\n",
 			chan->id, ret);
@@ -857,7 +900,9 @@  static int qmc_chan_stop_tx(struct qmc_chan *chan)
 	}
 
 	/* Send STOP TRANSMIT command */
-	ret = qmc_chan_cpm1_command(chan, 0x1);
+	ret = qmc_is_qe(chan->qmc) ?
+		qmc_chan_qe_command(chan, QE_QMC_STOP_TX) :
+		qmc_chan_cpm1_command(chan, 0x1);
 	if (ret) {
 		dev_err(chan->qmc->dev, "chan %u: Send STOP TRANSMIT failed (%d)\n",
 			chan->id, ret);
@@ -1627,9 +1672,62 @@  static int qmc_cpm1_init_resources(struct qmc *qmc, struct platform_device *pdev
 	return 0;
 }
 
+static int qmc_qe_init_resources(struct qmc *qmc, struct platform_device *pdev)
+{
+	struct resource *res;
+	int ucc_num;
+	s32 info;
+
+	qmc->scc_regs = devm_platform_ioremap_resource_byname(pdev, "ucc_regs");
+	if (IS_ERR(qmc->scc_regs))
+		return PTR_ERR(qmc->scc_regs);
+
+	ucc_num = tsa_serial_get_num(qmc->tsa_serial);
+	if (ucc_num < 0)
+		return dev_err_probe(qmc->dev, ucc_num, "Failed to get UCC num\n");
+
+	qmc->qe_subblock = ucc_slow_get_qe_cr_subblock(ucc_num);
+	if (qmc->qe_subblock == QE_CR_SUBBLOCK_INVALID) {
+		dev_err(qmc->dev, "Unsupported ucc num %u\n", ucc_num);
+		return -EINVAL;
+	}
+	/* Allocate the 'Global Multichannel Parameters' and the
+	 * 'Framer parameters' areas. The 'Framer parameters' area
+	 * is located right after the 'Global Multichannel Parameters'.
+	 * The 'Framer parameters' need 1 byte per receive and transmit
+	 * channel. The maximum number of receive or transmit channel
+	 * is 64. So reserve 2 * 64 bytes for the 'Framer parameters'.
+	 */
+	info = devm_qe_muram_alloc(qmc->dev, UCC_SLOW_PRAM_SIZE + 2 * 64,
+				   ALIGNMENT_OF_UCC_SLOW_PRAM);
+	if (IS_ERR_VALUE(info)) {
+		dev_err(qmc->dev, "cannot allocate MURAM for PRAM");
+		return -ENOMEM;
+	}
+	if (!qe_issue_cmd(QE_ASSIGN_PAGE_TO_DEVICE, qmc->qe_subblock,
+			  QE_CR_PROTOCOL_UNSPECIFIED, info)) {
+		dev_err(qmc->dev, "QE_ASSIGN_PAGE_TO_DEVICE cmd failed");
+		return -EIO;
+	}
+	qmc->scc_pram = qe_muram_addr(info);
+	qmc->scc_pram_offset = info;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dpram");
+	if (!res)
+		return -EINVAL;
+	qmc->dpram_offset = res->start - qe_muram_dma(qe_muram_addr(0));
+	qmc->dpram = devm_ioremap_resource(qmc->dev, res);
+	if (IS_ERR(qmc->scc_pram))
+		return PTR_ERR(qmc->scc_pram);
+
+	return 0;
+}
+
 static int qmc_init_resources(struct qmc *qmc, struct platform_device *pdev)
 {
-	return qmc_cpm1_init_resources(qmc, pdev);
+	return qmc_is_qe(qmc) ?
+		qmc_qe_init_resources(qmc, pdev) :
+		qmc_cpm1_init_resources(qmc, pdev);
 }
 
 static int qmc_cpm1_init_scc(struct qmc *qmc)
@@ -1656,9 +1754,69 @@  static int qmc_cpm1_init_scc(struct qmc *qmc)
 	return 0;
 }
 
+static int qmc_qe_init_ucc(struct qmc *qmc)
+{
+	u32 val;
+	int ret;
+
+	/* Set the UCC in slow mode */
+	qmc_write8(qmc->scc_regs + SCC_QE_UCC_GUEMR,
+		   UCC_GUEMR_SET_RESERVED3 | UCC_GUEMR_MODE_SLOW_RX | UCC_GUEMR_MODE_SLOW_TX);
+
+	/* Connect the serial (UCC) to TSA */
+	ret = tsa_serial_connect(qmc->tsa_serial);
+	if (ret)
+		return dev_err_probe(qmc->dev, ret, "Failed to connect TSA serial\n");
+
+	/* Initialize the QMC tx startup addresses */
+	if (!qe_issue_cmd(QE_PUSHSCHED, qmc->qe_subblock,
+			  QE_CR_PROTOCOL_UNSPECIFIED, 0x80)) {
+		dev_err(qmc->dev, "QE_CMD_PUSH_SCHED tx cmd failed");
+		ret = -EIO;
+		goto err_tsa_serial_disconnect;
+	}
+
+	/* Initialize the QMC rx startup addresses */
+	if (!qe_issue_cmd(QE_PUSHSCHED, qmc->qe_subblock | 0x00020000,
+			  QE_CR_PROTOCOL_UNSPECIFIED, 0x82)) {
+		dev_err(qmc->dev, "QE_CMD_PUSH_SCHED rx cmd failed");
+		ret = -EIO;
+		goto err_tsa_serial_disconnect;
+	}
+
+	/* Re-init RXPTR and TXPTR with the content of RX_S_PTR and
+	 * TX_S_PTR (RX_S_PTR and TX_S_PTR are initialized during
+	 * qmc_setup_tsa() call
+	 */
+	val = qmc_read16(qmc->scc_pram + QMC_GBL_RX_S_PTR);
+	qmc_write16(qmc->scc_pram + QMC_GBL_RXPTR, val);
+	val = qmc_read16(qmc->scc_pram + QMC_GBL_TX_S_PTR);
+	qmc_write16(qmc->scc_pram + QMC_GBL_TXPTR, val);
+
+	/* Init GUMR_H and GUMR_L registers (SCC GSMR_H and GSMR_L) */
+	val = SCC_GSMRH_CDS | SCC_GSMRH_CTSS | SCC_GSMRH_CDP | SCC_GSMRH_CTSP |
+	      SCC_GSMRH_TRX | SCC_GSMRH_TTX;
+	qmc_write32(qmc->scc_regs + SCC_GSMRH, val);
+
+	/* enable QMC mode */
+	qmc_write32(qmc->scc_regs + SCC_GSMRL, SCC_QE_GSMRL_MODE_QMC);
+
+	/* Disable and clear interrupts */
+	qmc_write16(qmc->scc_regs + SCC_SCCM, 0x0000);
+	qmc_write16(qmc->scc_regs + SCC_SCCE, 0x000F);
+
+	return 0;
+
+err_tsa_serial_disconnect:
+	tsa_serial_disconnect(qmc->tsa_serial);
+	return ret;
+}
+
 static int qmc_init_xcc(struct qmc *qmc)
 {
-	return qmc_cpm1_init_scc(qmc);
+	return qmc_is_qe(qmc) ?
+		qmc_qe_init_ucc(qmc) :
+		qmc_cpm1_init_scc(qmc);
 }
 
 static void qmc_exit_xcc(struct qmc *qmc)
@@ -1742,6 +1900,22 @@  static int qmc_probe(struct platform_device *pdev)
 	qmc_write32(qmc->scc_pram + QMC_GBL_C_MASK32, 0xDEBB20E3);
 	qmc_write16(qmc->scc_pram + QMC_GBL_C_MASK16, 0xF0B8);
 
+	if (qmc_is_qe(qmc)) {
+		/* Zeroed the reserved area */
+		memset_io(qmc->scc_pram + QMC_QE_GBL_RSV_B0_START, 0,
+			  QMC_QE_GBL_RSV_B0_SIZE);
+
+		qmc_write32(qmc->scc_pram + QMC_QE_GBL_GCSBASE, qmc->dpram_offset);
+
+		/* Init 'framer parameters' area and set the base addresses */
+		memset_io(qmc->scc_pram + UCC_SLOW_PRAM_SIZE, 0x01, 64);
+		memset_io(qmc->scc_pram + UCC_SLOW_PRAM_SIZE + 64, 0x01, 64);
+		qmc_write16(qmc->scc_pram + QMC_QE_GBL_RX_FRM_BASE,
+			    qmc->scc_pram_offset + UCC_SLOW_PRAM_SIZE);
+		qmc_write16(qmc->scc_pram + QMC_QE_GBL_TX_FRM_BASE,
+			    qmc->scc_pram_offset + UCC_SLOW_PRAM_SIZE + 64);
+	}
+
 	ret = qmc_init_tsa(qmc);
 	if (ret)
 		return ret;
@@ -1757,7 +1931,7 @@  static int qmc_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
-	/* Init SCC */
+	/* Init SCC (CPM1) or UCC (QE) */
 	ret = qmc_init_xcc(qmc);
 	if (ret)
 		return ret;
@@ -1811,7 +1985,7 @@  static void qmc_remove(struct platform_device *pdev)
 	/* Disable interrupts */
 	qmc_write16(qmc->scc_regs + SCC_SCCM, 0);
 
-	/* Exit SCC */
+	/* Exit SCC (CPM1) or UCC (QE) */
 	qmc_exit_xcc(qmc);
 }
 
@@ -1825,8 +1999,23 @@  static const struct qmc_data qmc_data_cpm1 = {
 	.rpack = 0x00000000,
 };
 
+static const struct qmc_data qmc_data_qe = {
+	.version = QMC_QE,
+	.tstate = 0x30000000,
+	.rstate = 0x30000000,
+	.zistate = 0x00000200,
+	.zdstate_hdlc = 0x80FFFFE0,
+	.zdstate_transp = 0x003FFFE2,
+	.rpack = 0x80000000,
+};
+
 static const struct of_device_id qmc_id_table[] = {
+#if IS_ENABLED(CONFIG_CPM1)
 	{ .compatible = "fsl,cpm1-scc-qmc", .data = &qmc_data_cpm1 },
+#endif
+#if IS_ENABLED(CONFIG_QUICC_ENGINE)
+	{ .compatible = "fsl,qe-ucc-qmc", .data = &qmc_data_qe },
+#endif
 	{} /* sentinel */
 };
 MODULE_DEVICE_TABLE(of, qmc_id_table);
@@ -1986,5 +2175,5 @@  struct qmc_chan *devm_qmc_chan_get_bychild(struct device *dev,
 EXPORT_SYMBOL(devm_qmc_chan_get_bychild);
 
 MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
-MODULE_DESCRIPTION("CPM QMC driver");
+MODULE_DESCRIPTION("CPM/QE QMC driver");
 MODULE_LICENSE("GPL");