1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-only
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Copyright (C) 2013-2015 Fujitsu Semiconductor Ltd.
4*4882a593Smuzhiyun * Copyright (C) 2015 Linaro Ltd.
5*4882a593Smuzhiyun * Based on ARM MHU driver by Jassi Brar <jaswinder.singh@linaro.org>
6*4882a593Smuzhiyun * Copyright (C) 2020 ARM Ltd.
7*4882a593Smuzhiyun */
8*4882a593Smuzhiyun
9*4882a593Smuzhiyun #include <linux/amba/bus.h>
10*4882a593Smuzhiyun #include <linux/device.h>
11*4882a593Smuzhiyun #include <linux/err.h>
12*4882a593Smuzhiyun #include <linux/interrupt.h>
13*4882a593Smuzhiyun #include <linux/io.h>
14*4882a593Smuzhiyun #include <linux/kernel.h>
15*4882a593Smuzhiyun #include <linux/mailbox_controller.h>
16*4882a593Smuzhiyun #include <linux/module.h>
17*4882a593Smuzhiyun #include <linux/of.h>
18*4882a593Smuzhiyun #include <linux/of_device.h>
19*4882a593Smuzhiyun
20*4882a593Smuzhiyun #define INTR_STAT_OFS 0x0
21*4882a593Smuzhiyun #define INTR_SET_OFS 0x8
22*4882a593Smuzhiyun #define INTR_CLR_OFS 0x10
23*4882a593Smuzhiyun
24*4882a593Smuzhiyun #define MHU_LP_OFFSET 0x0
25*4882a593Smuzhiyun #define MHU_HP_OFFSET 0x20
26*4882a593Smuzhiyun #define MHU_SEC_OFFSET 0x200
27*4882a593Smuzhiyun #define TX_REG_OFFSET 0x100
28*4882a593Smuzhiyun
29*4882a593Smuzhiyun #define MHU_CHANS 3 /* Secure, Non-Secure High and Low Priority */
30*4882a593Smuzhiyun #define MHU_CHAN_MAX 20 /* Max channels to save on unused RAM */
31*4882a593Smuzhiyun #define MHU_NUM_DOORBELLS 32
32*4882a593Smuzhiyun
33*4882a593Smuzhiyun struct mhu_db_link {
34*4882a593Smuzhiyun unsigned int irq;
35*4882a593Smuzhiyun void __iomem *tx_reg;
36*4882a593Smuzhiyun void __iomem *rx_reg;
37*4882a593Smuzhiyun };
38*4882a593Smuzhiyun
39*4882a593Smuzhiyun struct arm_mhu {
40*4882a593Smuzhiyun void __iomem *base;
41*4882a593Smuzhiyun struct mhu_db_link mlink[MHU_CHANS];
42*4882a593Smuzhiyun struct mbox_controller mbox;
43*4882a593Smuzhiyun struct device *dev;
44*4882a593Smuzhiyun };
45*4882a593Smuzhiyun
46*4882a593Smuzhiyun /**
47*4882a593Smuzhiyun * ARM MHU Mailbox allocated channel information
48*4882a593Smuzhiyun *
49*4882a593Smuzhiyun * @mhu: Pointer to parent mailbox device
50*4882a593Smuzhiyun * @pchan: Physical channel within which this doorbell resides in
51*4882a593Smuzhiyun * @doorbell: doorbell number pertaining to this channel
52*4882a593Smuzhiyun */
53*4882a593Smuzhiyun struct mhu_db_channel {
54*4882a593Smuzhiyun struct arm_mhu *mhu;
55*4882a593Smuzhiyun unsigned int pchan;
56*4882a593Smuzhiyun unsigned int doorbell;
57*4882a593Smuzhiyun };
58*4882a593Smuzhiyun
59*4882a593Smuzhiyun static inline struct mbox_chan *
mhu_db_mbox_to_channel(struct mbox_controller * mbox,unsigned int pchan,unsigned int doorbell)60*4882a593Smuzhiyun mhu_db_mbox_to_channel(struct mbox_controller *mbox, unsigned int pchan,
61*4882a593Smuzhiyun unsigned int doorbell)
62*4882a593Smuzhiyun {
63*4882a593Smuzhiyun int i;
64*4882a593Smuzhiyun struct mhu_db_channel *chan_info;
65*4882a593Smuzhiyun
66*4882a593Smuzhiyun for (i = 0; i < mbox->num_chans; i++) {
67*4882a593Smuzhiyun chan_info = mbox->chans[i].con_priv;
68*4882a593Smuzhiyun if (chan_info && chan_info->pchan == pchan &&
69*4882a593Smuzhiyun chan_info->doorbell == doorbell)
70*4882a593Smuzhiyun return &mbox->chans[i];
71*4882a593Smuzhiyun }
72*4882a593Smuzhiyun
73*4882a593Smuzhiyun return NULL;
74*4882a593Smuzhiyun }
75*4882a593Smuzhiyun
mhu_db_mbox_clear_irq(struct mbox_chan * chan)76*4882a593Smuzhiyun static void mhu_db_mbox_clear_irq(struct mbox_chan *chan)
77*4882a593Smuzhiyun {
78*4882a593Smuzhiyun struct mhu_db_channel *chan_info = chan->con_priv;
79*4882a593Smuzhiyun void __iomem *base = chan_info->mhu->mlink[chan_info->pchan].rx_reg;
80*4882a593Smuzhiyun
81*4882a593Smuzhiyun writel_relaxed(BIT(chan_info->doorbell), base + INTR_CLR_OFS);
82*4882a593Smuzhiyun }
83*4882a593Smuzhiyun
mhu_db_mbox_irq_to_pchan_num(struct arm_mhu * mhu,int irq)84*4882a593Smuzhiyun static unsigned int mhu_db_mbox_irq_to_pchan_num(struct arm_mhu *mhu, int irq)
85*4882a593Smuzhiyun {
86*4882a593Smuzhiyun unsigned int pchan;
87*4882a593Smuzhiyun
88*4882a593Smuzhiyun for (pchan = 0; pchan < MHU_CHANS; pchan++)
89*4882a593Smuzhiyun if (mhu->mlink[pchan].irq == irq)
90*4882a593Smuzhiyun break;
91*4882a593Smuzhiyun return pchan;
92*4882a593Smuzhiyun }
93*4882a593Smuzhiyun
94*4882a593Smuzhiyun static struct mbox_chan *
mhu_db_mbox_irq_to_channel(struct arm_mhu * mhu,unsigned int pchan)95*4882a593Smuzhiyun mhu_db_mbox_irq_to_channel(struct arm_mhu *mhu, unsigned int pchan)
96*4882a593Smuzhiyun {
97*4882a593Smuzhiyun unsigned long bits;
98*4882a593Smuzhiyun unsigned int doorbell;
99*4882a593Smuzhiyun struct mbox_chan *chan = NULL;
100*4882a593Smuzhiyun struct mbox_controller *mbox = &mhu->mbox;
101*4882a593Smuzhiyun void __iomem *base = mhu->mlink[pchan].rx_reg;
102*4882a593Smuzhiyun
103*4882a593Smuzhiyun bits = readl_relaxed(base + INTR_STAT_OFS);
104*4882a593Smuzhiyun if (!bits)
105*4882a593Smuzhiyun /* No IRQs fired in specified physical channel */
106*4882a593Smuzhiyun return NULL;
107*4882a593Smuzhiyun
108*4882a593Smuzhiyun /* An IRQ has fired, find the associated channel */
109*4882a593Smuzhiyun for (doorbell = 0; bits; doorbell++) {
110*4882a593Smuzhiyun if (!test_and_clear_bit(doorbell, &bits))
111*4882a593Smuzhiyun continue;
112*4882a593Smuzhiyun
113*4882a593Smuzhiyun chan = mhu_db_mbox_to_channel(mbox, pchan, doorbell);
114*4882a593Smuzhiyun if (chan)
115*4882a593Smuzhiyun break;
116*4882a593Smuzhiyun dev_err(mbox->dev,
117*4882a593Smuzhiyun "Channel not registered: pchan: %d doorbell: %d\n",
118*4882a593Smuzhiyun pchan, doorbell);
119*4882a593Smuzhiyun }
120*4882a593Smuzhiyun
121*4882a593Smuzhiyun return chan;
122*4882a593Smuzhiyun }
123*4882a593Smuzhiyun
mhu_db_mbox_rx_handler(int irq,void * data)124*4882a593Smuzhiyun static irqreturn_t mhu_db_mbox_rx_handler(int irq, void *data)
125*4882a593Smuzhiyun {
126*4882a593Smuzhiyun struct mbox_chan *chan;
127*4882a593Smuzhiyun struct arm_mhu *mhu = data;
128*4882a593Smuzhiyun unsigned int pchan = mhu_db_mbox_irq_to_pchan_num(mhu, irq);
129*4882a593Smuzhiyun
130*4882a593Smuzhiyun while (NULL != (chan = mhu_db_mbox_irq_to_channel(mhu, pchan))) {
131*4882a593Smuzhiyun mbox_chan_received_data(chan, NULL);
132*4882a593Smuzhiyun mhu_db_mbox_clear_irq(chan);
133*4882a593Smuzhiyun }
134*4882a593Smuzhiyun
135*4882a593Smuzhiyun return IRQ_HANDLED;
136*4882a593Smuzhiyun }
137*4882a593Smuzhiyun
mhu_db_last_tx_done(struct mbox_chan * chan)138*4882a593Smuzhiyun static bool mhu_db_last_tx_done(struct mbox_chan *chan)
139*4882a593Smuzhiyun {
140*4882a593Smuzhiyun struct mhu_db_channel *chan_info = chan->con_priv;
141*4882a593Smuzhiyun void __iomem *base = chan_info->mhu->mlink[chan_info->pchan].tx_reg;
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun if (readl_relaxed(base + INTR_STAT_OFS) & BIT(chan_info->doorbell))
144*4882a593Smuzhiyun return false;
145*4882a593Smuzhiyun
146*4882a593Smuzhiyun return true;
147*4882a593Smuzhiyun }
148*4882a593Smuzhiyun
mhu_db_send_data(struct mbox_chan * chan,void * data)149*4882a593Smuzhiyun static int mhu_db_send_data(struct mbox_chan *chan, void *data)
150*4882a593Smuzhiyun {
151*4882a593Smuzhiyun struct mhu_db_channel *chan_info = chan->con_priv;
152*4882a593Smuzhiyun void __iomem *base = chan_info->mhu->mlink[chan_info->pchan].tx_reg;
153*4882a593Smuzhiyun
154*4882a593Smuzhiyun /* Send event to co-processor */
155*4882a593Smuzhiyun writel_relaxed(BIT(chan_info->doorbell), base + INTR_SET_OFS);
156*4882a593Smuzhiyun
157*4882a593Smuzhiyun return 0;
158*4882a593Smuzhiyun }
159*4882a593Smuzhiyun
mhu_db_startup(struct mbox_chan * chan)160*4882a593Smuzhiyun static int mhu_db_startup(struct mbox_chan *chan)
161*4882a593Smuzhiyun {
162*4882a593Smuzhiyun mhu_db_mbox_clear_irq(chan);
163*4882a593Smuzhiyun return 0;
164*4882a593Smuzhiyun }
165*4882a593Smuzhiyun
mhu_db_shutdown(struct mbox_chan * chan)166*4882a593Smuzhiyun static void mhu_db_shutdown(struct mbox_chan *chan)
167*4882a593Smuzhiyun {
168*4882a593Smuzhiyun struct mhu_db_channel *chan_info = chan->con_priv;
169*4882a593Smuzhiyun struct mbox_controller *mbox = &chan_info->mhu->mbox;
170*4882a593Smuzhiyun int i;
171*4882a593Smuzhiyun
172*4882a593Smuzhiyun for (i = 0; i < mbox->num_chans; i++)
173*4882a593Smuzhiyun if (chan == &mbox->chans[i])
174*4882a593Smuzhiyun break;
175*4882a593Smuzhiyun
176*4882a593Smuzhiyun if (mbox->num_chans == i) {
177*4882a593Smuzhiyun dev_warn(mbox->dev, "Request to free non-existent channel\n");
178*4882a593Smuzhiyun return;
179*4882a593Smuzhiyun }
180*4882a593Smuzhiyun
181*4882a593Smuzhiyun /* Reset channel */
182*4882a593Smuzhiyun mhu_db_mbox_clear_irq(chan);
183*4882a593Smuzhiyun devm_kfree(mbox->dev, chan->con_priv);
184*4882a593Smuzhiyun chan->con_priv = NULL;
185*4882a593Smuzhiyun }
186*4882a593Smuzhiyun
mhu_db_mbox_xlate(struct mbox_controller * mbox,const struct of_phandle_args * spec)187*4882a593Smuzhiyun static struct mbox_chan *mhu_db_mbox_xlate(struct mbox_controller *mbox,
188*4882a593Smuzhiyun const struct of_phandle_args *spec)
189*4882a593Smuzhiyun {
190*4882a593Smuzhiyun struct arm_mhu *mhu = dev_get_drvdata(mbox->dev);
191*4882a593Smuzhiyun struct mhu_db_channel *chan_info;
192*4882a593Smuzhiyun struct mbox_chan *chan;
193*4882a593Smuzhiyun unsigned int pchan = spec->args[0];
194*4882a593Smuzhiyun unsigned int doorbell = spec->args[1];
195*4882a593Smuzhiyun int i;
196*4882a593Smuzhiyun
197*4882a593Smuzhiyun /* Bounds checking */
198*4882a593Smuzhiyun if (pchan >= MHU_CHANS || doorbell >= MHU_NUM_DOORBELLS) {
199*4882a593Smuzhiyun dev_err(mbox->dev,
200*4882a593Smuzhiyun "Invalid channel requested pchan: %d doorbell: %d\n",
201*4882a593Smuzhiyun pchan, doorbell);
202*4882a593Smuzhiyun return ERR_PTR(-EINVAL);
203*4882a593Smuzhiyun }
204*4882a593Smuzhiyun
205*4882a593Smuzhiyun /* Is requested channel free? */
206*4882a593Smuzhiyun chan = mhu_db_mbox_to_channel(mbox, pchan, doorbell);
207*4882a593Smuzhiyun if (chan) {
208*4882a593Smuzhiyun dev_err(mbox->dev, "Channel in use: pchan: %d doorbell: %d\n",
209*4882a593Smuzhiyun pchan, doorbell);
210*4882a593Smuzhiyun return ERR_PTR(-EBUSY);
211*4882a593Smuzhiyun }
212*4882a593Smuzhiyun
213*4882a593Smuzhiyun /* Find the first free slot */
214*4882a593Smuzhiyun for (i = 0; i < mbox->num_chans; i++)
215*4882a593Smuzhiyun if (!mbox->chans[i].con_priv)
216*4882a593Smuzhiyun break;
217*4882a593Smuzhiyun
218*4882a593Smuzhiyun if (mbox->num_chans == i) {
219*4882a593Smuzhiyun dev_err(mbox->dev, "No free channels left\n");
220*4882a593Smuzhiyun return ERR_PTR(-EBUSY);
221*4882a593Smuzhiyun }
222*4882a593Smuzhiyun
223*4882a593Smuzhiyun chan = &mbox->chans[i];
224*4882a593Smuzhiyun
225*4882a593Smuzhiyun chan_info = devm_kzalloc(mbox->dev, sizeof(*chan_info), GFP_KERNEL);
226*4882a593Smuzhiyun if (!chan_info)
227*4882a593Smuzhiyun return ERR_PTR(-ENOMEM);
228*4882a593Smuzhiyun
229*4882a593Smuzhiyun chan_info->mhu = mhu;
230*4882a593Smuzhiyun chan_info->pchan = pchan;
231*4882a593Smuzhiyun chan_info->doorbell = doorbell;
232*4882a593Smuzhiyun
233*4882a593Smuzhiyun chan->con_priv = chan_info;
234*4882a593Smuzhiyun
235*4882a593Smuzhiyun dev_dbg(mbox->dev, "mbox: created channel phys: %d doorbell: %d\n",
236*4882a593Smuzhiyun pchan, doorbell);
237*4882a593Smuzhiyun
238*4882a593Smuzhiyun return chan;
239*4882a593Smuzhiyun }
240*4882a593Smuzhiyun
241*4882a593Smuzhiyun static const struct mbox_chan_ops mhu_db_ops = {
242*4882a593Smuzhiyun .send_data = mhu_db_send_data,
243*4882a593Smuzhiyun .startup = mhu_db_startup,
244*4882a593Smuzhiyun .shutdown = mhu_db_shutdown,
245*4882a593Smuzhiyun .last_tx_done = mhu_db_last_tx_done,
246*4882a593Smuzhiyun };
247*4882a593Smuzhiyun
mhu_db_probe(struct amba_device * adev,const struct amba_id * id)248*4882a593Smuzhiyun static int mhu_db_probe(struct amba_device *adev, const struct amba_id *id)
249*4882a593Smuzhiyun {
250*4882a593Smuzhiyun u32 cell_count;
251*4882a593Smuzhiyun int i, err, max_chans;
252*4882a593Smuzhiyun struct arm_mhu *mhu;
253*4882a593Smuzhiyun struct mbox_chan *chans;
254*4882a593Smuzhiyun struct device *dev = &adev->dev;
255*4882a593Smuzhiyun struct device_node *np = dev->of_node;
256*4882a593Smuzhiyun int mhu_reg[MHU_CHANS] = {
257*4882a593Smuzhiyun MHU_LP_OFFSET, MHU_HP_OFFSET, MHU_SEC_OFFSET,
258*4882a593Smuzhiyun };
259*4882a593Smuzhiyun
260*4882a593Smuzhiyun if (!of_device_is_compatible(np, "arm,mhu-doorbell"))
261*4882a593Smuzhiyun return -ENODEV;
262*4882a593Smuzhiyun
263*4882a593Smuzhiyun err = of_property_read_u32(np, "#mbox-cells", &cell_count);
264*4882a593Smuzhiyun if (err) {
265*4882a593Smuzhiyun dev_err(dev, "failed to read #mbox-cells in '%pOF'\n", np);
266*4882a593Smuzhiyun return err;
267*4882a593Smuzhiyun }
268*4882a593Smuzhiyun
269*4882a593Smuzhiyun if (cell_count == 2) {
270*4882a593Smuzhiyun max_chans = MHU_CHAN_MAX;
271*4882a593Smuzhiyun } else {
272*4882a593Smuzhiyun dev_err(dev, "incorrect value of #mbox-cells in '%pOF'\n", np);
273*4882a593Smuzhiyun return -EINVAL;
274*4882a593Smuzhiyun }
275*4882a593Smuzhiyun
276*4882a593Smuzhiyun mhu = devm_kzalloc(dev, sizeof(*mhu), GFP_KERNEL);
277*4882a593Smuzhiyun if (!mhu)
278*4882a593Smuzhiyun return -ENOMEM;
279*4882a593Smuzhiyun
280*4882a593Smuzhiyun mhu->base = devm_ioremap_resource(dev, &adev->res);
281*4882a593Smuzhiyun if (IS_ERR(mhu->base)) {
282*4882a593Smuzhiyun dev_err(dev, "ioremap failed\n");
283*4882a593Smuzhiyun return PTR_ERR(mhu->base);
284*4882a593Smuzhiyun }
285*4882a593Smuzhiyun
286*4882a593Smuzhiyun chans = devm_kcalloc(dev, max_chans, sizeof(*chans), GFP_KERNEL);
287*4882a593Smuzhiyun if (!chans)
288*4882a593Smuzhiyun return -ENOMEM;
289*4882a593Smuzhiyun
290*4882a593Smuzhiyun mhu->dev = dev;
291*4882a593Smuzhiyun mhu->mbox.dev = dev;
292*4882a593Smuzhiyun mhu->mbox.chans = chans;
293*4882a593Smuzhiyun mhu->mbox.num_chans = max_chans;
294*4882a593Smuzhiyun mhu->mbox.txdone_irq = false;
295*4882a593Smuzhiyun mhu->mbox.txdone_poll = true;
296*4882a593Smuzhiyun mhu->mbox.txpoll_period = 1;
297*4882a593Smuzhiyun
298*4882a593Smuzhiyun mhu->mbox.of_xlate = mhu_db_mbox_xlate;
299*4882a593Smuzhiyun amba_set_drvdata(adev, mhu);
300*4882a593Smuzhiyun
301*4882a593Smuzhiyun mhu->mbox.ops = &mhu_db_ops;
302*4882a593Smuzhiyun
303*4882a593Smuzhiyun err = devm_mbox_controller_register(dev, &mhu->mbox);
304*4882a593Smuzhiyun if (err) {
305*4882a593Smuzhiyun dev_err(dev, "Failed to register mailboxes %d\n", err);
306*4882a593Smuzhiyun return err;
307*4882a593Smuzhiyun }
308*4882a593Smuzhiyun
309*4882a593Smuzhiyun for (i = 0; i < MHU_CHANS; i++) {
310*4882a593Smuzhiyun int irq = mhu->mlink[i].irq = adev->irq[i];
311*4882a593Smuzhiyun
312*4882a593Smuzhiyun if (irq <= 0) {
313*4882a593Smuzhiyun dev_dbg(dev, "No IRQ found for Channel %d\n", i);
314*4882a593Smuzhiyun continue;
315*4882a593Smuzhiyun }
316*4882a593Smuzhiyun
317*4882a593Smuzhiyun mhu->mlink[i].rx_reg = mhu->base + mhu_reg[i];
318*4882a593Smuzhiyun mhu->mlink[i].tx_reg = mhu->mlink[i].rx_reg + TX_REG_OFFSET;
319*4882a593Smuzhiyun
320*4882a593Smuzhiyun err = devm_request_threaded_irq(dev, irq, NULL,
321*4882a593Smuzhiyun mhu_db_mbox_rx_handler,
322*4882a593Smuzhiyun IRQF_ONESHOT, "mhu_db_link", mhu);
323*4882a593Smuzhiyun if (err) {
324*4882a593Smuzhiyun dev_err(dev, "Can't claim IRQ %d\n", irq);
325*4882a593Smuzhiyun mbox_controller_unregister(&mhu->mbox);
326*4882a593Smuzhiyun return err;
327*4882a593Smuzhiyun }
328*4882a593Smuzhiyun }
329*4882a593Smuzhiyun
330*4882a593Smuzhiyun dev_info(dev, "ARM MHU Doorbell mailbox registered\n");
331*4882a593Smuzhiyun return 0;
332*4882a593Smuzhiyun }
333*4882a593Smuzhiyun
334*4882a593Smuzhiyun static struct amba_id mhu_ids[] = {
335*4882a593Smuzhiyun {
336*4882a593Smuzhiyun .id = 0x1bb098,
337*4882a593Smuzhiyun .mask = 0xffffff,
338*4882a593Smuzhiyun },
339*4882a593Smuzhiyun { 0, 0 },
340*4882a593Smuzhiyun };
341*4882a593Smuzhiyun MODULE_DEVICE_TABLE(amba, mhu_ids);
342*4882a593Smuzhiyun
343*4882a593Smuzhiyun static struct amba_driver arm_mhu_db_driver = {
344*4882a593Smuzhiyun .drv = {
345*4882a593Smuzhiyun .name = "mhu-doorbell",
346*4882a593Smuzhiyun },
347*4882a593Smuzhiyun .id_table = mhu_ids,
348*4882a593Smuzhiyun .probe = mhu_db_probe,
349*4882a593Smuzhiyun };
350*4882a593Smuzhiyun module_amba_driver(arm_mhu_db_driver);
351*4882a593Smuzhiyun
352*4882a593Smuzhiyun MODULE_LICENSE("GPL v2");
353*4882a593Smuzhiyun MODULE_DESCRIPTION("ARM MHU Doorbell Driver");
354*4882a593Smuzhiyun MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
355