Commit | Line | Data |
---|---|---|
ca27fc26 BW |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * Spreadtrum mailbox driver | |
4 | * | |
5 | * Copyright (c) 2020 Spreadtrum Communications Inc. | |
6 | */ | |
7 | ||
8 | #include <linux/delay.h> | |
9 | #include <linux/err.h> | |
10 | #include <linux/interrupt.h> | |
11 | #include <linux/io.h> | |
12 | #include <linux/mailbox_controller.h> | |
13 | #include <linux/module.h> | |
e9803aac | 14 | #include <linux/of.h> |
ca27fc26 BW |
15 | #include <linux/platform_device.h> |
16 | #include <linux/clk.h> | |
17 | ||
18 | #define SPRD_MBOX_ID 0x0 | |
19 | #define SPRD_MBOX_MSG_LOW 0x4 | |
20 | #define SPRD_MBOX_MSG_HIGH 0x8 | |
21 | #define SPRD_MBOX_TRIGGER 0xc | |
22 | #define SPRD_MBOX_FIFO_RST 0x10 | |
23 | #define SPRD_MBOX_FIFO_STS 0x14 | |
24 | #define SPRD_MBOX_IRQ_STS 0x18 | |
25 | #define SPRD_MBOX_IRQ_MSK 0x1c | |
26 | #define SPRD_MBOX_LOCK 0x20 | |
27 | #define SPRD_MBOX_FIFO_DEPTH 0x24 | |
28 | ||
9d2e8b93 | 29 | /* Bit and mask definition for inbox's SPRD_MBOX_FIFO_STS register */ |
ca27fc26 BW |
30 | #define SPRD_INBOX_FIFO_DELIVER_MASK GENMASK(23, 16) |
31 | #define SPRD_INBOX_FIFO_OVERLOW_MASK GENMASK(15, 8) | |
32 | #define SPRD_INBOX_FIFO_DELIVER_SHIFT 16 | |
33 | #define SPRD_INBOX_FIFO_BUSY_MASK GENMASK(7, 0) | |
34 | ||
9d2e8b93 | 35 | /* Bit and mask definition for SPRD_MBOX_IRQ_STS register */ |
ca27fc26 BW |
36 | #define SPRD_MBOX_IRQ_CLR BIT(0) |
37 | ||
9d2e8b93 | 38 | /* Bit and mask definition for outbox's SPRD_MBOX_FIFO_STS register */ |
4450f128 | 39 | #define SPRD_OUTBOX_FIFO_FULL BIT(2) |
ca27fc26 BW |
40 | #define SPRD_OUTBOX_FIFO_WR_SHIFT 16 |
41 | #define SPRD_OUTBOX_FIFO_RD_SHIFT 24 | |
42 | #define SPRD_OUTBOX_FIFO_POS_MASK GENMASK(7, 0) | |
43 | ||
9d2e8b93 | 44 | /* Bit and mask definition for inbox's SPRD_MBOX_IRQ_MSK register */ |
ca27fc26 BW |
45 | #define SPRD_INBOX_FIFO_BLOCK_IRQ BIT(0) |
46 | #define SPRD_INBOX_FIFO_OVERFLOW_IRQ BIT(1) | |
47 | #define SPRD_INBOX_FIFO_DELIVER_IRQ BIT(2) | |
48 | #define SPRD_INBOX_FIFO_IRQ_MASK GENMASK(2, 0) | |
49 | ||
9d2e8b93 | 50 | /* Bit and mask definition for outbox's SPRD_MBOX_IRQ_MSK register */ |
ca27fc26 BW |
51 | #define SPRD_OUTBOX_FIFO_NOT_EMPTY_IRQ BIT(0) |
52 | #define SPRD_OUTBOX_FIFO_IRQ_MASK GENMASK(4, 0) | |
53 | ||
6457f4cd | 54 | #define SPRD_OUTBOX_BASE_SPAN 0x1000 |
ca27fc26 | 55 | #define SPRD_MBOX_CHAN_MAX 8 |
6457f4cd | 56 | #define SPRD_SUPP_INBOX_ID_SC9863A 7 |
ca27fc26 BW |
57 | |
58 | struct sprd_mbox_priv { | |
59 | struct mbox_controller mbox; | |
60 | struct device *dev; | |
61 | void __iomem *inbox_base; | |
62 | void __iomem *outbox_base; | |
6457f4cd OZ |
63 | /* Base register address for supplementary outbox */ |
64 | void __iomem *supp_base; | |
ca27fc26 BW |
65 | struct clk *clk; |
66 | u32 outbox_fifo_depth; | |
67 | ||
9468ab84 OZ |
68 | struct mutex lock; |
69 | u32 refcnt; | |
ca27fc26 BW |
70 | struct mbox_chan chan[SPRD_MBOX_CHAN_MAX]; |
71 | }; | |
72 | ||
73 | static struct sprd_mbox_priv *to_sprd_mbox_priv(struct mbox_controller *mbox) | |
74 | { | |
75 | return container_of(mbox, struct sprd_mbox_priv, mbox); | |
76 | } | |
77 | ||
78 | static u32 sprd_mbox_get_fifo_len(struct sprd_mbox_priv *priv, u32 fifo_sts) | |
79 | { | |
80 | u32 wr_pos = (fifo_sts >> SPRD_OUTBOX_FIFO_WR_SHIFT) & | |
81 | SPRD_OUTBOX_FIFO_POS_MASK; | |
82 | u32 rd_pos = (fifo_sts >> SPRD_OUTBOX_FIFO_RD_SHIFT) & | |
83 | SPRD_OUTBOX_FIFO_POS_MASK; | |
84 | u32 fifo_len; | |
85 | ||
86 | /* | |
87 | * If the read pointer is equal with write pointer, which means the fifo | |
88 | * is full or empty. | |
89 | */ | |
90 | if (wr_pos == rd_pos) { | |
91 | if (fifo_sts & SPRD_OUTBOX_FIFO_FULL) | |
92 | fifo_len = priv->outbox_fifo_depth; | |
93 | else | |
94 | fifo_len = 0; | |
95 | } else if (wr_pos > rd_pos) { | |
96 | fifo_len = wr_pos - rd_pos; | |
97 | } else { | |
98 | fifo_len = priv->outbox_fifo_depth - rd_pos + wr_pos; | |
99 | } | |
100 | ||
101 | return fifo_len; | |
102 | } | |
103 | ||
6457f4cd | 104 | static irqreturn_t do_outbox_isr(void __iomem *base, struct sprd_mbox_priv *priv) |
ca27fc26 | 105 | { |
ca27fc26 BW |
106 | struct mbox_chan *chan; |
107 | u32 fifo_sts, fifo_len, msg[2]; | |
108 | int i, id; | |
109 | ||
6457f4cd | 110 | fifo_sts = readl(base + SPRD_MBOX_FIFO_STS); |
ca27fc26 BW |
111 | |
112 | fifo_len = sprd_mbox_get_fifo_len(priv, fifo_sts); | |
113 | if (!fifo_len) { | |
114 | dev_warn_ratelimited(priv->dev, "spurious outbox interrupt\n"); | |
115 | return IRQ_NONE; | |
116 | } | |
117 | ||
118 | for (i = 0; i < fifo_len; i++) { | |
6457f4cd OZ |
119 | msg[0] = readl(base + SPRD_MBOX_MSG_LOW); |
120 | msg[1] = readl(base + SPRD_MBOX_MSG_HIGH); | |
121 | id = readl(base + SPRD_MBOX_ID); | |
ca27fc26 BW |
122 | |
123 | chan = &priv->chan[id]; | |
9468ab84 OZ |
124 | if (chan->cl) |
125 | mbox_chan_received_data(chan, (void *)msg); | |
126 | else | |
127 | dev_warn_ratelimited(priv->dev, | |
128 | "message's been dropped at ch[%d]\n", id); | |
ca27fc26 BW |
129 | |
130 | /* Trigger to update outbox FIFO pointer */ | |
6457f4cd | 131 | writel(0x1, base + SPRD_MBOX_TRIGGER); |
ca27fc26 BW |
132 | } |
133 | ||
134 | /* Clear irq status after reading all message. */ | |
6457f4cd | 135 | writel(SPRD_MBOX_IRQ_CLR, base + SPRD_MBOX_IRQ_STS); |
ca27fc26 BW |
136 | |
137 | return IRQ_HANDLED; | |
138 | } | |
139 | ||
6457f4cd OZ |
140 | static irqreturn_t sprd_mbox_outbox_isr(int irq, void *data) |
141 | { | |
142 | struct sprd_mbox_priv *priv = data; | |
143 | ||
144 | return do_outbox_isr(priv->outbox_base, priv); | |
145 | } | |
146 | ||
147 | static irqreturn_t sprd_mbox_supp_isr(int irq, void *data) | |
148 | { | |
149 | struct sprd_mbox_priv *priv = data; | |
150 | ||
151 | return do_outbox_isr(priv->supp_base, priv); | |
152 | } | |
153 | ||
ca27fc26 BW |
154 | static irqreturn_t sprd_mbox_inbox_isr(int irq, void *data) |
155 | { | |
156 | struct sprd_mbox_priv *priv = data; | |
157 | struct mbox_chan *chan; | |
158 | u32 fifo_sts, send_sts, busy, id; | |
159 | ||
160 | fifo_sts = readl(priv->inbox_base + SPRD_MBOX_FIFO_STS); | |
161 | ||
162 | /* Get the inbox data delivery status */ | |
163 | send_sts = (fifo_sts & SPRD_INBOX_FIFO_DELIVER_MASK) >> | |
164 | SPRD_INBOX_FIFO_DELIVER_SHIFT; | |
165 | if (!send_sts) { | |
166 | dev_warn_ratelimited(priv->dev, "spurious inbox interrupt\n"); | |
167 | return IRQ_NONE; | |
168 | } | |
169 | ||
170 | while (send_sts) { | |
171 | id = __ffs(send_sts); | |
172 | send_sts &= (send_sts - 1); | |
173 | ||
174 | chan = &priv->chan[id]; | |
175 | ||
176 | /* | |
9d2e8b93 | 177 | * Check if the message was fetched by remote target, if yes, |
ca27fc26 BW |
178 | * that means the transmission has been completed. |
179 | */ | |
180 | busy = fifo_sts & SPRD_INBOX_FIFO_BUSY_MASK; | |
181 | if (!(busy & BIT(id))) | |
182 | mbox_chan_txdone(chan, 0); | |
183 | } | |
184 | ||
185 | /* Clear FIFO delivery and overflow status */ | |
186 | writel(fifo_sts & | |
187 | (SPRD_INBOX_FIFO_DELIVER_MASK | SPRD_INBOX_FIFO_OVERLOW_MASK), | |
188 | priv->inbox_base + SPRD_MBOX_FIFO_RST); | |
189 | ||
190 | /* Clear irq status */ | |
191 | writel(SPRD_MBOX_IRQ_CLR, priv->inbox_base + SPRD_MBOX_IRQ_STS); | |
192 | ||
193 | return IRQ_HANDLED; | |
194 | } | |
195 | ||
196 | static int sprd_mbox_send_data(struct mbox_chan *chan, void *msg) | |
197 | { | |
198 | struct sprd_mbox_priv *priv = to_sprd_mbox_priv(chan->mbox); | |
199 | unsigned long id = (unsigned long)chan->con_priv; | |
200 | u32 *data = msg; | |
201 | ||
202 | /* Write data into inbox FIFO, and only support 8 bytes every time */ | |
203 | writel(data[0], priv->inbox_base + SPRD_MBOX_MSG_LOW); | |
204 | writel(data[1], priv->inbox_base + SPRD_MBOX_MSG_HIGH); | |
205 | ||
206 | /* Set target core id */ | |
207 | writel(id, priv->inbox_base + SPRD_MBOX_ID); | |
208 | ||
209 | /* Trigger remote request */ | |
210 | writel(0x1, priv->inbox_base + SPRD_MBOX_TRIGGER); | |
211 | ||
212 | return 0; | |
213 | } | |
214 | ||
215 | static int sprd_mbox_flush(struct mbox_chan *chan, unsigned long timeout) | |
216 | { | |
217 | struct sprd_mbox_priv *priv = to_sprd_mbox_priv(chan->mbox); | |
218 | unsigned long id = (unsigned long)chan->con_priv; | |
219 | u32 busy; | |
220 | ||
221 | timeout = jiffies + msecs_to_jiffies(timeout); | |
222 | ||
223 | while (time_before(jiffies, timeout)) { | |
224 | busy = readl(priv->inbox_base + SPRD_MBOX_FIFO_STS) & | |
225 | SPRD_INBOX_FIFO_BUSY_MASK; | |
226 | if (!(busy & BIT(id))) { | |
227 | mbox_chan_txdone(chan, 0); | |
228 | return 0; | |
229 | } | |
230 | ||
231 | udelay(1); | |
232 | } | |
233 | ||
234 | return -ETIME; | |
235 | } | |
236 | ||
237 | static int sprd_mbox_startup(struct mbox_chan *chan) | |
238 | { | |
239 | struct sprd_mbox_priv *priv = to_sprd_mbox_priv(chan->mbox); | |
240 | u32 val; | |
241 | ||
9468ab84 OZ |
242 | mutex_lock(&priv->lock); |
243 | if (priv->refcnt++ == 0) { | |
244 | /* Select outbox FIFO mode and reset the outbox FIFO status */ | |
245 | writel(0x0, priv->outbox_base + SPRD_MBOX_FIFO_RST); | |
ca27fc26 | 246 | |
9468ab84 OZ |
247 | /* Enable inbox FIFO overflow and delivery interrupt */ |
248 | val = readl(priv->inbox_base + SPRD_MBOX_IRQ_MSK); | |
249 | val &= ~(SPRD_INBOX_FIFO_OVERFLOW_IRQ | SPRD_INBOX_FIFO_DELIVER_IRQ); | |
250 | writel(val, priv->inbox_base + SPRD_MBOX_IRQ_MSK); | |
ca27fc26 | 251 | |
9468ab84 OZ |
252 | /* Enable outbox FIFO not empty interrupt */ |
253 | val = readl(priv->outbox_base + SPRD_MBOX_IRQ_MSK); | |
254 | val &= ~SPRD_OUTBOX_FIFO_NOT_EMPTY_IRQ; | |
255 | writel(val, priv->outbox_base + SPRD_MBOX_IRQ_MSK); | |
6457f4cd OZ |
256 | |
257 | /* Enable supplementary outbox as the fundamental one */ | |
258 | if (priv->supp_base) { | |
259 | writel(0x0, priv->supp_base + SPRD_MBOX_FIFO_RST); | |
260 | val = readl(priv->supp_base + SPRD_MBOX_IRQ_MSK); | |
261 | val &= ~SPRD_OUTBOX_FIFO_NOT_EMPTY_IRQ; | |
262 | writel(val, priv->supp_base + SPRD_MBOX_IRQ_MSK); | |
263 | } | |
9468ab84 OZ |
264 | } |
265 | mutex_unlock(&priv->lock); | |
ca27fc26 BW |
266 | |
267 | return 0; | |
268 | } | |
269 | ||
270 | static void sprd_mbox_shutdown(struct mbox_chan *chan) | |
271 | { | |
272 | struct sprd_mbox_priv *priv = to_sprd_mbox_priv(chan->mbox); | |
273 | ||
9468ab84 OZ |
274 | mutex_lock(&priv->lock); |
275 | if (--priv->refcnt == 0) { | |
276 | /* Disable inbox & outbox interrupt */ | |
277 | writel(SPRD_INBOX_FIFO_IRQ_MASK, priv->inbox_base + SPRD_MBOX_IRQ_MSK); | |
278 | writel(SPRD_OUTBOX_FIFO_IRQ_MASK, priv->outbox_base + SPRD_MBOX_IRQ_MSK); | |
6457f4cd OZ |
279 | |
280 | if (priv->supp_base) | |
281 | writel(SPRD_OUTBOX_FIFO_IRQ_MASK, | |
282 | priv->supp_base + SPRD_MBOX_IRQ_MSK); | |
9468ab84 OZ |
283 | } |
284 | mutex_unlock(&priv->lock); | |
ca27fc26 BW |
285 | } |
286 | ||
287 | static const struct mbox_chan_ops sprd_mbox_ops = { | |
288 | .send_data = sprd_mbox_send_data, | |
289 | .flush = sprd_mbox_flush, | |
290 | .startup = sprd_mbox_startup, | |
291 | .shutdown = sprd_mbox_shutdown, | |
292 | }; | |
293 | ||
294 | static void sprd_mbox_disable(void *data) | |
295 | { | |
296 | struct sprd_mbox_priv *priv = data; | |
297 | ||
298 | clk_disable_unprepare(priv->clk); | |
299 | } | |
300 | ||
301 | static int sprd_mbox_probe(struct platform_device *pdev) | |
302 | { | |
303 | struct device *dev = &pdev->dev; | |
304 | struct sprd_mbox_priv *priv; | |
6457f4cd OZ |
305 | int ret, inbox_irq, outbox_irq, supp_irq; |
306 | unsigned long id, supp; | |
ca27fc26 BW |
307 | |
308 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
309 | if (!priv) | |
310 | return -ENOMEM; | |
311 | ||
312 | priv->dev = dev; | |
9468ab84 | 313 | mutex_init(&priv->lock); |
ca27fc26 BW |
314 | |
315 | /* | |
6457f4cd OZ |
316 | * Unisoc mailbox uses an inbox to send messages to the target |
317 | * core, and uses (an) outbox(es) to receive messages from other | |
318 | * cores. | |
319 | * | |
320 | * Thus in general the mailbox controller supplies 2 different | |
321 | * register addresses and IRQ numbers for inbox and outbox. | |
ca27fc26 | 322 | * |
6457f4cd OZ |
323 | * If necessary, a supplementary inbox could be enabled optionally |
324 | * with an independent FIFO and an extra interrupt. | |
ca27fc26 BW |
325 | */ |
326 | priv->inbox_base = devm_platform_ioremap_resource(pdev, 0); | |
327 | if (IS_ERR(priv->inbox_base)) | |
328 | return PTR_ERR(priv->inbox_base); | |
329 | ||
330 | priv->outbox_base = devm_platform_ioremap_resource(pdev, 1); | |
331 | if (IS_ERR(priv->outbox_base)) | |
332 | return PTR_ERR(priv->outbox_base); | |
333 | ||
334 | priv->clk = devm_clk_get(dev, "enable"); | |
335 | if (IS_ERR(priv->clk)) { | |
336 | dev_err(dev, "failed to get mailbox clock\n"); | |
337 | return PTR_ERR(priv->clk); | |
338 | } | |
339 | ||
340 | ret = clk_prepare_enable(priv->clk); | |
341 | if (ret) | |
342 | return ret; | |
343 | ||
344 | ret = devm_add_action_or_reset(dev, sprd_mbox_disable, priv); | |
345 | if (ret) { | |
346 | dev_err(dev, "failed to add mailbox disable action\n"); | |
347 | return ret; | |
348 | } | |
349 | ||
6457f4cd | 350 | inbox_irq = platform_get_irq_byname(pdev, "inbox"); |
ca27fc26 BW |
351 | if (inbox_irq < 0) |
352 | return inbox_irq; | |
353 | ||
354 | ret = devm_request_irq(dev, inbox_irq, sprd_mbox_inbox_isr, | |
355 | IRQF_NO_SUSPEND, dev_name(dev), priv); | |
356 | if (ret) { | |
357 | dev_err(dev, "failed to request inbox IRQ: %d\n", ret); | |
358 | return ret; | |
359 | } | |
360 | ||
6457f4cd | 361 | outbox_irq = platform_get_irq_byname(pdev, "outbox"); |
ca27fc26 BW |
362 | if (outbox_irq < 0) |
363 | return outbox_irq; | |
364 | ||
365 | ret = devm_request_irq(dev, outbox_irq, sprd_mbox_outbox_isr, | |
366 | IRQF_NO_SUSPEND, dev_name(dev), priv); | |
367 | if (ret) { | |
368 | dev_err(dev, "failed to request outbox IRQ: %d\n", ret); | |
369 | return ret; | |
370 | } | |
371 | ||
6457f4cd OZ |
372 | /* Supplementary outbox IRQ is optional */ |
373 | supp_irq = platform_get_irq_byname(pdev, "supp-outbox"); | |
374 | if (supp_irq > 0) { | |
375 | ret = devm_request_irq(dev, supp_irq, sprd_mbox_supp_isr, | |
376 | IRQF_NO_SUSPEND, dev_name(dev), priv); | |
377 | if (ret) { | |
378 | dev_err(dev, "failed to request outbox IRQ: %d\n", ret); | |
379 | return ret; | |
380 | } | |
381 | ||
382 | supp = (unsigned long) of_device_get_match_data(dev); | |
383 | if (!supp) { | |
384 | dev_err(dev, "no supplementary outbox specified\n"); | |
385 | return -ENODEV; | |
386 | } | |
387 | priv->supp_base = priv->outbox_base + (SPRD_OUTBOX_BASE_SPAN * supp); | |
388 | } | |
389 | ||
ca27fc26 BW |
390 | /* Get the default outbox FIFO depth */ |
391 | priv->outbox_fifo_depth = | |
392 | readl(priv->outbox_base + SPRD_MBOX_FIFO_DEPTH) + 1; | |
393 | priv->mbox.dev = dev; | |
394 | priv->mbox.chans = &priv->chan[0]; | |
395 | priv->mbox.num_chans = SPRD_MBOX_CHAN_MAX; | |
396 | priv->mbox.ops = &sprd_mbox_ops; | |
397 | priv->mbox.txdone_irq = true; | |
398 | ||
399 | for (id = 0; id < SPRD_MBOX_CHAN_MAX; id++) | |
400 | priv->chan[id].con_priv = (void *)id; | |
401 | ||
402 | ret = devm_mbox_controller_register(dev, &priv->mbox); | |
403 | if (ret) { | |
404 | dev_err(dev, "failed to register mailbox: %d\n", ret); | |
405 | return ret; | |
406 | } | |
407 | ||
408 | return 0; | |
409 | } | |
410 | ||
411 | static const struct of_device_id sprd_mbox_of_match[] = { | |
6457f4cd OZ |
412 | { .compatible = "sprd,sc9860-mailbox" }, |
413 | { .compatible = "sprd,sc9863a-mailbox", | |
414 | .data = (void *)SPRD_SUPP_INBOX_ID_SC9863A }, | |
ca27fc26 BW |
415 | { }, |
416 | }; | |
417 | MODULE_DEVICE_TABLE(of, sprd_mbox_of_match); | |
418 | ||
419 | static struct platform_driver sprd_mbox_driver = { | |
420 | .driver = { | |
421 | .name = "sprd-mailbox", | |
422 | .of_match_table = sprd_mbox_of_match, | |
423 | }, | |
424 | .probe = sprd_mbox_probe, | |
425 | }; | |
426 | module_platform_driver(sprd_mbox_driver); | |
427 | ||
428 | MODULE_AUTHOR("Baolin Wang <baolin.wang@unisoc.com>"); | |
429 | MODULE_DESCRIPTION("Spreadtrum mailbox driver"); | |
430 | MODULE_LICENSE("GPL v2"); |