Commit | Line | Data |
---|---|---|
f70ed3b5 CW |
1 | /* |
2 | * Copyright (c) 2015, Fuzhou Rockchip Electronics Co., Ltd | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify it | |
5 | * under the terms and conditions of the GNU General Public License, | |
6 | * version 2, as published by the Free Software Foundation. | |
7 | * | |
8 | * This program is distributed in the hope it will be useful, but WITHOUT | |
9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
11 | * more details. | |
12 | */ | |
13 | ||
14 | #include <linux/clk.h> | |
15 | #include <linux/interrupt.h> | |
16 | #include <linux/io.h> | |
17 | #include <linux/kernel.h> | |
18 | #include <linux/mailbox_controller.h> | |
19 | #include <linux/module.h> | |
20 | #include <linux/of_device.h> | |
21 | #include <linux/platform_device.h> | |
22 | ||
23 | #define MAILBOX_A2B_INTEN 0x00 | |
24 | #define MAILBOX_A2B_STATUS 0x04 | |
25 | #define MAILBOX_A2B_CMD(x) (0x08 + (x) * 8) | |
26 | #define MAILBOX_A2B_DAT(x) (0x0c + (x) * 8) | |
27 | ||
28 | #define MAILBOX_B2A_INTEN 0x28 | |
29 | #define MAILBOX_B2A_STATUS 0x2C | |
30 | #define MAILBOX_B2A_CMD(x) (0x30 + (x) * 8) | |
31 | #define MAILBOX_B2A_DAT(x) (0x34 + (x) * 8) | |
32 | ||
33 | struct rockchip_mbox_msg { | |
34 | u32 cmd; | |
35 | int rx_size; | |
36 | }; | |
37 | ||
38 | struct rockchip_mbox_data { | |
39 | int num_chans; | |
40 | }; | |
41 | ||
42 | struct rockchip_mbox_chan { | |
43 | int idx; | |
44 | int irq; | |
45 | struct rockchip_mbox_msg *msg; | |
46 | struct rockchip_mbox *mb; | |
47 | }; | |
48 | ||
49 | struct rockchip_mbox { | |
50 | struct mbox_controller mbox; | |
51 | struct clk *pclk; | |
52 | void __iomem *mbox_base; | |
53 | ||
54 | /* The maximum size of buf for each channel */ | |
55 | u32 buf_size; | |
56 | ||
57 | struct rockchip_mbox_chan *chans; | |
58 | }; | |
59 | ||
60 | static int rockchip_mbox_send_data(struct mbox_chan *chan, void *data) | |
61 | { | |
62 | struct rockchip_mbox *mb = dev_get_drvdata(chan->mbox->dev); | |
63 | struct rockchip_mbox_msg *msg = data; | |
64 | struct rockchip_mbox_chan *chans = mb->chans; | |
65 | ||
66 | if (!msg) | |
67 | return -EINVAL; | |
68 | ||
69 | if (msg->rx_size > mb->buf_size) { | |
70 | dev_err(mb->mbox.dev, "Transmit size over buf size(%d)\n", | |
71 | mb->buf_size); | |
72 | return -EINVAL; | |
73 | } | |
74 | ||
75 | dev_dbg(mb->mbox.dev, "Chan[%d]: A2B message, cmd 0x%08x\n", | |
76 | chans->idx, msg->cmd); | |
77 | ||
78 | mb->chans[chans->idx].msg = msg; | |
79 | ||
80 | writel_relaxed(msg->cmd, mb->mbox_base + MAILBOX_A2B_CMD(chans->idx)); | |
81 | writel_relaxed(msg->rx_size, mb->mbox_base + | |
82 | MAILBOX_A2B_DAT(chans->idx)); | |
83 | ||
84 | return 0; | |
85 | } | |
86 | ||
87 | static int rockchip_mbox_startup(struct mbox_chan *chan) | |
88 | { | |
89 | struct rockchip_mbox *mb = dev_get_drvdata(chan->mbox->dev); | |
90 | ||
91 | /* Enable all B2A interrupts */ | |
92 | writel_relaxed((1 << mb->mbox.num_chans) - 1, | |
93 | mb->mbox_base + MAILBOX_B2A_INTEN); | |
94 | ||
95 | return 0; | |
96 | } | |
97 | ||
98 | static void rockchip_mbox_shutdown(struct mbox_chan *chan) | |
99 | { | |
100 | struct rockchip_mbox *mb = dev_get_drvdata(chan->mbox->dev); | |
101 | struct rockchip_mbox_chan *chans = mb->chans; | |
102 | ||
103 | /* Disable all B2A interrupts */ | |
104 | writel_relaxed(0, mb->mbox_base + MAILBOX_B2A_INTEN); | |
105 | ||
106 | mb->chans[chans->idx].msg = NULL; | |
107 | } | |
108 | ||
109 | static const struct mbox_chan_ops rockchip_mbox_chan_ops = { | |
110 | .send_data = rockchip_mbox_send_data, | |
111 | .startup = rockchip_mbox_startup, | |
112 | .shutdown = rockchip_mbox_shutdown, | |
113 | }; | |
114 | ||
115 | static irqreturn_t rockchip_mbox_irq(int irq, void *dev_id) | |
116 | { | |
117 | int idx; | |
118 | struct rockchip_mbox *mb = (struct rockchip_mbox *)dev_id; | |
119 | u32 status = readl_relaxed(mb->mbox_base + MAILBOX_B2A_STATUS); | |
120 | ||
121 | for (idx = 0; idx < mb->mbox.num_chans; idx++) { | |
122 | if ((status & (1 << idx)) && (irq == mb->chans[idx].irq)) { | |
123 | /* Clear mbox interrupt */ | |
124 | writel_relaxed(1 << idx, | |
125 | mb->mbox_base + MAILBOX_B2A_STATUS); | |
126 | return IRQ_WAKE_THREAD; | |
127 | } | |
128 | } | |
129 | ||
130 | return IRQ_NONE; | |
131 | } | |
132 | ||
133 | static irqreturn_t rockchip_mbox_isr(int irq, void *dev_id) | |
134 | { | |
135 | int idx; | |
136 | struct rockchip_mbox_msg *msg = NULL; | |
137 | struct rockchip_mbox *mb = (struct rockchip_mbox *)dev_id; | |
138 | ||
139 | for (idx = 0; idx < mb->mbox.num_chans; idx++) { | |
140 | if (irq != mb->chans[idx].irq) | |
141 | continue; | |
142 | ||
143 | msg = mb->chans[idx].msg; | |
144 | if (!msg) { | |
145 | dev_err(mb->mbox.dev, | |
146 | "Chan[%d]: B2A message is NULL\n", idx); | |
147 | break; /* spurious */ | |
148 | } | |
149 | ||
150 | mbox_chan_received_data(&mb->mbox.chans[idx], msg); | |
151 | mb->chans[idx].msg = NULL; | |
152 | ||
153 | dev_dbg(mb->mbox.dev, "Chan[%d]: B2A message, cmd 0x%08x\n", | |
154 | idx, msg->cmd); | |
155 | ||
156 | break; | |
157 | } | |
158 | ||
159 | return IRQ_HANDLED; | |
160 | } | |
161 | ||
162 | static const struct rockchip_mbox_data rk3368_drv_data = { | |
163 | .num_chans = 4, | |
164 | }; | |
165 | ||
166 | static const struct of_device_id rockchip_mbox_of_match[] = { | |
167 | { .compatible = "rockchip,rk3368-mailbox", .data = &rk3368_drv_data}, | |
168 | { }, | |
169 | }; | |
170 | MODULE_DEVICE_TABLE(of, rockchp_mbox_of_match); | |
171 | ||
172 | static int rockchip_mbox_probe(struct platform_device *pdev) | |
173 | { | |
174 | struct rockchip_mbox *mb; | |
175 | const struct of_device_id *match; | |
176 | const struct rockchip_mbox_data *drv_data; | |
177 | struct resource *res; | |
178 | int ret, irq, i; | |
179 | ||
180 | if (!pdev->dev.of_node) | |
181 | return -ENODEV; | |
182 | ||
183 | match = of_match_node(rockchip_mbox_of_match, pdev->dev.of_node); | |
184 | drv_data = (const struct rockchip_mbox_data *)match->data; | |
185 | ||
186 | mb = devm_kzalloc(&pdev->dev, sizeof(*mb), GFP_KERNEL); | |
187 | if (!mb) | |
188 | return -ENOMEM; | |
189 | ||
190 | mb->chans = devm_kcalloc(&pdev->dev, drv_data->num_chans, | |
191 | sizeof(*mb->chans), GFP_KERNEL); | |
192 | if (!mb->chans) | |
193 | return -ENOMEM; | |
194 | ||
195 | mb->mbox.chans = devm_kcalloc(&pdev->dev, drv_data->num_chans, | |
196 | sizeof(*mb->mbox.chans), GFP_KERNEL); | |
197 | if (!mb->mbox.chans) | |
198 | return -ENOMEM; | |
199 | ||
200 | platform_set_drvdata(pdev, mb); | |
201 | ||
202 | mb->mbox.dev = &pdev->dev; | |
203 | mb->mbox.num_chans = drv_data->num_chans; | |
204 | mb->mbox.ops = &rockchip_mbox_chan_ops; | |
205 | mb->mbox.txdone_irq = true; | |
206 | ||
207 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
208 | if (!res) | |
209 | return -ENODEV; | |
210 | ||
211 | mb->mbox_base = devm_ioremap_resource(&pdev->dev, res); | |
212 | if (IS_ERR(mb->mbox_base)) | |
213 | return PTR_ERR(mb->mbox_base); | |
214 | ||
215 | /* Each channel has two buffers for A2B and B2A */ | |
c5a9d1f3 | 216 | mb->buf_size = (size_t)resource_size(res) / (drv_data->num_chans * 2); |
f70ed3b5 CW |
217 | |
218 | mb->pclk = devm_clk_get(&pdev->dev, "pclk_mailbox"); | |
219 | if (IS_ERR(mb->pclk)) { | |
220 | ret = PTR_ERR(mb->pclk); | |
221 | dev_err(&pdev->dev, "failed to get pclk_mailbox clock: %d\n", | |
222 | ret); | |
223 | return ret; | |
224 | } | |
225 | ||
226 | ret = clk_prepare_enable(mb->pclk); | |
227 | if (ret) { | |
228 | dev_err(&pdev->dev, "failed to enable pclk: %d\n", ret); | |
229 | return ret; | |
230 | } | |
231 | ||
232 | for (i = 0; i < mb->mbox.num_chans; i++) { | |
233 | irq = platform_get_irq(pdev, i); | |
234 | if (irq < 0) | |
235 | return irq; | |
236 | ||
237 | ret = devm_request_threaded_irq(&pdev->dev, irq, | |
238 | rockchip_mbox_irq, | |
239 | rockchip_mbox_isr, IRQF_ONESHOT, | |
240 | dev_name(&pdev->dev), mb); | |
241 | if (ret < 0) | |
242 | return ret; | |
243 | ||
244 | mb->chans[i].idx = i; | |
245 | mb->chans[i].irq = irq; | |
246 | mb->chans[i].mb = mb; | |
247 | mb->chans[i].msg = NULL; | |
248 | } | |
249 | ||
250 | ret = mbox_controller_register(&mb->mbox); | |
251 | if (ret < 0) | |
252 | dev_err(&pdev->dev, "Failed to register mailbox: %d\n", ret); | |
253 | ||
254 | return ret; | |
255 | } | |
256 | ||
257 | static int rockchip_mbox_remove(struct platform_device *pdev) | |
258 | { | |
259 | struct rockchip_mbox *mb = platform_get_drvdata(pdev); | |
260 | ||
261 | if (!mb) | |
262 | return -EINVAL; | |
263 | ||
264 | mbox_controller_unregister(&mb->mbox); | |
265 | ||
266 | return 0; | |
267 | } | |
268 | ||
269 | static struct platform_driver rockchip_mbox_driver = { | |
270 | .probe = rockchip_mbox_probe, | |
271 | .remove = rockchip_mbox_remove, | |
272 | .driver = { | |
273 | .name = "rockchip-mailbox", | |
274 | .of_match_table = of_match_ptr(rockchip_mbox_of_match), | |
275 | }, | |
276 | }; | |
277 | ||
278 | module_platform_driver(rockchip_mbox_driver); | |
279 | ||
280 | MODULE_LICENSE("GPL v2"); | |
281 | MODULE_DESCRIPTION("Rockchip mailbox: communicate between CPU cores and MCU"); | |
282 | MODULE_AUTHOR("Addy Ke <addy.ke@rock-chips.com>"); | |
283 | MODULE_AUTHOR("Caesar Wang <wxt@rock-chips.com>"); |