xref: /openbmc/linux/drivers/mailbox/imx-mailbox.c (revision ba5f9fa0)
12bb70056SOleksij Rempel // SPDX-License-Identifier: GPL-2.0
22bb70056SOleksij Rempel /*
32bb70056SOleksij Rempel  * Copyright (c) 2018 Pengutronix, Oleksij Rempel <o.rempel@pengutronix.de>
42bb70056SOleksij Rempel  */
52bb70056SOleksij Rempel 
62bb70056SOleksij Rempel #include <linux/clk.h>
70a67003bSPeng Fan #include <linux/firmware/imx/ipc.h>
82bb70056SOleksij Rempel #include <linux/interrupt.h>
92bb70056SOleksij Rempel #include <linux/io.h>
100a67003bSPeng Fan #include <linux/iopoll.h>
112bb70056SOleksij Rempel #include <linux/kernel.h>
122bb70056SOleksij Rempel #include <linux/mailbox_controller.h>
132bb70056SOleksij Rempel #include <linux/module.h>
142bb70056SOleksij Rempel #include <linux/of_device.h>
15676f23eaSAnson Huang #include <linux/pm_runtime.h>
162bb70056SOleksij Rempel #include <linux/slab.h>
172bb70056SOleksij Rempel 
182bb70056SOleksij Rempel #define IMX_MU_xSR_GIPn(x)	BIT(28 + (3 - (x)))
192bb70056SOleksij Rempel #define IMX_MU_xSR_RFn(x)	BIT(24 + (3 - (x)))
202bb70056SOleksij Rempel #define IMX_MU_xSR_TEn(x)	BIT(20 + (3 - (x)))
212bb70056SOleksij Rempel #define IMX_MU_xSR_BRDIP	BIT(9)
222bb70056SOleksij Rempel 
232bb70056SOleksij Rempel /* General Purpose Interrupt Enable */
242bb70056SOleksij Rempel #define IMX_MU_xCR_GIEn(x)	BIT(28 + (3 - (x)))
252bb70056SOleksij Rempel /* Receive Interrupt Enable */
262bb70056SOleksij Rempel #define IMX_MU_xCR_RIEn(x)	BIT(24 + (3 - (x)))
272bb70056SOleksij Rempel /* Transmit Interrupt Enable */
282bb70056SOleksij Rempel #define IMX_MU_xCR_TIEn(x)	BIT(20 + (3 - (x)))
292bb70056SOleksij Rempel /* General Purpose Interrupt Request */
302bb70056SOleksij Rempel #define IMX_MU_xCR_GIRn(x)	BIT(16 + (3 - (x)))
312bb70056SOleksij Rempel 
322bb70056SOleksij Rempel #define IMX_MU_CHANS		16
330a67003bSPeng Fan /* TX0/RX0/RXDB[0-3] */
340a67003bSPeng Fan #define IMX_MU_SCU_CHANS	6
352bb70056SOleksij Rempel #define IMX_MU_CHAN_NAME_SIZE	20
362bb70056SOleksij Rempel 
372bb70056SOleksij Rempel enum imx_mu_chan_type {
382bb70056SOleksij Rempel 	IMX_MU_TYPE_TX,		/* Tx */
392bb70056SOleksij Rempel 	IMX_MU_TYPE_RX,		/* Rx */
402bb70056SOleksij Rempel 	IMX_MU_TYPE_TXDB,	/* Tx doorbell */
412bb70056SOleksij Rempel 	IMX_MU_TYPE_RXDB,	/* Rx doorbell */
422bb70056SOleksij Rempel };
432bb70056SOleksij Rempel 
440a67003bSPeng Fan struct imx_sc_rpc_msg_max {
450a67003bSPeng Fan 	struct imx_sc_rpc_msg hdr;
460a67003bSPeng Fan 	u32 data[7];
470a67003bSPeng Fan };
480a67003bSPeng Fan 
492bb70056SOleksij Rempel struct imx_mu_con_priv {
502bb70056SOleksij Rempel 	unsigned int		idx;
512bb70056SOleksij Rempel 	char			irq_desc[IMX_MU_CHAN_NAME_SIZE];
522bb70056SOleksij Rempel 	enum imx_mu_chan_type	type;
532bb70056SOleksij Rempel 	struct mbox_chan	*chan;
542bb70056SOleksij Rempel 	struct tasklet_struct	txdb_tasklet;
552bb70056SOleksij Rempel };
562bb70056SOleksij Rempel 
572bb70056SOleksij Rempel struct imx_mu_priv {
582bb70056SOleksij Rempel 	struct device		*dev;
592bb70056SOleksij Rempel 	void __iomem		*base;
602bb70056SOleksij Rempel 	spinlock_t		xcr_lock; /* control register lock */
612bb70056SOleksij Rempel 
622bb70056SOleksij Rempel 	struct mbox_controller	mbox;
632bb70056SOleksij Rempel 	struct mbox_chan	mbox_chans[IMX_MU_CHANS];
642bb70056SOleksij Rempel 
652bb70056SOleksij Rempel 	struct imx_mu_con_priv  con_priv[IMX_MU_CHANS];
66c6c6bc6eSRichard Zhu 	const struct imx_mu_dcfg	*dcfg;
672bb70056SOleksij Rempel 	struct clk		*clk;
682bb70056SOleksij Rempel 	int			irq;
692bb70056SOleksij Rempel 
70ba5f9fa0SDong Aisheng 	u32 xcr;
71ba5f9fa0SDong Aisheng 
722bb70056SOleksij Rempel 	bool			side_b;
732bb70056SOleksij Rempel };
742bb70056SOleksij Rempel 
7563b38357SPeng Fan struct imx_mu_dcfg {
7663b38357SPeng Fan 	int (*tx)(struct imx_mu_priv *priv, struct imx_mu_con_priv *cp, void *data);
7763b38357SPeng Fan 	int (*rx)(struct imx_mu_priv *priv, struct imx_mu_con_priv *cp);
7863b38357SPeng Fan 	void (*init)(struct imx_mu_priv *priv);
7963b38357SPeng Fan 	u32	xTR[4];		/* Transmit Registers */
8063b38357SPeng Fan 	u32	xRR[4];		/* Receive Registers */
8163b38357SPeng Fan 	u32	xSR;		/* Status Register */
8263b38357SPeng Fan 	u32	xCR;		/* Control Register */
83c6c6bc6eSRichard Zhu };
84c6c6bc6eSRichard Zhu 
852bb70056SOleksij Rempel static struct imx_mu_priv *to_imx_mu_priv(struct mbox_controller *mbox)
862bb70056SOleksij Rempel {
872bb70056SOleksij Rempel 	return container_of(mbox, struct imx_mu_priv, mbox);
882bb70056SOleksij Rempel }
892bb70056SOleksij Rempel 
902bb70056SOleksij Rempel static void imx_mu_write(struct imx_mu_priv *priv, u32 val, u32 offs)
912bb70056SOleksij Rempel {
922bb70056SOleksij Rempel 	iowrite32(val, priv->base + offs);
932bb70056SOleksij Rempel }
942bb70056SOleksij Rempel 
952bb70056SOleksij Rempel static u32 imx_mu_read(struct imx_mu_priv *priv, u32 offs)
962bb70056SOleksij Rempel {
972bb70056SOleksij Rempel 	return ioread32(priv->base + offs);
982bb70056SOleksij Rempel }
992bb70056SOleksij Rempel 
1002bb70056SOleksij Rempel static u32 imx_mu_xcr_rmw(struct imx_mu_priv *priv, u32 set, u32 clr)
1012bb70056SOleksij Rempel {
1022bb70056SOleksij Rempel 	unsigned long flags;
1032bb70056SOleksij Rempel 	u32 val;
1042bb70056SOleksij Rempel 
1052bb70056SOleksij Rempel 	spin_lock_irqsave(&priv->xcr_lock, flags);
106c6c6bc6eSRichard Zhu 	val = imx_mu_read(priv, priv->dcfg->xCR);
1072bb70056SOleksij Rempel 	val &= ~clr;
1082bb70056SOleksij Rempel 	val |= set;
109c6c6bc6eSRichard Zhu 	imx_mu_write(priv, val, priv->dcfg->xCR);
1102bb70056SOleksij Rempel 	spin_unlock_irqrestore(&priv->xcr_lock, flags);
1112bb70056SOleksij Rempel 
1122bb70056SOleksij Rempel 	return val;
1132bb70056SOleksij Rempel }
1142bb70056SOleksij Rempel 
11563b38357SPeng Fan static int imx_mu_generic_tx(struct imx_mu_priv *priv,
11663b38357SPeng Fan 			     struct imx_mu_con_priv *cp,
11763b38357SPeng Fan 			     void *data)
11863b38357SPeng Fan {
11963b38357SPeng Fan 	u32 *arg = data;
12063b38357SPeng Fan 
12163b38357SPeng Fan 	switch (cp->type) {
12263b38357SPeng Fan 	case IMX_MU_TYPE_TX:
12363b38357SPeng Fan 		imx_mu_write(priv, *arg, priv->dcfg->xTR[cp->idx]);
12463b38357SPeng Fan 		imx_mu_xcr_rmw(priv, IMX_MU_xCR_TIEn(cp->idx), 0);
12563b38357SPeng Fan 		break;
12663b38357SPeng Fan 	case IMX_MU_TYPE_TXDB:
12763b38357SPeng Fan 		imx_mu_xcr_rmw(priv, IMX_MU_xCR_GIRn(cp->idx), 0);
12863b38357SPeng Fan 		tasklet_schedule(&cp->txdb_tasklet);
12963b38357SPeng Fan 		break;
13063b38357SPeng Fan 	default:
13163b38357SPeng Fan 		dev_warn_ratelimited(priv->dev, "Send data on wrong channel type: %d\n", cp->type);
13263b38357SPeng Fan 		return -EINVAL;
13363b38357SPeng Fan 	}
13463b38357SPeng Fan 
13563b38357SPeng Fan 	return 0;
13663b38357SPeng Fan }
13763b38357SPeng Fan 
13863b38357SPeng Fan static int imx_mu_generic_rx(struct imx_mu_priv *priv,
13963b38357SPeng Fan 			     struct imx_mu_con_priv *cp)
14063b38357SPeng Fan {
14163b38357SPeng Fan 	u32 dat;
14263b38357SPeng Fan 
14363b38357SPeng Fan 	dat = imx_mu_read(priv, priv->dcfg->xRR[cp->idx]);
14463b38357SPeng Fan 	mbox_chan_received_data(cp->chan, (void *)&dat);
14563b38357SPeng Fan 
14663b38357SPeng Fan 	return 0;
14763b38357SPeng Fan }
14863b38357SPeng Fan 
1490a67003bSPeng Fan static int imx_mu_scu_tx(struct imx_mu_priv *priv,
1500a67003bSPeng Fan 			 struct imx_mu_con_priv *cp,
1510a67003bSPeng Fan 			 void *data)
1520a67003bSPeng Fan {
1530a67003bSPeng Fan 	struct imx_sc_rpc_msg_max *msg = data;
1540a67003bSPeng Fan 	u32 *arg = data;
1550a67003bSPeng Fan 	int i, ret;
1560a67003bSPeng Fan 	u32 xsr;
1570a67003bSPeng Fan 
1580a67003bSPeng Fan 	switch (cp->type) {
1590a67003bSPeng Fan 	case IMX_MU_TYPE_TX:
1609d8ca628SPeng Fan 		/*
1619d8ca628SPeng Fan 		 * msg->hdr.size specifies the number of u32 words while
1629d8ca628SPeng Fan 		 * sizeof yields bytes.
1639d8ca628SPeng Fan 		 */
1649d8ca628SPeng Fan 
1659d8ca628SPeng Fan 		if (msg->hdr.size > sizeof(*msg) / 4) {
1660a67003bSPeng Fan 			/*
1670a67003bSPeng Fan 			 * The real message size can be different to
1680a67003bSPeng Fan 			 * struct imx_sc_rpc_msg_max size
1690a67003bSPeng Fan 			 */
1709d8ca628SPeng Fan 			dev_err(priv->dev, "Maximal message size (%zu bytes) exceeded on TX; got: %i bytes\n", sizeof(*msg), msg->hdr.size << 2);
1710a67003bSPeng Fan 			return -EINVAL;
1720a67003bSPeng Fan 		}
1730a67003bSPeng Fan 
1740a67003bSPeng Fan 		for (i = 0; i < 4 && i < msg->hdr.size; i++)
1750a67003bSPeng Fan 			imx_mu_write(priv, *arg++, priv->dcfg->xTR[i % 4]);
1760a67003bSPeng Fan 		for (; i < msg->hdr.size; i++) {
1770a67003bSPeng Fan 			ret = readl_poll_timeout(priv->base + priv->dcfg->xSR,
1780a67003bSPeng Fan 						 xsr,
1790a67003bSPeng Fan 						 xsr & IMX_MU_xSR_TEn(i % 4),
1800a67003bSPeng Fan 						 0, 100);
1810a67003bSPeng Fan 			if (ret) {
1820a67003bSPeng Fan 				dev_err(priv->dev, "Send data index: %d timeout\n", i);
1830a67003bSPeng Fan 				return ret;
1840a67003bSPeng Fan 			}
1850a67003bSPeng Fan 			imx_mu_write(priv, *arg++, priv->dcfg->xTR[i % 4]);
1860a67003bSPeng Fan 		}
1870a67003bSPeng Fan 
1880a67003bSPeng Fan 		imx_mu_xcr_rmw(priv, IMX_MU_xCR_TIEn(cp->idx), 0);
1890a67003bSPeng Fan 		break;
1900a67003bSPeng Fan 	default:
1910a67003bSPeng Fan 		dev_warn_ratelimited(priv->dev, "Send data on wrong channel type: %d\n", cp->type);
1920a67003bSPeng Fan 		return -EINVAL;
1930a67003bSPeng Fan 	}
1940a67003bSPeng Fan 
1950a67003bSPeng Fan 	return 0;
1960a67003bSPeng Fan }
1970a67003bSPeng Fan 
1980a67003bSPeng Fan static int imx_mu_scu_rx(struct imx_mu_priv *priv,
1990a67003bSPeng Fan 			 struct imx_mu_con_priv *cp)
2000a67003bSPeng Fan {
2010a67003bSPeng Fan 	struct imx_sc_rpc_msg_max msg;
2020a67003bSPeng Fan 	u32 *data = (u32 *)&msg;
2030a67003bSPeng Fan 	int i, ret;
2040a67003bSPeng Fan 	u32 xsr;
2050a67003bSPeng Fan 
2060a67003bSPeng Fan 	imx_mu_xcr_rmw(priv, 0, IMX_MU_xCR_RIEn(0));
2070a67003bSPeng Fan 	*data++ = imx_mu_read(priv, priv->dcfg->xRR[0]);
2080a67003bSPeng Fan 
2099d8ca628SPeng Fan 	if (msg.hdr.size > sizeof(msg) / 4) {
2109d8ca628SPeng Fan 		dev_err(priv->dev, "Maximal message size (%zu bytes) exceeded on RX; got: %i bytes\n", sizeof(msg), msg.hdr.size << 2);
2110a67003bSPeng Fan 		return -EINVAL;
2120a67003bSPeng Fan 	}
2130a67003bSPeng Fan 
2140a67003bSPeng Fan 	for (i = 1; i < msg.hdr.size; i++) {
2150a67003bSPeng Fan 		ret = readl_poll_timeout(priv->base + priv->dcfg->xSR, xsr,
2160a67003bSPeng Fan 					 xsr & IMX_MU_xSR_RFn(i % 4), 0, 100);
2170a67003bSPeng Fan 		if (ret) {
2180a67003bSPeng Fan 			dev_err(priv->dev, "timeout read idx %d\n", i);
2190a67003bSPeng Fan 			return ret;
2200a67003bSPeng Fan 		}
2210a67003bSPeng Fan 		*data++ = imx_mu_read(priv, priv->dcfg->xRR[i % 4]);
2220a67003bSPeng Fan 	}
2230a67003bSPeng Fan 
2240a67003bSPeng Fan 	imx_mu_xcr_rmw(priv, IMX_MU_xCR_RIEn(0), 0);
2250a67003bSPeng Fan 	mbox_chan_received_data(cp->chan, (void *)&msg);
2260a67003bSPeng Fan 
2270a67003bSPeng Fan 	return 0;
2280a67003bSPeng Fan }
2290a67003bSPeng Fan 
2302bb70056SOleksij Rempel static void imx_mu_txdb_tasklet(unsigned long data)
2312bb70056SOleksij Rempel {
2322bb70056SOleksij Rempel 	struct imx_mu_con_priv *cp = (struct imx_mu_con_priv *)data;
2332bb70056SOleksij Rempel 
2342bb70056SOleksij Rempel 	mbox_chan_txdone(cp->chan, 0);
2352bb70056SOleksij Rempel }
2362bb70056SOleksij Rempel 
2372bb70056SOleksij Rempel static irqreturn_t imx_mu_isr(int irq, void *p)
2382bb70056SOleksij Rempel {
2392bb70056SOleksij Rempel 	struct mbox_chan *chan = p;
2402bb70056SOleksij Rempel 	struct imx_mu_priv *priv = to_imx_mu_priv(chan->mbox);
2412bb70056SOleksij Rempel 	struct imx_mu_con_priv *cp = chan->con_priv;
24263b38357SPeng Fan 	u32 val, ctrl;
2432bb70056SOleksij Rempel 
244c6c6bc6eSRichard Zhu 	ctrl = imx_mu_read(priv, priv->dcfg->xCR);
245c6c6bc6eSRichard Zhu 	val = imx_mu_read(priv, priv->dcfg->xSR);
2462bb70056SOleksij Rempel 
2472bb70056SOleksij Rempel 	switch (cp->type) {
2482bb70056SOleksij Rempel 	case IMX_MU_TYPE_TX:
2492bb70056SOleksij Rempel 		val &= IMX_MU_xSR_TEn(cp->idx) &
2502bb70056SOleksij Rempel 			(ctrl & IMX_MU_xCR_TIEn(cp->idx));
2512bb70056SOleksij Rempel 		break;
2522bb70056SOleksij Rempel 	case IMX_MU_TYPE_RX:
2532bb70056SOleksij Rempel 		val &= IMX_MU_xSR_RFn(cp->idx) &
2542bb70056SOleksij Rempel 			(ctrl & IMX_MU_xCR_RIEn(cp->idx));
2552bb70056SOleksij Rempel 		break;
2562bb70056SOleksij Rempel 	case IMX_MU_TYPE_RXDB:
2572bb70056SOleksij Rempel 		val &= IMX_MU_xSR_GIPn(cp->idx) &
2582bb70056SOleksij Rempel 			(ctrl & IMX_MU_xCR_GIEn(cp->idx));
2592bb70056SOleksij Rempel 		break;
2602bb70056SOleksij Rempel 	default:
2612bb70056SOleksij Rempel 		break;
2622bb70056SOleksij Rempel 	}
2632bb70056SOleksij Rempel 
2642bb70056SOleksij Rempel 	if (!val)
2652bb70056SOleksij Rempel 		return IRQ_NONE;
2662bb70056SOleksij Rempel 
2672bb70056SOleksij Rempel 	if (val == IMX_MU_xSR_TEn(cp->idx)) {
2682bb70056SOleksij Rempel 		imx_mu_xcr_rmw(priv, 0, IMX_MU_xCR_TIEn(cp->idx));
2692bb70056SOleksij Rempel 		mbox_chan_txdone(chan, 0);
2702bb70056SOleksij Rempel 	} else if (val == IMX_MU_xSR_RFn(cp->idx)) {
27163b38357SPeng Fan 		priv->dcfg->rx(priv, cp);
2722bb70056SOleksij Rempel 	} else if (val == IMX_MU_xSR_GIPn(cp->idx)) {
273c6c6bc6eSRichard Zhu 		imx_mu_write(priv, IMX_MU_xSR_GIPn(cp->idx), priv->dcfg->xSR);
2742bb70056SOleksij Rempel 		mbox_chan_received_data(chan, NULL);
2752bb70056SOleksij Rempel 	} else {
2762bb70056SOleksij Rempel 		dev_warn_ratelimited(priv->dev, "Not handled interrupt\n");
2772bb70056SOleksij Rempel 		return IRQ_NONE;
2782bb70056SOleksij Rempel 	}
2792bb70056SOleksij Rempel 
2802bb70056SOleksij Rempel 	return IRQ_HANDLED;
2812bb70056SOleksij Rempel }
2822bb70056SOleksij Rempel 
2832bb70056SOleksij Rempel static int imx_mu_send_data(struct mbox_chan *chan, void *data)
2842bb70056SOleksij Rempel {
2852bb70056SOleksij Rempel 	struct imx_mu_priv *priv = to_imx_mu_priv(chan->mbox);
2862bb70056SOleksij Rempel 	struct imx_mu_con_priv *cp = chan->con_priv;
2872bb70056SOleksij Rempel 
28863b38357SPeng Fan 	return priv->dcfg->tx(priv, cp, data);
2892bb70056SOleksij Rempel }
2902bb70056SOleksij Rempel 
2912bb70056SOleksij Rempel static int imx_mu_startup(struct mbox_chan *chan)
2922bb70056SOleksij Rempel {
2932bb70056SOleksij Rempel 	struct imx_mu_priv *priv = to_imx_mu_priv(chan->mbox);
2942bb70056SOleksij Rempel 	struct imx_mu_con_priv *cp = chan->con_priv;
2952bb70056SOleksij Rempel 	int ret;
2962bb70056SOleksij Rempel 
297676f23eaSAnson Huang 	pm_runtime_get_sync(priv->dev);
2982bb70056SOleksij Rempel 	if (cp->type == IMX_MU_TYPE_TXDB) {
2992bb70056SOleksij Rempel 		/* Tx doorbell don't have ACK support */
3002bb70056SOleksij Rempel 		tasklet_init(&cp->txdb_tasklet, imx_mu_txdb_tasklet,
3012bb70056SOleksij Rempel 			     (unsigned long)cp);
3022bb70056SOleksij Rempel 		return 0;
3032bb70056SOleksij Rempel 	}
3042bb70056SOleksij Rempel 
30517b860bbSAnson Huang 	ret = request_irq(priv->irq, imx_mu_isr, IRQF_SHARED |
30617b860bbSAnson Huang 			  IRQF_NO_SUSPEND, cp->irq_desc, chan);
3072bb70056SOleksij Rempel 	if (ret) {
3082bb70056SOleksij Rempel 		dev_err(priv->dev,
3092bb70056SOleksij Rempel 			"Unable to acquire IRQ %d\n", priv->irq);
3102bb70056SOleksij Rempel 		return ret;
3112bb70056SOleksij Rempel 	}
3122bb70056SOleksij Rempel 
3132bb70056SOleksij Rempel 	switch (cp->type) {
3142bb70056SOleksij Rempel 	case IMX_MU_TYPE_RX:
3152bb70056SOleksij Rempel 		imx_mu_xcr_rmw(priv, IMX_MU_xCR_RIEn(cp->idx), 0);
3162bb70056SOleksij Rempel 		break;
3172bb70056SOleksij Rempel 	case IMX_MU_TYPE_RXDB:
3182bb70056SOleksij Rempel 		imx_mu_xcr_rmw(priv, IMX_MU_xCR_GIEn(cp->idx), 0);
3192bb70056SOleksij Rempel 		break;
3202bb70056SOleksij Rempel 	default:
3212bb70056SOleksij Rempel 		break;
3222bb70056SOleksij Rempel 	}
3232bb70056SOleksij Rempel 
3242bb70056SOleksij Rempel 	return 0;
3252bb70056SOleksij Rempel }
3262bb70056SOleksij Rempel 
3272bb70056SOleksij Rempel static void imx_mu_shutdown(struct mbox_chan *chan)
3282bb70056SOleksij Rempel {
3292bb70056SOleksij Rempel 	struct imx_mu_priv *priv = to_imx_mu_priv(chan->mbox);
3302bb70056SOleksij Rempel 	struct imx_mu_con_priv *cp = chan->con_priv;
3312bb70056SOleksij Rempel 
332bf159d15SDaniel Baluta 	if (cp->type == IMX_MU_TYPE_TXDB) {
3332bb70056SOleksij Rempel 		tasklet_kill(&cp->txdb_tasklet);
334676f23eaSAnson Huang 		pm_runtime_put_sync(priv->dev);
335bf159d15SDaniel Baluta 		return;
336bf159d15SDaniel Baluta 	}
3372bb70056SOleksij Rempel 
3385f0af07eSDaniel Baluta 	switch (cp->type) {
3395f0af07eSDaniel Baluta 	case IMX_MU_TYPE_TX:
3405f0af07eSDaniel Baluta 		imx_mu_xcr_rmw(priv, 0, IMX_MU_xCR_TIEn(cp->idx));
3415f0af07eSDaniel Baluta 		break;
3425f0af07eSDaniel Baluta 	case IMX_MU_TYPE_RX:
3435f0af07eSDaniel Baluta 		imx_mu_xcr_rmw(priv, 0, IMX_MU_xCR_RIEn(cp->idx));
3445f0af07eSDaniel Baluta 		break;
3455f0af07eSDaniel Baluta 	case IMX_MU_TYPE_RXDB:
3465f0af07eSDaniel Baluta 		imx_mu_xcr_rmw(priv, 0, IMX_MU_xCR_GIEn(cp->idx));
3475f0af07eSDaniel Baluta 		break;
3485f0af07eSDaniel Baluta 	default:
3495f0af07eSDaniel Baluta 		break;
3505f0af07eSDaniel Baluta 	}
3512bb70056SOleksij Rempel 
3522bb70056SOleksij Rempel 	free_irq(priv->irq, chan);
353676f23eaSAnson Huang 	pm_runtime_put_sync(priv->dev);
3542bb70056SOleksij Rempel }
3552bb70056SOleksij Rempel 
3562bb70056SOleksij Rempel static const struct mbox_chan_ops imx_mu_ops = {
3572bb70056SOleksij Rempel 	.send_data = imx_mu_send_data,
3582bb70056SOleksij Rempel 	.startup = imx_mu_startup,
3592bb70056SOleksij Rempel 	.shutdown = imx_mu_shutdown,
3602bb70056SOleksij Rempel };
3612bb70056SOleksij Rempel 
3620a67003bSPeng Fan static struct mbox_chan *imx_mu_scu_xlate(struct mbox_controller *mbox,
3630a67003bSPeng Fan 					  const struct of_phandle_args *sp)
3640a67003bSPeng Fan {
3650a67003bSPeng Fan 	u32 type, idx, chan;
3660a67003bSPeng Fan 
3670a67003bSPeng Fan 	if (sp->args_count != 2) {
3680a67003bSPeng Fan 		dev_err(mbox->dev, "Invalid argument count %d\n", sp->args_count);
3690a67003bSPeng Fan 		return ERR_PTR(-EINVAL);
3700a67003bSPeng Fan 	}
3710a67003bSPeng Fan 
3720a67003bSPeng Fan 	type = sp->args[0]; /* channel type */
3730a67003bSPeng Fan 	idx = sp->args[1]; /* index */
3740a67003bSPeng Fan 
3750a67003bSPeng Fan 	switch (type) {
3760a67003bSPeng Fan 	case IMX_MU_TYPE_TX:
3770a67003bSPeng Fan 	case IMX_MU_TYPE_RX:
3780a67003bSPeng Fan 		if (idx != 0)
3790a67003bSPeng Fan 			dev_err(mbox->dev, "Invalid chan idx: %d\n", idx);
3800a67003bSPeng Fan 		chan = type;
3810a67003bSPeng Fan 		break;
3820a67003bSPeng Fan 	case IMX_MU_TYPE_RXDB:
3830a67003bSPeng Fan 		chan = 2 + idx;
3840a67003bSPeng Fan 		break;
3850a67003bSPeng Fan 	default:
3860a67003bSPeng Fan 		dev_err(mbox->dev, "Invalid chan type: %d\n", type);
3871b3a347bSDan Carpenter 		return ERR_PTR(-EINVAL);
3880a67003bSPeng Fan 	}
3890a67003bSPeng Fan 
3900a67003bSPeng Fan 	if (chan >= mbox->num_chans) {
3910a67003bSPeng Fan 		dev_err(mbox->dev, "Not supported channel number: %d. (type: %d, idx: %d)\n", chan, type, idx);
3920a67003bSPeng Fan 		return ERR_PTR(-EINVAL);
3930a67003bSPeng Fan 	}
3940a67003bSPeng Fan 
3950a67003bSPeng Fan 	return &mbox->chans[chan];
3960a67003bSPeng Fan }
3970a67003bSPeng Fan 
3982bb70056SOleksij Rempel static struct mbox_chan * imx_mu_xlate(struct mbox_controller *mbox,
3992bb70056SOleksij Rempel 				       const struct of_phandle_args *sp)
4002bb70056SOleksij Rempel {
4012bb70056SOleksij Rempel 	u32 type, idx, chan;
4022bb70056SOleksij Rempel 
4032bb70056SOleksij Rempel 	if (sp->args_count != 2) {
4042bb70056SOleksij Rempel 		dev_err(mbox->dev, "Invalid argument count %d\n", sp->args_count);
4052bb70056SOleksij Rempel 		return ERR_PTR(-EINVAL);
4062bb70056SOleksij Rempel 	}
4072bb70056SOleksij Rempel 
4082bb70056SOleksij Rempel 	type = sp->args[0]; /* channel type */
4092bb70056SOleksij Rempel 	idx = sp->args[1]; /* index */
4102bb70056SOleksij Rempel 	chan = type * 4 + idx;
4112bb70056SOleksij Rempel 
4122bb70056SOleksij Rempel 	if (chan >= mbox->num_chans) {
4132bb70056SOleksij Rempel 		dev_err(mbox->dev, "Not supported channel number: %d. (type: %d, idx: %d)\n", chan, type, idx);
4142bb70056SOleksij Rempel 		return ERR_PTR(-EINVAL);
4152bb70056SOleksij Rempel 	}
4162bb70056SOleksij Rempel 
4172bb70056SOleksij Rempel 	return &mbox->chans[chan];
4182bb70056SOleksij Rempel }
4192bb70056SOleksij Rempel 
4202bb70056SOleksij Rempel static void imx_mu_init_generic(struct imx_mu_priv *priv)
4212bb70056SOleksij Rempel {
42263b38357SPeng Fan 	unsigned int i;
42363b38357SPeng Fan 
42463b38357SPeng Fan 	for (i = 0; i < IMX_MU_CHANS; i++) {
42563b38357SPeng Fan 		struct imx_mu_con_priv *cp = &priv->con_priv[i];
42663b38357SPeng Fan 
42763b38357SPeng Fan 		cp->idx = i % 4;
42863b38357SPeng Fan 		cp->type = i >> 2;
42963b38357SPeng Fan 		cp->chan = &priv->mbox_chans[i];
43063b38357SPeng Fan 		priv->mbox_chans[i].con_priv = cp;
43163b38357SPeng Fan 		snprintf(cp->irq_desc, sizeof(cp->irq_desc),
43263b38357SPeng Fan 			 "imx_mu_chan[%i-%i]", cp->type, cp->idx);
43363b38357SPeng Fan 	}
43463b38357SPeng Fan 
43563b38357SPeng Fan 	priv->mbox.num_chans = IMX_MU_CHANS;
43663b38357SPeng Fan 	priv->mbox.of_xlate = imx_mu_xlate;
43763b38357SPeng Fan 
4382bb70056SOleksij Rempel 	if (priv->side_b)
4392bb70056SOleksij Rempel 		return;
4402bb70056SOleksij Rempel 
4412bb70056SOleksij Rempel 	/* Set default MU configuration */
442c6c6bc6eSRichard Zhu 	imx_mu_write(priv, 0, priv->dcfg->xCR);
4432bb70056SOleksij Rempel }
4442bb70056SOleksij Rempel 
4450a67003bSPeng Fan static void imx_mu_init_scu(struct imx_mu_priv *priv)
4460a67003bSPeng Fan {
4470a67003bSPeng Fan 	unsigned int i;
4480a67003bSPeng Fan 
4490a67003bSPeng Fan 	for (i = 0; i < IMX_MU_SCU_CHANS; i++) {
4500a67003bSPeng Fan 		struct imx_mu_con_priv *cp = &priv->con_priv[i];
4510a67003bSPeng Fan 
4520a67003bSPeng Fan 		cp->idx = i < 2 ? 0 : i - 2;
4530a67003bSPeng Fan 		cp->type = i < 2 ? i : IMX_MU_TYPE_RXDB;
4540a67003bSPeng Fan 		cp->chan = &priv->mbox_chans[i];
4550a67003bSPeng Fan 		priv->mbox_chans[i].con_priv = cp;
4560a67003bSPeng Fan 		snprintf(cp->irq_desc, sizeof(cp->irq_desc),
4570a67003bSPeng Fan 			 "imx_mu_chan[%i-%i]", cp->type, cp->idx);
4580a67003bSPeng Fan 	}
4590a67003bSPeng Fan 
4600a67003bSPeng Fan 	priv->mbox.num_chans = IMX_MU_SCU_CHANS;
4610a67003bSPeng Fan 	priv->mbox.of_xlate = imx_mu_scu_xlate;
4620a67003bSPeng Fan 
4630a67003bSPeng Fan 	/* Set default MU configuration */
4640a67003bSPeng Fan 	imx_mu_write(priv, 0, priv->dcfg->xCR);
4650a67003bSPeng Fan }
4660a67003bSPeng Fan 
4672bb70056SOleksij Rempel static int imx_mu_probe(struct platform_device *pdev)
4682bb70056SOleksij Rempel {
4692bb70056SOleksij Rempel 	struct device *dev = &pdev->dev;
4702bb70056SOleksij Rempel 	struct device_node *np = dev->of_node;
4712bb70056SOleksij Rempel 	struct imx_mu_priv *priv;
472c6c6bc6eSRichard Zhu 	const struct imx_mu_dcfg *dcfg;
4732bb70056SOleksij Rempel 	int ret;
4742bb70056SOleksij Rempel 
4752bb70056SOleksij Rempel 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
4762bb70056SOleksij Rempel 	if (!priv)
4772bb70056SOleksij Rempel 		return -ENOMEM;
4782bb70056SOleksij Rempel 
4792bb70056SOleksij Rempel 	priv->dev = dev;
4802bb70056SOleksij Rempel 
4810c40e631SAnson Huang 	priv->base = devm_platform_ioremap_resource(pdev, 0);
4822bb70056SOleksij Rempel 	if (IS_ERR(priv->base))
4832bb70056SOleksij Rempel 		return PTR_ERR(priv->base);
4842bb70056SOleksij Rempel 
4852bb70056SOleksij Rempel 	priv->irq = platform_get_irq(pdev, 0);
4862bb70056SOleksij Rempel 	if (priv->irq < 0)
4872bb70056SOleksij Rempel 		return priv->irq;
4882bb70056SOleksij Rempel 
489c6c6bc6eSRichard Zhu 	dcfg = of_device_get_match_data(dev);
490c6c6bc6eSRichard Zhu 	if (!dcfg)
491c6c6bc6eSRichard Zhu 		return -EINVAL;
492c6c6bc6eSRichard Zhu 	priv->dcfg = dcfg;
493c6c6bc6eSRichard Zhu 
4942bb70056SOleksij Rempel 	priv->clk = devm_clk_get(dev, NULL);
4952bb70056SOleksij Rempel 	if (IS_ERR(priv->clk)) {
4962bb70056SOleksij Rempel 		if (PTR_ERR(priv->clk) != -ENOENT)
4972bb70056SOleksij Rempel 			return PTR_ERR(priv->clk);
4982bb70056SOleksij Rempel 
4992bb70056SOleksij Rempel 		priv->clk = NULL;
5002bb70056SOleksij Rempel 	}
5012bb70056SOleksij Rempel 
5022bb70056SOleksij Rempel 	ret = clk_prepare_enable(priv->clk);
5032bb70056SOleksij Rempel 	if (ret) {
5042bb70056SOleksij Rempel 		dev_err(dev, "Failed to enable clock\n");
5052bb70056SOleksij Rempel 		return ret;
5062bb70056SOleksij Rempel 	}
5072bb70056SOleksij Rempel 
5082bb70056SOleksij Rempel 	priv->side_b = of_property_read_bool(np, "fsl,mu-side-b");
5092bb70056SOleksij Rempel 
51063b38357SPeng Fan 	priv->dcfg->init(priv);
51163b38357SPeng Fan 
5122bb70056SOleksij Rempel 	spin_lock_init(&priv->xcr_lock);
5132bb70056SOleksij Rempel 
5142bb70056SOleksij Rempel 	priv->mbox.dev = dev;
5152bb70056SOleksij Rempel 	priv->mbox.ops = &imx_mu_ops;
5162bb70056SOleksij Rempel 	priv->mbox.chans = priv->mbox_chans;
5172bb70056SOleksij Rempel 	priv->mbox.txdone_irq = true;
5182bb70056SOleksij Rempel 
5192bb70056SOleksij Rempel 	platform_set_drvdata(pdev, priv);
5202bb70056SOleksij Rempel 
521676f23eaSAnson Huang 	ret = devm_mbox_controller_register(dev, &priv->mbox);
52247303f94SFabio Estevam 	if (ret) {
52347303f94SFabio Estevam 		clk_disable_unprepare(priv->clk);
524676f23eaSAnson Huang 		return ret;
52547303f94SFabio Estevam 	}
526676f23eaSAnson Huang 
527676f23eaSAnson Huang 	pm_runtime_enable(dev);
528676f23eaSAnson Huang 
529676f23eaSAnson Huang 	ret = pm_runtime_get_sync(dev);
530676f23eaSAnson Huang 	if (ret < 0) {
531676f23eaSAnson Huang 		pm_runtime_put_noidle(dev);
532676f23eaSAnson Huang 		goto disable_runtime_pm;
533676f23eaSAnson Huang 	}
534676f23eaSAnson Huang 
535676f23eaSAnson Huang 	ret = pm_runtime_put_sync(dev);
536676f23eaSAnson Huang 	if (ret < 0)
537676f23eaSAnson Huang 		goto disable_runtime_pm;
538676f23eaSAnson Huang 
539676f23eaSAnson Huang 	return 0;
540676f23eaSAnson Huang 
541676f23eaSAnson Huang disable_runtime_pm:
542676f23eaSAnson Huang 	pm_runtime_disable(dev);
543676f23eaSAnson Huang 	return ret;
5442bb70056SOleksij Rempel }
5452bb70056SOleksij Rempel 
5462bb70056SOleksij Rempel static int imx_mu_remove(struct platform_device *pdev)
5472bb70056SOleksij Rempel {
5482bb70056SOleksij Rempel 	struct imx_mu_priv *priv = platform_get_drvdata(pdev);
5492bb70056SOleksij Rempel 
5502bb70056SOleksij Rempel 	clk_disable_unprepare(priv->clk);
551676f23eaSAnson Huang 	pm_runtime_disable(priv->dev);
5522bb70056SOleksij Rempel 
5532bb70056SOleksij Rempel 	return 0;
5542bb70056SOleksij Rempel }
5552bb70056SOleksij Rempel 
55663b38357SPeng Fan static const struct imx_mu_dcfg imx_mu_cfg_imx6sx = {
55763b38357SPeng Fan 	.tx	= imx_mu_generic_tx,
55863b38357SPeng Fan 	.rx	= imx_mu_generic_rx,
55963b38357SPeng Fan 	.init	= imx_mu_init_generic,
56063b38357SPeng Fan 	.xTR	= {0x0, 0x4, 0x8, 0xc},
56163b38357SPeng Fan 	.xRR	= {0x10, 0x14, 0x18, 0x1c},
56263b38357SPeng Fan 	.xSR	= 0x20,
56363b38357SPeng Fan 	.xCR	= 0x24,
56463b38357SPeng Fan };
56563b38357SPeng Fan 
56663b38357SPeng Fan static const struct imx_mu_dcfg imx_mu_cfg_imx7ulp = {
56763b38357SPeng Fan 	.tx	= imx_mu_generic_tx,
56863b38357SPeng Fan 	.rx	= imx_mu_generic_rx,
56963b38357SPeng Fan 	.init	= imx_mu_init_generic,
57063b38357SPeng Fan 	.xTR	= {0x20, 0x24, 0x28, 0x2c},
57163b38357SPeng Fan 	.xRR	= {0x40, 0x44, 0x48, 0x4c},
57263b38357SPeng Fan 	.xSR	= 0x60,
57363b38357SPeng Fan 	.xCR	= 0x64,
57463b38357SPeng Fan };
57563b38357SPeng Fan 
5760a67003bSPeng Fan static const struct imx_mu_dcfg imx_mu_cfg_imx8_scu = {
5770a67003bSPeng Fan 	.tx	= imx_mu_scu_tx,
5780a67003bSPeng Fan 	.rx	= imx_mu_scu_rx,
5790a67003bSPeng Fan 	.init	= imx_mu_init_scu,
5800a67003bSPeng Fan 	.xTR	= {0x0, 0x4, 0x8, 0xc},
5810a67003bSPeng Fan 	.xRR	= {0x10, 0x14, 0x18, 0x1c},
5820a67003bSPeng Fan 	.xSR	= 0x20,
5830a67003bSPeng Fan 	.xCR	= 0x24,
5840a67003bSPeng Fan };
5850a67003bSPeng Fan 
5862bb70056SOleksij Rempel static const struct of_device_id imx_mu_dt_ids[] = {
587c6c6bc6eSRichard Zhu 	{ .compatible = "fsl,imx7ulp-mu", .data = &imx_mu_cfg_imx7ulp },
588c6c6bc6eSRichard Zhu 	{ .compatible = "fsl,imx6sx-mu", .data = &imx_mu_cfg_imx6sx },
5890a67003bSPeng Fan 	{ .compatible = "fsl,imx8-mu-scu", .data = &imx_mu_cfg_imx8_scu },
5902bb70056SOleksij Rempel 	{ },
5912bb70056SOleksij Rempel };
5922bb70056SOleksij Rempel MODULE_DEVICE_TABLE(of, imx_mu_dt_ids);
5932bb70056SOleksij Rempel 
594ba5f9fa0SDong Aisheng static int imx_mu_suspend_noirq(struct device *dev)
595ba5f9fa0SDong Aisheng {
596ba5f9fa0SDong Aisheng 	struct imx_mu_priv *priv = dev_get_drvdata(dev);
597ba5f9fa0SDong Aisheng 
598ba5f9fa0SDong Aisheng 	priv->xcr = imx_mu_read(priv, priv->dcfg->xCR);
599ba5f9fa0SDong Aisheng 
600ba5f9fa0SDong Aisheng 	return 0;
601ba5f9fa0SDong Aisheng }
602ba5f9fa0SDong Aisheng 
603ba5f9fa0SDong Aisheng static int imx_mu_resume_noirq(struct device *dev)
604ba5f9fa0SDong Aisheng {
605ba5f9fa0SDong Aisheng 	struct imx_mu_priv *priv = dev_get_drvdata(dev);
606ba5f9fa0SDong Aisheng 
607ba5f9fa0SDong Aisheng 	/*
608ba5f9fa0SDong Aisheng 	 * ONLY restore MU when context lost, the TIE could
609ba5f9fa0SDong Aisheng 	 * be set during noirq resume as there is MU data
610ba5f9fa0SDong Aisheng 	 * communication going on, and restore the saved
611ba5f9fa0SDong Aisheng 	 * value will overwrite the TIE and cause MU data
612ba5f9fa0SDong Aisheng 	 * send failed, may lead to system freeze. This issue
613ba5f9fa0SDong Aisheng 	 * is observed by testing freeze mode suspend.
614ba5f9fa0SDong Aisheng 	 */
615ba5f9fa0SDong Aisheng 	if (!imx_mu_read(priv, priv->dcfg->xCR))
616ba5f9fa0SDong Aisheng 		imx_mu_write(priv, priv->xcr, priv->dcfg->xCR);
617ba5f9fa0SDong Aisheng 
618ba5f9fa0SDong Aisheng 	return 0;
619ba5f9fa0SDong Aisheng }
620ba5f9fa0SDong Aisheng 
621ba5f9fa0SDong Aisheng static const struct dev_pm_ops imx_mu_pm_ops = {
622ba5f9fa0SDong Aisheng 	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(imx_mu_suspend_noirq,
623ba5f9fa0SDong Aisheng 				      imx_mu_resume_noirq)
624ba5f9fa0SDong Aisheng };
625ba5f9fa0SDong Aisheng 
6262bb70056SOleksij Rempel static struct platform_driver imx_mu_driver = {
6272bb70056SOleksij Rempel 	.probe		= imx_mu_probe,
6282bb70056SOleksij Rempel 	.remove		= imx_mu_remove,
6292bb70056SOleksij Rempel 	.driver = {
6302bb70056SOleksij Rempel 		.name	= "imx_mu",
6312bb70056SOleksij Rempel 		.of_match_table = imx_mu_dt_ids,
632ba5f9fa0SDong Aisheng 		.pm = &imx_mu_pm_ops,
6332bb70056SOleksij Rempel 	},
6342bb70056SOleksij Rempel };
6352bb70056SOleksij Rempel module_platform_driver(imx_mu_driver);
6362bb70056SOleksij Rempel 
6372bb70056SOleksij Rempel MODULE_AUTHOR("Oleksij Rempel <o.rempel@pengutronix.de>");
6382bb70056SOleksij Rempel MODULE_DESCRIPTION("Message Unit driver for i.MX");
6392bb70056SOleksij Rempel MODULE_LICENSE("GPL v2");
640