1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2015, Fuzhou Rockchip Electronics Co., Ltd |
4 | */ |
5 | |
6 | #include <linux/clk.h> |
7 | #include <linux/interrupt.h> |
8 | #include <linux/io.h> |
9 | #include <linux/kernel.h> |
10 | #include <linux/mailbox_controller.h> |
11 | #include <linux/of.h> |
12 | #include <linux/module.h> |
13 | #include <linux/platform_device.h> |
14 | |
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) |
19 | |
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) |
24 | |
25 | struct rockchip_mbox_msg { |
26 | u32 cmd; |
27 | int rx_size; |
28 | }; |
29 | |
30 | struct rockchip_mbox_data { |
31 | int num_chans; |
32 | }; |
33 | |
34 | struct rockchip_mbox_chan { |
35 | int idx; |
36 | int irq; |
37 | struct rockchip_mbox_msg *msg; |
38 | struct rockchip_mbox *mb; |
39 | }; |
40 | |
41 | struct rockchip_mbox { |
42 | struct mbox_controller mbox; |
43 | struct clk *pclk; |
44 | void __iomem *mbox_base; |
45 | |
46 | /* The maximum size of buf for each channel */ |
47 | u32 buf_size; |
48 | |
49 | struct rockchip_mbox_chan *chans; |
50 | }; |
51 | |
52 | static int rockchip_mbox_send_data(struct mbox_chan *chan, void *data) |
53 | { |
54 | struct rockchip_mbox *mb = dev_get_drvdata(dev: chan->mbox->dev); |
55 | struct rockchip_mbox_msg *msg = data; |
56 | struct rockchip_mbox_chan *chans = mb->chans; |
57 | |
58 | if (!msg) |
59 | return -EINVAL; |
60 | |
61 | if (msg->rx_size > mb->buf_size) { |
62 | dev_err(mb->mbox.dev, "Transmit size over buf size(%d)\n" , |
63 | mb->buf_size); |
64 | return -EINVAL; |
65 | } |
66 | |
67 | dev_dbg(mb->mbox.dev, "Chan[%d]: A2B message, cmd 0x%08x\n" , |
68 | chans->idx, msg->cmd); |
69 | |
70 | mb->chans[chans->idx].msg = msg; |
71 | |
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)); |
75 | |
76 | return 0; |
77 | } |
78 | |
79 | static int rockchip_mbox_startup(struct mbox_chan *chan) |
80 | { |
81 | struct rockchip_mbox *mb = dev_get_drvdata(dev: chan->mbox->dev); |
82 | |
83 | /* Enable all B2A interrupts */ |
84 | writel_relaxed((1 << mb->mbox.num_chans) - 1, |
85 | mb->mbox_base + MAILBOX_B2A_INTEN); |
86 | |
87 | return 0; |
88 | } |
89 | |
90 | static void rockchip_mbox_shutdown(struct mbox_chan *chan) |
91 | { |
92 | struct rockchip_mbox *mb = dev_get_drvdata(dev: chan->mbox->dev); |
93 | struct rockchip_mbox_chan *chans = mb->chans; |
94 | |
95 | /* Disable all B2A interrupts */ |
96 | writel_relaxed(0, mb->mbox_base + MAILBOX_B2A_INTEN); |
97 | |
98 | mb->chans[chans->idx].msg = NULL; |
99 | } |
100 | |
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, |
105 | }; |
106 | |
107 | static irqreturn_t rockchip_mbox_irq(int irq, void *dev_id) |
108 | { |
109 | int idx; |
110 | struct rockchip_mbox *mb = (struct rockchip_mbox *)dev_id; |
111 | u32 status = readl_relaxed(mb->mbox_base + MAILBOX_B2A_STATUS); |
112 | |
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; |
119 | } |
120 | } |
121 | |
122 | return IRQ_NONE; |
123 | } |
124 | |
125 | static irqreturn_t rockchip_mbox_isr(int irq, void *dev_id) |
126 | { |
127 | int idx; |
128 | struct rockchip_mbox_msg *msg = NULL; |
129 | struct rockchip_mbox *mb = (struct rockchip_mbox *)dev_id; |
130 | |
131 | for (idx = 0; idx < mb->mbox.num_chans; idx++) { |
132 | if (irq != mb->chans[idx].irq) |
133 | continue; |
134 | |
135 | msg = mb->chans[idx].msg; |
136 | if (!msg) { |
137 | dev_err(mb->mbox.dev, |
138 | "Chan[%d]: B2A message is NULL\n" , idx); |
139 | break; /* spurious */ |
140 | } |
141 | |
142 | mbox_chan_received_data(chan: &mb->mbox.chans[idx], data: msg); |
143 | mb->chans[idx].msg = NULL; |
144 | |
145 | dev_dbg(mb->mbox.dev, "Chan[%d]: B2A message, cmd 0x%08x\n" , |
146 | idx, msg->cmd); |
147 | |
148 | break; |
149 | } |
150 | |
151 | return IRQ_HANDLED; |
152 | } |
153 | |
154 | static const struct rockchip_mbox_data rk3368_drv_data = { |
155 | .num_chans = 4, |
156 | }; |
157 | |
158 | static const struct of_device_id rockchip_mbox_of_match[] = { |
159 | { .compatible = "rockchip,rk3368-mailbox" , .data = &rk3368_drv_data}, |
160 | { }, |
161 | }; |
162 | MODULE_DEVICE_TABLE(of, rockchp_mbox_of_match); |
163 | |
164 | static int rockchip_mbox_probe(struct platform_device *pdev) |
165 | { |
166 | struct rockchip_mbox *mb; |
167 | const struct rockchip_mbox_data *drv_data; |
168 | struct resource *res; |
169 | int ret, irq, i; |
170 | |
171 | if (!pdev->dev.of_node) |
172 | return -ENODEV; |
173 | |
174 | drv_data = (const struct rockchip_mbox_data *) device_get_match_data(dev: &pdev->dev); |
175 | |
176 | mb = devm_kzalloc(dev: &pdev->dev, size: sizeof(*mb), GFP_KERNEL); |
177 | if (!mb) |
178 | return -ENOMEM; |
179 | |
180 | mb->chans = devm_kcalloc(dev: &pdev->dev, n: drv_data->num_chans, |
181 | size: sizeof(*mb->chans), GFP_KERNEL); |
182 | if (!mb->chans) |
183 | return -ENOMEM; |
184 | |
185 | mb->mbox.chans = devm_kcalloc(dev: &pdev->dev, n: drv_data->num_chans, |
186 | size: sizeof(*mb->mbox.chans), GFP_KERNEL); |
187 | if (!mb->mbox.chans) |
188 | return -ENOMEM; |
189 | |
190 | platform_set_drvdata(pdev, data: mb); |
191 | |
192 | mb->mbox.dev = &pdev->dev; |
193 | mb->mbox.num_chans = drv_data->num_chans; |
194 | mb->mbox.ops = &rockchip_mbox_chan_ops; |
195 | mb->mbox.txdone_irq = true; |
196 | |
197 | mb->mbox_base = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
198 | if (IS_ERR(ptr: mb->mbox_base)) |
199 | return PTR_ERR(ptr: mb->mbox_base); |
200 | |
201 | /* Each channel has two buffers for A2B and B2A */ |
202 | mb->buf_size = (size_t)resource_size(res) / (drv_data->num_chans * 2); |
203 | |
204 | mb->pclk = devm_clk_get(dev: &pdev->dev, id: "pclk_mailbox" ); |
205 | if (IS_ERR(ptr: mb->pclk)) { |
206 | ret = PTR_ERR(ptr: mb->pclk); |
207 | dev_err(&pdev->dev, "failed to get pclk_mailbox clock: %d\n" , |
208 | ret); |
209 | return ret; |
210 | } |
211 | |
212 | ret = clk_prepare_enable(clk: mb->pclk); |
213 | if (ret) { |
214 | dev_err(&pdev->dev, "failed to enable pclk: %d\n" , ret); |
215 | return ret; |
216 | } |
217 | |
218 | for (i = 0; i < mb->mbox.num_chans; i++) { |
219 | irq = platform_get_irq(pdev, i); |
220 | if (irq < 0) |
221 | return irq; |
222 | |
223 | ret = devm_request_threaded_irq(dev: &pdev->dev, irq, |
224 | handler: rockchip_mbox_irq, |
225 | thread_fn: rockchip_mbox_isr, IRQF_ONESHOT, |
226 | devname: dev_name(dev: &pdev->dev), dev_id: mb); |
227 | if (ret < 0) |
228 | return ret; |
229 | |
230 | mb->chans[i].idx = i; |
231 | mb->chans[i].irq = irq; |
232 | mb->chans[i].mb = mb; |
233 | mb->chans[i].msg = NULL; |
234 | } |
235 | |
236 | ret = devm_mbox_controller_register(dev: &pdev->dev, mbox: &mb->mbox); |
237 | if (ret < 0) |
238 | dev_err(&pdev->dev, "Failed to register mailbox: %d\n" , ret); |
239 | |
240 | return ret; |
241 | } |
242 | |
243 | static struct platform_driver rockchip_mbox_driver = { |
244 | .probe = rockchip_mbox_probe, |
245 | .driver = { |
246 | .name = "rockchip-mailbox" , |
247 | .of_match_table = rockchip_mbox_of_match, |
248 | }, |
249 | }; |
250 | |
251 | module_platform_driver(rockchip_mbox_driver); |
252 | |
253 | MODULE_DESCRIPTION("Rockchip mailbox: communicate between CPU cores and MCU" ); |
254 | MODULE_AUTHOR("Addy Ke <addy.ke@rock-chips.com>" ); |
255 | MODULE_AUTHOR("Caesar Wang <wxt@rock-chips.com>" ); |
256 | |