1 // SPDX-License-Identifier: GPL-2.0-only
3 * Copyright (c) 2015, Fuzhou Rockchip Electronics Co., Ltd
7 #include <linux/interrupt.h>
9 #include <linux/kernel.h>
10 #include <linux/mailbox_controller.h>
11 #include <linux/module.h>
12 #include <linux/of_device.h>
13 #include <linux/platform_device.h>
15 #define MAILBOX_A2B_INTEN 0x00
16 #define MAILBOX_A2B_STATUS 0x04
17 #define MAILBOX_A2B_CMD(x) (0x08 + (x) * 8)
18 #define MAILBOX_A2B_DAT(x) (0x0c + (x) * 8)
20 #define MAILBOX_B2A_INTEN 0x28
21 #define MAILBOX_B2A_STATUS 0x2C
22 #define MAILBOX_B2A_CMD(x) (0x30 + (x) * 8)
23 #define MAILBOX_B2A_DAT(x) (0x34 + (x) * 8)
25 struct rockchip_mbox_msg
{
30 struct rockchip_mbox_data
{
34 struct rockchip_mbox_chan
{
37 struct rockchip_mbox_msg
*msg
;
38 struct rockchip_mbox
*mb
;
41 struct rockchip_mbox
{
42 struct mbox_controller mbox
;
44 void __iomem
*mbox_base
;
46 /* The maximum size of buf for each channel */
49 struct rockchip_mbox_chan
*chans
;
52 static int rockchip_mbox_send_data(struct mbox_chan
*chan
, void *data
)
54 struct rockchip_mbox
*mb
= dev_get_drvdata(chan
->mbox
->dev
);
55 struct rockchip_mbox_msg
*msg
= data
;
56 struct rockchip_mbox_chan
*chans
= mb
->chans
;
61 if (msg
->rx_size
> mb
->buf_size
) {
62 dev_err(mb
->mbox
.dev
, "Transmit size over buf size(%d)\n",
67 dev_dbg(mb
->mbox
.dev
, "Chan[%d]: A2B message, cmd 0x%08x\n",
68 chans
->idx
, msg
->cmd
);
70 mb
->chans
[chans
->idx
].msg
= msg
;
72 writel_relaxed(msg
->cmd
, mb
->mbox_base
+ MAILBOX_A2B_CMD(chans
->idx
));
73 writel_relaxed(msg
->rx_size
, mb
->mbox_base
+
74 MAILBOX_A2B_DAT(chans
->idx
));
79 static int rockchip_mbox_startup(struct mbox_chan
*chan
)
81 struct rockchip_mbox
*mb
= dev_get_drvdata(chan
->mbox
->dev
);
83 /* Enable all B2A interrupts */
84 writel_relaxed((1 << mb
->mbox
.num_chans
) - 1,
85 mb
->mbox_base
+ MAILBOX_B2A_INTEN
);
90 static void rockchip_mbox_shutdown(struct mbox_chan
*chan
)
92 struct rockchip_mbox
*mb
= dev_get_drvdata(chan
->mbox
->dev
);
93 struct rockchip_mbox_chan
*chans
= mb
->chans
;
95 /* Disable all B2A interrupts */
96 writel_relaxed(0, mb
->mbox_base
+ MAILBOX_B2A_INTEN
);
98 mb
->chans
[chans
->idx
].msg
= NULL
;
101 static const struct mbox_chan_ops rockchip_mbox_chan_ops
= {
102 .send_data
= rockchip_mbox_send_data
,
103 .startup
= rockchip_mbox_startup
,
104 .shutdown
= rockchip_mbox_shutdown
,
107 static irqreturn_t
rockchip_mbox_irq(int irq
, void *dev_id
)
110 struct rockchip_mbox
*mb
= (struct rockchip_mbox
*)dev_id
;
111 u32 status
= readl_relaxed(mb
->mbox_base
+ MAILBOX_B2A_STATUS
);
113 for (idx
= 0; idx
< mb
->mbox
.num_chans
; idx
++) {
114 if ((status
& (1 << idx
)) && (irq
== mb
->chans
[idx
].irq
)) {
115 /* Clear mbox interrupt */
116 writel_relaxed(1 << idx
,
117 mb
->mbox_base
+ MAILBOX_B2A_STATUS
);
118 return IRQ_WAKE_THREAD
;
125 static irqreturn_t
rockchip_mbox_isr(int irq
, void *dev_id
)
128 struct rockchip_mbox_msg
*msg
= NULL
;
129 struct rockchip_mbox
*mb
= (struct rockchip_mbox
*)dev_id
;
131 for (idx
= 0; idx
< mb
->mbox
.num_chans
; idx
++) {
132 if (irq
!= mb
->chans
[idx
].irq
)
135 msg
= mb
->chans
[idx
].msg
;
137 dev_err(mb
->mbox
.dev
,
138 "Chan[%d]: B2A message is NULL\n", idx
);
139 break; /* spurious */
142 mbox_chan_received_data(&mb
->mbox
.chans
[idx
], msg
);
143 mb
->chans
[idx
].msg
= NULL
;
145 dev_dbg(mb
->mbox
.dev
, "Chan[%d]: B2A message, cmd 0x%08x\n",
154 static const struct rockchip_mbox_data rk3368_drv_data
= {
158 static const struct of_device_id rockchip_mbox_of_match
[] = {
159 { .compatible
= "rockchip,rk3368-mailbox", .data
= &rk3368_drv_data
},
162 MODULE_DEVICE_TABLE(of
, rockchp_mbox_of_match
);
164 static int rockchip_mbox_probe(struct platform_device
*pdev
)
166 struct rockchip_mbox
*mb
;
167 const struct of_device_id
*match
;
168 const struct rockchip_mbox_data
*drv_data
;
169 struct resource
*res
;
172 if (!pdev
->dev
.of_node
)
175 match
= of_match_node(rockchip_mbox_of_match
, pdev
->dev
.of_node
);
176 drv_data
= (const struct rockchip_mbox_data
*)match
->data
;
178 mb
= devm_kzalloc(&pdev
->dev
, sizeof(*mb
), GFP_KERNEL
);
182 mb
->chans
= devm_kcalloc(&pdev
->dev
, drv_data
->num_chans
,
183 sizeof(*mb
->chans
), GFP_KERNEL
);
187 mb
->mbox
.chans
= devm_kcalloc(&pdev
->dev
, drv_data
->num_chans
,
188 sizeof(*mb
->mbox
.chans
), GFP_KERNEL
);
192 platform_set_drvdata(pdev
, mb
);
194 mb
->mbox
.dev
= &pdev
->dev
;
195 mb
->mbox
.num_chans
= drv_data
->num_chans
;
196 mb
->mbox
.ops
= &rockchip_mbox_chan_ops
;
197 mb
->mbox
.txdone_irq
= true;
199 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
203 mb
->mbox_base
= devm_ioremap_resource(&pdev
->dev
, res
);
204 if (IS_ERR(mb
->mbox_base
))
205 return PTR_ERR(mb
->mbox_base
);
207 /* Each channel has two buffers for A2B and B2A */
208 mb
->buf_size
= (size_t)resource_size(res
) / (drv_data
->num_chans
* 2);
210 mb
->pclk
= devm_clk_get(&pdev
->dev
, "pclk_mailbox");
211 if (IS_ERR(mb
->pclk
)) {
212 ret
= PTR_ERR(mb
->pclk
);
213 dev_err(&pdev
->dev
, "failed to get pclk_mailbox clock: %d\n",
218 ret
= clk_prepare_enable(mb
->pclk
);
220 dev_err(&pdev
->dev
, "failed to enable pclk: %d\n", ret
);
224 for (i
= 0; i
< mb
->mbox
.num_chans
; i
++) {
225 irq
= platform_get_irq(pdev
, i
);
229 ret
= devm_request_threaded_irq(&pdev
->dev
, irq
,
231 rockchip_mbox_isr
, IRQF_ONESHOT
,
232 dev_name(&pdev
->dev
), mb
);
236 mb
->chans
[i
].idx
= i
;
237 mb
->chans
[i
].irq
= irq
;
238 mb
->chans
[i
].mb
= mb
;
239 mb
->chans
[i
].msg
= NULL
;
242 ret
= devm_mbox_controller_register(&pdev
->dev
, &mb
->mbox
);
244 dev_err(&pdev
->dev
, "Failed to register mailbox: %d\n", ret
);
249 static struct platform_driver rockchip_mbox_driver
= {
250 .probe
= rockchip_mbox_probe
,
252 .name
= "rockchip-mailbox",
253 .of_match_table
= of_match_ptr(rockchip_mbox_of_match
),
257 module_platform_driver(rockchip_mbox_driver
);
259 MODULE_LICENSE("GPL v2");
260 MODULE_DESCRIPTION("Rockchip mailbox: communicate between CPU cores and MCU");
261 MODULE_AUTHOR("Addy Ke <addy.ke@rock-chips.com>");
262 MODULE_AUTHOR("Caesar Wang <wxt@rock-chips.com>");