Commit | Line | Data |
---|---|---|
2ad51576 | 1 | // SPDX-License-Identifier: GPL-2.0 |
340a614a HD |
2 | /* |
3 | * OMAP mailbox driver | |
4 | * | |
f48cca87 | 5 | * Copyright (C) 2006-2009 Nokia Corporation. All rights reserved. |
595be654 | 6 | * Copyright (C) 2013-2021 Texas Instruments Incorporated - https://www.ti.com |
340a614a | 7 | * |
f48cca87 | 8 | * Contact: Hiroshi DOYU <Hiroshi.DOYU@nokia.com> |
5040f534 | 9 | * Suman Anna <s-anna@ti.com> |
340a614a HD |
10 | */ |
11 | ||
340a614a | 12 | #include <linux/interrupt.h> |
b3e69146 FC |
13 | #include <linux/spinlock.h> |
14 | #include <linux/mutex.h> | |
5a0e3ad6 | 15 | #include <linux/slab.h> |
b5bebe41 OBC |
16 | #include <linux/kfifo.h> |
17 | #include <linux/err.h> | |
73017a54 | 18 | #include <linux/module.h> |
e9803aac | 19 | #include <linux/of.h> |
5040f534 SA |
20 | #include <linux/platform_device.h> |
21 | #include <linux/pm_runtime.h> | |
5040f534 | 22 | #include <linux/omap-mailbox.h> |
8841a66a SA |
23 | #include <linux/mailbox_controller.h> |
24 | #include <linux/mailbox_client.h> | |
5040f534 | 25 | |
8e3c5952 DG |
26 | #include "mailbox.h" |
27 | ||
5040f534 SA |
28 | #define MAILBOX_REVISION 0x000 |
29 | #define MAILBOX_MESSAGE(m) (0x040 + 4 * (m)) | |
30 | #define MAILBOX_FIFOSTATUS(m) (0x080 + 4 * (m)) | |
31 | #define MAILBOX_MSGSTATUS(m) (0x0c0 + 4 * (m)) | |
32 | ||
33 | #define OMAP2_MAILBOX_IRQSTATUS(u) (0x100 + 8 * (u)) | |
34 | #define OMAP2_MAILBOX_IRQENABLE(u) (0x104 + 8 * (u)) | |
35 | ||
36 | #define OMAP4_MAILBOX_IRQSTATUS(u) (0x104 + 0x10 * (u)) | |
37 | #define OMAP4_MAILBOX_IRQENABLE(u) (0x108 + 0x10 * (u)) | |
38 | #define OMAP4_MAILBOX_IRQENABLE_CLR(u) (0x10c + 0x10 * (u)) | |
39 | ||
40 | #define MAILBOX_IRQSTATUS(type, u) (type ? OMAP4_MAILBOX_IRQSTATUS(u) : \ | |
41 | OMAP2_MAILBOX_IRQSTATUS(u)) | |
42 | #define MAILBOX_IRQENABLE(type, u) (type ? OMAP4_MAILBOX_IRQENABLE(u) : \ | |
43 | OMAP2_MAILBOX_IRQENABLE(u)) | |
44 | #define MAILBOX_IRQDISABLE(type, u) (type ? OMAP4_MAILBOX_IRQENABLE_CLR(u) \ | |
45 | : OMAP2_MAILBOX_IRQENABLE(u)) | |
46 | ||
47 | #define MAILBOX_IRQ_NEWMSG(m) (1 << (2 * (m))) | |
48 | #define MAILBOX_IRQ_NOTFULL(m) (1 << (2 * (m) + 1)) | |
49 | ||
4899f78a SA |
50 | /* Interrupt register configuration types */ |
51 | #define MBOX_INTR_CFG_TYPE1 0 | |
52 | #define MBOX_INTR_CFG_TYPE2 1 | |
53 | ||
5040f534 SA |
54 | struct omap_mbox_fifo { |
55 | unsigned long msg; | |
56 | unsigned long fifo_stat; | |
57 | unsigned long msg_stat; | |
5040f534 SA |
58 | unsigned long irqenable; |
59 | unsigned long irqstatus; | |
5040f534 | 60 | unsigned long irqdisable; |
be3322eb | 61 | u32 intr_bit; |
5040f534 SA |
62 | }; |
63 | ||
64 | struct omap_mbox_queue { | |
65 | spinlock_t lock; | |
66 | struct kfifo fifo; | |
67 | struct work_struct work; | |
5040f534 SA |
68 | struct omap_mbox *mbox; |
69 | bool full; | |
70 | }; | |
71 | ||
ea2ec1e8 SA |
72 | struct omap_mbox_match_data { |
73 | u32 intr_type; | |
74 | }; | |
75 | ||
72c1c817 SA |
76 | struct omap_mbox_device { |
77 | struct device *dev; | |
78 | struct mutex cfg_lock; | |
79 | void __iomem *mbox_base; | |
af1d2f5c | 80 | u32 *irq_ctx; |
72c1c817 SA |
81 | u32 num_users; |
82 | u32 num_fifos; | |
2240f8ae | 83 | u32 intr_type; |
72c1c817 | 84 | struct omap_mbox **mboxes; |
8841a66a | 85 | struct mbox_controller controller; |
72c1c817 SA |
86 | struct list_head elem; |
87 | }; | |
88 | ||
75288cc6 SA |
89 | struct omap_mbox_fifo_info { |
90 | int tx_id; | |
91 | int tx_usr; | |
92 | int tx_irq; | |
93 | ||
94 | int rx_id; | |
95 | int rx_usr; | |
96 | int rx_irq; | |
97 | ||
98 | const char *name; | |
8e3c5952 | 99 | bool send_no_irq; |
75288cc6 SA |
100 | }; |
101 | ||
5040f534 SA |
102 | struct omap_mbox { |
103 | const char *name; | |
104 | int irq; | |
8841a66a | 105 | struct omap_mbox_queue *rxq; |
5040f534 | 106 | struct device *dev; |
72c1c817 | 107 | struct omap_mbox_device *parent; |
be3322eb SA |
108 | struct omap_mbox_fifo tx_fifo; |
109 | struct omap_mbox_fifo rx_fifo; | |
be3322eb | 110 | u32 intr_type; |
8841a66a | 111 | struct mbox_chan *chan; |
8e3c5952 | 112 | bool send_no_irq; |
5040f534 SA |
113 | }; |
114 | ||
72c1c817 SA |
115 | /* global variables for the mailbox devices */ |
116 | static DEFINE_MUTEX(omap_mbox_devices_lock); | |
117 | static LIST_HEAD(omap_mbox_devices); | |
5f00ec64 | 118 | |
b5bebe41 OBC |
119 | static unsigned int mbox_kfifo_size = CONFIG_OMAP_MBOX_KFIFO_SIZE; |
120 | module_param(mbox_kfifo_size, uint, S_IRUGO); | |
121 | MODULE_PARM_DESC(mbox_kfifo_size, "Size of omap's mailbox kfifo (bytes)"); | |
122 | ||
8841a66a SA |
123 | static struct omap_mbox *mbox_chan_to_omap_mbox(struct mbox_chan *chan) |
124 | { | |
125 | if (!chan || !chan->con_priv) | |
126 | return NULL; | |
127 | ||
128 | return (struct omap_mbox *)chan->con_priv; | |
129 | } | |
130 | ||
72c1c817 SA |
131 | static inline |
132 | unsigned int mbox_read_reg(struct omap_mbox_device *mdev, size_t ofs) | |
5040f534 | 133 | { |
72c1c817 | 134 | return __raw_readl(mdev->mbox_base + ofs); |
5040f534 SA |
135 | } |
136 | ||
72c1c817 SA |
137 | static inline |
138 | void mbox_write_reg(struct omap_mbox_device *mdev, u32 val, size_t ofs) | |
5040f534 | 139 | { |
72c1c817 | 140 | __raw_writel(val, mdev->mbox_base + ofs); |
5040f534 SA |
141 | } |
142 | ||
9ae0ee00 | 143 | /* Mailbox FIFO handle functions */ |
9c1f2a5d | 144 | static u32 mbox_fifo_read(struct omap_mbox *mbox) |
9ae0ee00 | 145 | { |
be3322eb | 146 | struct omap_mbox_fifo *fifo = &mbox->rx_fifo; |
2665a4c1 | 147 | |
9c1f2a5d | 148 | return mbox_read_reg(mbox->parent, fifo->msg); |
9ae0ee00 | 149 | } |
5040f534 | 150 | |
9c1f2a5d | 151 | static void mbox_fifo_write(struct omap_mbox *mbox, u32 msg) |
9ae0ee00 | 152 | { |
be3322eb | 153 | struct omap_mbox_fifo *fifo = &mbox->tx_fifo; |
2665a4c1 | 154 | |
72c1c817 | 155 | mbox_write_reg(mbox->parent, msg, fifo->msg); |
9ae0ee00 | 156 | } |
5040f534 SA |
157 | |
158 | static int mbox_fifo_empty(struct omap_mbox *mbox) | |
9ae0ee00 | 159 | { |
be3322eb | 160 | struct omap_mbox_fifo *fifo = &mbox->rx_fifo; |
2665a4c1 | 161 | |
72c1c817 | 162 | return (mbox_read_reg(mbox->parent, fifo->msg_stat) == 0); |
9ae0ee00 | 163 | } |
5040f534 SA |
164 | |
165 | static int mbox_fifo_full(struct omap_mbox *mbox) | |
9ae0ee00 | 166 | { |
be3322eb | 167 | struct omap_mbox_fifo *fifo = &mbox->tx_fifo; |
2665a4c1 | 168 | |
72c1c817 | 169 | return mbox_read_reg(mbox->parent, fifo->fifo_stat); |
9ae0ee00 HD |
170 | } |
171 | ||
172 | /* Mailbox IRQ handle functions */ | |
5040f534 | 173 | static void ack_mbox_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) |
9ae0ee00 | 174 | { |
be3322eb SA |
175 | struct omap_mbox_fifo *fifo = (irq == IRQ_TX) ? |
176 | &mbox->tx_fifo : &mbox->rx_fifo; | |
177 | u32 bit = fifo->intr_bit; | |
178 | u32 irqstatus = fifo->irqstatus; | |
5040f534 | 179 | |
72c1c817 | 180 | mbox_write_reg(mbox->parent, bit, irqstatus); |
5040f534 SA |
181 | |
182 | /* Flush posted write for irq status to avoid spurious interrupts */ | |
72c1c817 | 183 | mbox_read_reg(mbox->parent, irqstatus); |
9ae0ee00 | 184 | } |
5040f534 SA |
185 | |
186 | static int is_mbox_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) | |
9ae0ee00 | 187 | { |
be3322eb SA |
188 | struct omap_mbox_fifo *fifo = (irq == IRQ_TX) ? |
189 | &mbox->tx_fifo : &mbox->rx_fifo; | |
190 | u32 bit = fifo->intr_bit; | |
191 | u32 irqenable = fifo->irqenable; | |
192 | u32 irqstatus = fifo->irqstatus; | |
193 | ||
72c1c817 SA |
194 | u32 enable = mbox_read_reg(mbox->parent, irqenable); |
195 | u32 status = mbox_read_reg(mbox->parent, irqstatus); | |
5040f534 SA |
196 | |
197 | return (int)(enable & status & bit); | |
9ae0ee00 HD |
198 | } |
199 | ||
8841a66a | 200 | static void _omap_mbox_enable_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) |
c869c75c | 201 | { |
be3322eb SA |
202 | u32 l; |
203 | struct omap_mbox_fifo *fifo = (irq == IRQ_TX) ? | |
204 | &mbox->tx_fifo : &mbox->rx_fifo; | |
205 | u32 bit = fifo->intr_bit; | |
206 | u32 irqenable = fifo->irqenable; | |
5040f534 | 207 | |
72c1c817 | 208 | l = mbox_read_reg(mbox->parent, irqenable); |
5040f534 | 209 | l |= bit; |
72c1c817 | 210 | mbox_write_reg(mbox->parent, l, irqenable); |
c869c75c | 211 | } |
c869c75c | 212 | |
8841a66a | 213 | static void _omap_mbox_disable_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) |
c869c75c | 214 | { |
be3322eb SA |
215 | struct omap_mbox_fifo *fifo = (irq == IRQ_TX) ? |
216 | &mbox->tx_fifo : &mbox->rx_fifo; | |
217 | u32 bit = fifo->intr_bit; | |
218 | u32 irqdisable = fifo->irqdisable; | |
5040f534 SA |
219 | |
220 | /* | |
221 | * Read and update the interrupt configuration register for pre-OMAP4. | |
222 | * OMAP4 and later SoCs have a dedicated interrupt disabling register. | |
223 | */ | |
be3322eb | 224 | if (!mbox->intr_type) |
72c1c817 | 225 | bit = mbox_read_reg(mbox->parent, irqdisable) & ~bit; |
5040f534 | 226 | |
72c1c817 | 227 | mbox_write_reg(mbox->parent, bit, irqdisable); |
c869c75c | 228 | } |
c869c75c | 229 | |
8841a66a | 230 | void omap_mbox_enable_irq(struct mbox_chan *chan, omap_mbox_irq_t irq) |
340a614a | 231 | { |
8841a66a | 232 | struct omap_mbox *mbox = mbox_chan_to_omap_mbox(chan); |
340a614a | 233 | |
8841a66a SA |
234 | if (WARN_ON(!mbox)) |
235 | return; | |
b5bebe41 | 236 | |
8841a66a SA |
237 | _omap_mbox_enable_irq(mbox, irq); |
238 | } | |
239 | EXPORT_SYMBOL(omap_mbox_enable_irq); | |
b5bebe41 | 240 | |
8841a66a SA |
241 | void omap_mbox_disable_irq(struct mbox_chan *chan, omap_mbox_irq_t irq) |
242 | { | |
243 | struct omap_mbox *mbox = mbox_chan_to_omap_mbox(chan); | |
244 | ||
245 | if (WARN_ON(!mbox)) | |
246 | return; | |
247 | ||
248 | _omap_mbox_disable_irq(mbox, irq); | |
340a614a | 249 | } |
8841a66a | 250 | EXPORT_SYMBOL(omap_mbox_disable_irq); |
340a614a HD |
251 | |
252 | /* | |
253 | * Message receiver(workqueue) | |
254 | */ | |
255 | static void mbox_rx_work(struct work_struct *work) | |
256 | { | |
257 | struct omap_mbox_queue *mq = | |
258 | container_of(work, struct omap_mbox_queue, work); | |
9c1f2a5d SA |
259 | mbox_msg_t data; |
260 | u32 msg; | |
b5bebe41 OBC |
261 | int len; |
262 | ||
263 | while (kfifo_len(&mq->fifo) >= sizeof(msg)) { | |
264 | len = kfifo_out(&mq->fifo, (unsigned char *)&msg, sizeof(msg)); | |
265 | WARN_ON(len != sizeof(msg)); | |
9c1f2a5d | 266 | data = msg; |
340a614a | 267 | |
9c1f2a5d | 268 | mbox_chan_received_data(mq->mbox->chan, (void *)data); |
d2295042 FGL |
269 | spin_lock_irq(&mq->lock); |
270 | if (mq->full) { | |
271 | mq->full = false; | |
8841a66a | 272 | _omap_mbox_enable_irq(mq->mbox, IRQ_RX); |
d2295042 FGL |
273 | } |
274 | spin_unlock_irq(&mq->lock); | |
340a614a HD |
275 | } |
276 | } | |
277 | ||
278 | /* | |
279 | * Mailbox interrupt handler | |
280 | */ | |
340a614a HD |
281 | static void __mbox_tx_interrupt(struct omap_mbox *mbox) |
282 | { | |
8841a66a | 283 | _omap_mbox_disable_irq(mbox, IRQ_TX); |
340a614a | 284 | ack_mbox_irq(mbox, IRQ_TX); |
8841a66a | 285 | mbox_chan_txdone(mbox->chan, 0); |
340a614a HD |
286 | } |
287 | ||
288 | static void __mbox_rx_interrupt(struct omap_mbox *mbox) | |
289 | { | |
b5bebe41 | 290 | struct omap_mbox_queue *mq = mbox->rxq; |
9c1f2a5d | 291 | u32 msg; |
b5bebe41 | 292 | int len; |
340a614a | 293 | |
340a614a | 294 | while (!mbox_fifo_empty(mbox)) { |
b5bebe41 | 295 | if (unlikely(kfifo_avail(&mq->fifo) < sizeof(msg))) { |
8841a66a | 296 | _omap_mbox_disable_irq(mbox, IRQ_RX); |
d2295042 | 297 | mq->full = true; |
340a614a | 298 | goto nomem; |
1ea5d6d1 | 299 | } |
340a614a HD |
300 | |
301 | msg = mbox_fifo_read(mbox); | |
340a614a | 302 | |
b5bebe41 OBC |
303 | len = kfifo_in(&mq->fifo, (unsigned char *)&msg, sizeof(msg)); |
304 | WARN_ON(len != sizeof(msg)); | |
340a614a HD |
305 | } |
306 | ||
307 | /* no more messages in the fifo. clear IRQ source. */ | |
308 | ack_mbox_irq(mbox, IRQ_RX); | |
f48cca87 | 309 | nomem: |
c4873005 | 310 | schedule_work(&mbox->rxq->work); |
340a614a HD |
311 | } |
312 | ||
313 | static irqreturn_t mbox_interrupt(int irq, void *p) | |
314 | { | |
2a7057e3 | 315 | struct omap_mbox *mbox = p; |
340a614a HD |
316 | |
317 | if (is_mbox_irq(mbox, IRQ_TX)) | |
318 | __mbox_tx_interrupt(mbox); | |
319 | ||
320 | if (is_mbox_irq(mbox, IRQ_RX)) | |
321 | __mbox_rx_interrupt(mbox); | |
322 | ||
323 | return IRQ_HANDLED; | |
324 | } | |
325 | ||
340a614a | 326 | static struct omap_mbox_queue *mbox_queue_alloc(struct omap_mbox *mbox, |
8841a66a | 327 | void (*work)(struct work_struct *)) |
340a614a | 328 | { |
340a614a HD |
329 | struct omap_mbox_queue *mq; |
330 | ||
8841a66a SA |
331 | if (!work) |
332 | return NULL; | |
333 | ||
86f6f5e2 | 334 | mq = kzalloc(sizeof(*mq), GFP_KERNEL); |
340a614a HD |
335 | if (!mq) |
336 | return NULL; | |
337 | ||
338 | spin_lock_init(&mq->lock); | |
339 | ||
b5bebe41 | 340 | if (kfifo_alloc(&mq->fifo, mbox_kfifo_size, GFP_KERNEL)) |
340a614a | 341 | goto error; |
340a614a | 342 | |
8841a66a | 343 | INIT_WORK(&mq->work, work); |
340a614a | 344 | return mq; |
8841a66a | 345 | |
340a614a HD |
346 | error: |
347 | kfree(mq); | |
348 | return NULL; | |
349 | } | |
350 | ||
351 | static void mbox_queue_free(struct omap_mbox_queue *q) | |
352 | { | |
b5bebe41 | 353 | kfifo_free(&q->fifo); |
340a614a HD |
354 | kfree(q); |
355 | } | |
356 | ||
c7c158e5 | 357 | static int omap_mbox_startup(struct omap_mbox *mbox) |
340a614a | 358 | { |
5f00ec64 | 359 | int ret = 0; |
340a614a HD |
360 | struct omap_mbox_queue *mq; |
361 | ||
8841a66a SA |
362 | mq = mbox_queue_alloc(mbox, mbox_rx_work); |
363 | if (!mq) | |
364 | return -ENOMEM; | |
365 | mbox->rxq = mq; | |
366 | mq->mbox = mbox; | |
367 | ||
368 | ret = request_irq(mbox->irq, mbox_interrupt, IRQF_SHARED, | |
369 | mbox->name, mbox); | |
370 | if (unlikely(ret)) { | |
371 | pr_err("failed to register mailbox interrupt:%d\n", ret); | |
372 | goto fail_request_irq; | |
373 | } | |
340a614a | 374 | |
8e3c5952 DG |
375 | if (mbox->send_no_irq) |
376 | mbox->chan->txdone_method = TXDONE_BY_ACK; | |
377 | ||
8841a66a | 378 | _omap_mbox_enable_irq(mbox, IRQ_RX); |
1d8a0e96 | 379 | |
340a614a HD |
380 | return 0; |
381 | ||
ecf305cf SA |
382 | fail_request_irq: |
383 | mbox_queue_free(mbox->rxq); | |
340a614a HD |
384 | return ret; |
385 | } | |
386 | ||
387 | static void omap_mbox_fini(struct omap_mbox *mbox) | |
388 | { | |
8841a66a SA |
389 | _omap_mbox_disable_irq(mbox, IRQ_RX); |
390 | free_irq(mbox->irq, mbox); | |
391 | flush_work(&mbox->rxq->work); | |
392 | mbox_queue_free(mbox->rxq); | |
340a614a HD |
393 | } |
394 | ||
72c1c817 SA |
395 | static struct omap_mbox *omap_mbox_device_find(struct omap_mbox_device *mdev, |
396 | const char *mbox_name) | |
340a614a | 397 | { |
c0377320 | 398 | struct omap_mbox *_mbox, *mbox = NULL; |
72c1c817 SA |
399 | struct omap_mbox **mboxes = mdev->mboxes; |
400 | int i; | |
340a614a | 401 | |
9c80c8cd | 402 | if (!mboxes) |
72c1c817 | 403 | return NULL; |
340a614a | 404 | |
c0377320 | 405 | for (i = 0; (_mbox = mboxes[i]); i++) { |
72c1c817 | 406 | if (!strcmp(_mbox->name, mbox_name)) { |
c0377320 | 407 | mbox = _mbox; |
9c80c8cd | 408 | break; |
c0377320 KH |
409 | } |
410 | } | |
72c1c817 SA |
411 | return mbox; |
412 | } | |
413 | ||
8841a66a SA |
414 | struct mbox_chan *omap_mbox_request_channel(struct mbox_client *cl, |
415 | const char *chan_name) | |
72c1c817 | 416 | { |
8841a66a | 417 | struct device *dev = cl->dev; |
72c1c817 SA |
418 | struct omap_mbox *mbox = NULL; |
419 | struct omap_mbox_device *mdev; | |
420 | int ret; | |
421 | ||
8841a66a SA |
422 | if (!dev) |
423 | return ERR_PTR(-ENODEV); | |
424 | ||
425 | if (dev->of_node) { | |
426 | pr_err("%s: please use mbox_request_channel(), this API is supported only for OMAP non-DT usage\n", | |
427 | __func__); | |
428 | return ERR_PTR(-ENODEV); | |
429 | } | |
430 | ||
72c1c817 SA |
431 | mutex_lock(&omap_mbox_devices_lock); |
432 | list_for_each_entry(mdev, &omap_mbox_devices, elem) { | |
8841a66a | 433 | mbox = omap_mbox_device_find(mdev, chan_name); |
72c1c817 SA |
434 | if (mbox) |
435 | break; | |
436 | } | |
437 | mutex_unlock(&omap_mbox_devices_lock); | |
9c80c8cd | 438 | |
8841a66a | 439 | if (!mbox || !mbox->chan) |
9c80c8cd | 440 | return ERR_PTR(-ENOENT); |
340a614a | 441 | |
f11ff34d EB |
442 | ret = mbox_bind_client(mbox->chan, cl); |
443 | if (ret) | |
444 | return ERR_PTR(ret); | |
1d8a0e96 | 445 | |
f11ff34d | 446 | return mbox->chan; |
340a614a | 447 | } |
8841a66a | 448 | EXPORT_SYMBOL(omap_mbox_request_channel); |
340a614a | 449 | |
6b233985 HD |
450 | static struct class omap_mbox_class = { .name = "mbox", }; |
451 | ||
72c1c817 | 452 | static int omap_mbox_register(struct omap_mbox_device *mdev) |
340a614a | 453 | { |
9c80c8cd FC |
454 | int ret; |
455 | int i; | |
72c1c817 | 456 | struct omap_mbox **mboxes; |
340a614a | 457 | |
72c1c817 | 458 | if (!mdev || !mdev->mboxes) |
340a614a | 459 | return -EINVAL; |
340a614a | 460 | |
72c1c817 | 461 | mboxes = mdev->mboxes; |
9c80c8cd FC |
462 | for (i = 0; mboxes[i]; i++) { |
463 | struct omap_mbox *mbox = mboxes[i]; | |
2665a4c1 | 464 | |
8841a66a SA |
465 | mbox->dev = device_create(&omap_mbox_class, mdev->dev, |
466 | 0, mbox, "%s", mbox->name); | |
9c80c8cd FC |
467 | if (IS_ERR(mbox->dev)) { |
468 | ret = PTR_ERR(mbox->dev); | |
469 | goto err_out; | |
470 | } | |
471 | } | |
72c1c817 SA |
472 | |
473 | mutex_lock(&omap_mbox_devices_lock); | |
474 | list_add(&mdev->elem, &omap_mbox_devices); | |
475 | mutex_unlock(&omap_mbox_devices_lock); | |
476 | ||
a3abf436 | 477 | ret = devm_mbox_controller_register(mdev->dev, &mdev->controller); |
f48cca87 | 478 | |
9c80c8cd | 479 | err_out: |
8841a66a SA |
480 | if (ret) { |
481 | while (i--) | |
482 | device_unregister(mboxes[i]->dev); | |
483 | } | |
340a614a HD |
484 | return ret; |
485 | } | |
340a614a | 486 | |
72c1c817 | 487 | static int omap_mbox_unregister(struct omap_mbox_device *mdev) |
340a614a | 488 | { |
9c80c8cd | 489 | int i; |
72c1c817 | 490 | struct omap_mbox **mboxes; |
340a614a | 491 | |
72c1c817 | 492 | if (!mdev || !mdev->mboxes) |
9c80c8cd FC |
493 | return -EINVAL; |
494 | ||
72c1c817 SA |
495 | mutex_lock(&omap_mbox_devices_lock); |
496 | list_del(&mdev->elem); | |
497 | mutex_unlock(&omap_mbox_devices_lock); | |
498 | ||
499 | mboxes = mdev->mboxes; | |
9c80c8cd FC |
500 | for (i = 0; mboxes[i]; i++) |
501 | device_unregister(mboxes[i]->dev); | |
9c80c8cd | 502 | return 0; |
340a614a | 503 | } |
5040f534 | 504 | |
8841a66a SA |
505 | static int omap_mbox_chan_startup(struct mbox_chan *chan) |
506 | { | |
507 | struct omap_mbox *mbox = mbox_chan_to_omap_mbox(chan); | |
508 | struct omap_mbox_device *mdev = mbox->parent; | |
509 | int ret = 0; | |
510 | ||
511 | mutex_lock(&mdev->cfg_lock); | |
512 | pm_runtime_get_sync(mdev->dev); | |
513 | ret = omap_mbox_startup(mbox); | |
514 | if (ret) | |
515 | pm_runtime_put_sync(mdev->dev); | |
516 | mutex_unlock(&mdev->cfg_lock); | |
517 | return ret; | |
518 | } | |
519 | ||
520 | static void omap_mbox_chan_shutdown(struct mbox_chan *chan) | |
521 | { | |
522 | struct omap_mbox *mbox = mbox_chan_to_omap_mbox(chan); | |
523 | struct omap_mbox_device *mdev = mbox->parent; | |
524 | ||
525 | mutex_lock(&mdev->cfg_lock); | |
526 | omap_mbox_fini(mbox); | |
527 | pm_runtime_put_sync(mdev->dev); | |
528 | mutex_unlock(&mdev->cfg_lock); | |
529 | } | |
530 | ||
9c1f2a5d | 531 | static int omap_mbox_chan_send_noirq(struct omap_mbox *mbox, u32 msg) |
8841a66a | 532 | { |
8841a66a SA |
533 | int ret = -EBUSY; |
534 | ||
8e3c5952 DG |
535 | if (!mbox_fifo_full(mbox)) { |
536 | _omap_mbox_enable_irq(mbox, IRQ_RX); | |
9c1f2a5d | 537 | mbox_fifo_write(mbox, msg); |
8e3c5952 DG |
538 | ret = 0; |
539 | _omap_mbox_disable_irq(mbox, IRQ_RX); | |
540 | ||
541 | /* we must read and ack the interrupt directly from here */ | |
542 | mbox_fifo_read(mbox); | |
543 | ack_mbox_irq(mbox, IRQ_RX); | |
544 | } | |
545 | ||
546 | return ret; | |
547 | } | |
548 | ||
9c1f2a5d | 549 | static int omap_mbox_chan_send(struct omap_mbox *mbox, u32 msg) |
8e3c5952 DG |
550 | { |
551 | int ret = -EBUSY; | |
8841a66a SA |
552 | |
553 | if (!mbox_fifo_full(mbox)) { | |
9c1f2a5d | 554 | mbox_fifo_write(mbox, msg); |
8841a66a SA |
555 | ret = 0; |
556 | } | |
557 | ||
558 | /* always enable the interrupt */ | |
559 | _omap_mbox_enable_irq(mbox, IRQ_TX); | |
560 | return ret; | |
561 | } | |
562 | ||
8e3c5952 DG |
563 | static int omap_mbox_chan_send_data(struct mbox_chan *chan, void *data) |
564 | { | |
565 | struct omap_mbox *mbox = mbox_chan_to_omap_mbox(chan); | |
566 | int ret; | |
9c1f2a5d | 567 | u32 msg = omap_mbox_message(data); |
8e3c5952 DG |
568 | |
569 | if (!mbox) | |
570 | return -EINVAL; | |
571 | ||
572 | if (mbox->send_no_irq) | |
9c1f2a5d | 573 | ret = omap_mbox_chan_send_noirq(mbox, msg); |
8e3c5952 | 574 | else |
9c1f2a5d | 575 | ret = omap_mbox_chan_send(mbox, msg); |
8e3c5952 DG |
576 | |
577 | return ret; | |
578 | } | |
579 | ||
05ae7975 | 580 | static const struct mbox_chan_ops omap_mbox_chan_ops = { |
8841a66a SA |
581 | .startup = omap_mbox_chan_startup, |
582 | .send_data = omap_mbox_chan_send_data, | |
583 | .shutdown = omap_mbox_chan_shutdown, | |
584 | }; | |
585 | ||
af1d2f5c SA |
586 | #ifdef CONFIG_PM_SLEEP |
587 | static int omap_mbox_suspend(struct device *dev) | |
588 | { | |
589 | struct omap_mbox_device *mdev = dev_get_drvdata(dev); | |
9f0cee98 | 590 | u32 usr, fifo, reg; |
af1d2f5c SA |
591 | |
592 | if (pm_runtime_status_suspended(dev)) | |
593 | return 0; | |
594 | ||
9f0cee98 SA |
595 | for (fifo = 0; fifo < mdev->num_fifos; fifo++) { |
596 | if (mbox_read_reg(mdev, MAILBOX_MSGSTATUS(fifo))) { | |
597 | dev_err(mdev->dev, "fifo %d has unexpected unread messages\n", | |
598 | fifo); | |
599 | return -EBUSY; | |
600 | } | |
601 | } | |
602 | ||
af1d2f5c SA |
603 | for (usr = 0; usr < mdev->num_users; usr++) { |
604 | reg = MAILBOX_IRQENABLE(mdev->intr_type, usr); | |
605 | mdev->irq_ctx[usr] = mbox_read_reg(mdev, reg); | |
606 | } | |
607 | ||
608 | return 0; | |
609 | } | |
610 | ||
611 | static int omap_mbox_resume(struct device *dev) | |
612 | { | |
613 | struct omap_mbox_device *mdev = dev_get_drvdata(dev); | |
614 | u32 usr, reg; | |
615 | ||
616 | if (pm_runtime_status_suspended(dev)) | |
617 | return 0; | |
618 | ||
619 | for (usr = 0; usr < mdev->num_users; usr++) { | |
620 | reg = MAILBOX_IRQENABLE(mdev->intr_type, usr); | |
621 | mbox_write_reg(mdev, mdev->irq_ctx[usr], reg); | |
622 | } | |
623 | ||
624 | return 0; | |
625 | } | |
626 | #endif | |
627 | ||
628 | static const struct dev_pm_ops omap_mbox_pm_ops = { | |
629 | SET_SYSTEM_SLEEP_PM_OPS(omap_mbox_suspend, omap_mbox_resume) | |
630 | }; | |
631 | ||
ea2ec1e8 SA |
632 | static const struct omap_mbox_match_data omap2_data = { MBOX_INTR_CFG_TYPE1 }; |
633 | static const struct omap_mbox_match_data omap4_data = { MBOX_INTR_CFG_TYPE2 }; | |
634 | ||
75288cc6 SA |
635 | static const struct of_device_id omap_mailbox_of_match[] = { |
636 | { | |
637 | .compatible = "ti,omap2-mailbox", | |
ea2ec1e8 | 638 | .data = &omap2_data, |
75288cc6 SA |
639 | }, |
640 | { | |
641 | .compatible = "ti,omap3-mailbox", | |
ea2ec1e8 | 642 | .data = &omap2_data, |
75288cc6 SA |
643 | }, |
644 | { | |
645 | .compatible = "ti,omap4-mailbox", | |
ea2ec1e8 | 646 | .data = &omap4_data, |
75288cc6 | 647 | }, |
9c1f2a5d SA |
648 | { |
649 | .compatible = "ti,am654-mailbox", | |
650 | .data = &omap4_data, | |
651 | }, | |
595be654 SA |
652 | { |
653 | .compatible = "ti,am64-mailbox", | |
654 | .data = &omap4_data, | |
655 | }, | |
75288cc6 SA |
656 | { |
657 | /* end */ | |
658 | }, | |
659 | }; | |
660 | MODULE_DEVICE_TABLE(of, omap_mailbox_of_match); | |
661 | ||
8841a66a SA |
662 | static struct mbox_chan *omap_mbox_of_xlate(struct mbox_controller *controller, |
663 | const struct of_phandle_args *sp) | |
664 | { | |
665 | phandle phandle = sp->args[0]; | |
666 | struct device_node *node; | |
667 | struct omap_mbox_device *mdev; | |
668 | struct omap_mbox *mbox; | |
669 | ||
670 | mdev = container_of(controller, struct omap_mbox_device, controller); | |
671 | if (WARN_ON(!mdev)) | |
2d805fc1 | 672 | return ERR_PTR(-EINVAL); |
8841a66a SA |
673 | |
674 | node = of_find_node_by_phandle(phandle); | |
675 | if (!node) { | |
676 | pr_err("%s: could not find node phandle 0x%x\n", | |
677 | __func__, phandle); | |
2d805fc1 | 678 | return ERR_PTR(-ENODEV); |
8841a66a SA |
679 | } |
680 | ||
681 | mbox = omap_mbox_device_find(mdev, node->name); | |
682 | of_node_put(node); | |
2d805fc1 | 683 | return mbox ? mbox->chan : ERR_PTR(-ENOENT); |
8841a66a SA |
684 | } |
685 | ||
5040f534 SA |
686 | static int omap_mbox_probe(struct platform_device *pdev) |
687 | { | |
5040f534 | 688 | int ret; |
8841a66a | 689 | struct mbox_chan *chnls; |
5040f534 | 690 | struct omap_mbox **list, *mbox, *mboxblk; |
75288cc6 | 691 | struct omap_mbox_fifo_info *finfo, *finfoblk; |
72c1c817 | 692 | struct omap_mbox_device *mdev; |
be3322eb | 693 | struct omap_mbox_fifo *fifo; |
75288cc6 SA |
694 | struct device_node *node = pdev->dev.of_node; |
695 | struct device_node *child; | |
ea2ec1e8 | 696 | const struct omap_mbox_match_data *match_data; |
75288cc6 SA |
697 | u32 intr_type, info_count; |
698 | u32 num_users, num_fifos; | |
699 | u32 tmp[3]; | |
5040f534 SA |
700 | u32 l; |
701 | int i; | |
702 | ||
4899f78a SA |
703 | if (!node) { |
704 | pr_err("%s: only DT-based devices are supported\n", __func__); | |
5040f534 SA |
705 | return -ENODEV; |
706 | } | |
707 | ||
ea2ec1e8 SA |
708 | match_data = of_device_get_match_data(&pdev->dev); |
709 | if (!match_data) | |
4899f78a | 710 | return -ENODEV; |
ea2ec1e8 | 711 | intr_type = match_data->intr_type; |
75288cc6 | 712 | |
4899f78a SA |
713 | if (of_property_read_u32(node, "ti,mbox-num-users", &num_users)) |
714 | return -ENODEV; | |
75288cc6 | 715 | |
4899f78a SA |
716 | if (of_property_read_u32(node, "ti,mbox-num-fifos", &num_fifos)) |
717 | return -ENODEV; | |
75288cc6 | 718 | |
4899f78a SA |
719 | info_count = of_get_available_child_count(node); |
720 | if (!info_count) { | |
721 | dev_err(&pdev->dev, "no available mbox devices found\n"); | |
722 | return -ENODEV; | |
75288cc6 SA |
723 | } |
724 | ||
a86854d0 | 725 | finfoblk = devm_kcalloc(&pdev->dev, info_count, sizeof(*finfoblk), |
75288cc6 SA |
726 | GFP_KERNEL); |
727 | if (!finfoblk) | |
728 | return -ENOMEM; | |
729 | ||
730 | finfo = finfoblk; | |
731 | child = NULL; | |
732 | for (i = 0; i < info_count; i++, finfo++) { | |
4899f78a SA |
733 | child = of_get_next_available_child(node, child); |
734 | ret = of_property_read_u32_array(child, "ti,mbox-tx", tmp, | |
735 | ARRAY_SIZE(tmp)); | |
736 | if (ret) | |
737 | return ret; | |
738 | finfo->tx_id = tmp[0]; | |
739 | finfo->tx_irq = tmp[1]; | |
740 | finfo->tx_usr = tmp[2]; | |
741 | ||
742 | ret = of_property_read_u32_array(child, "ti,mbox-rx", tmp, | |
743 | ARRAY_SIZE(tmp)); | |
744 | if (ret) | |
745 | return ret; | |
746 | finfo->rx_id = tmp[0]; | |
747 | finfo->rx_irq = tmp[1]; | |
748 | finfo->rx_usr = tmp[2]; | |
749 | ||
750 | finfo->name = child->name; | |
751 | ||
2a61e7b7 | 752 | finfo->send_no_irq = of_property_read_bool(child, "ti,mbox-send-noirq"); |
4899f78a | 753 | |
75288cc6 SA |
754 | if (finfo->tx_id >= num_fifos || finfo->rx_id >= num_fifos || |
755 | finfo->tx_usr >= num_users || finfo->rx_usr >= num_users) | |
756 | return -EINVAL; | |
757 | } | |
758 | ||
72c1c817 SA |
759 | mdev = devm_kzalloc(&pdev->dev, sizeof(*mdev), GFP_KERNEL); |
760 | if (!mdev) | |
761 | return -ENOMEM; | |
762 | ||
6bb9e5ee | 763 | mdev->mbox_base = devm_platform_ioremap_resource(pdev, 0); |
72c1c817 SA |
764 | if (IS_ERR(mdev->mbox_base)) |
765 | return PTR_ERR(mdev->mbox_base); | |
766 | ||
a86854d0 | 767 | mdev->irq_ctx = devm_kcalloc(&pdev->dev, num_users, sizeof(u32), |
af1d2f5c SA |
768 | GFP_KERNEL); |
769 | if (!mdev->irq_ctx) | |
770 | return -ENOMEM; | |
771 | ||
5040f534 | 772 | /* allocate one extra for marking end of list */ |
a86854d0 | 773 | list = devm_kcalloc(&pdev->dev, info_count + 1, sizeof(*list), |
5040f534 SA |
774 | GFP_KERNEL); |
775 | if (!list) | |
776 | return -ENOMEM; | |
777 | ||
a86854d0 | 778 | chnls = devm_kcalloc(&pdev->dev, info_count + 1, sizeof(*chnls), |
8841a66a SA |
779 | GFP_KERNEL); |
780 | if (!chnls) | |
781 | return -ENOMEM; | |
782 | ||
a86854d0 | 783 | mboxblk = devm_kcalloc(&pdev->dev, info_count, sizeof(*mbox), |
5040f534 SA |
784 | GFP_KERNEL); |
785 | if (!mboxblk) | |
786 | return -ENOMEM; | |
787 | ||
5040f534 | 788 | mbox = mboxblk; |
75288cc6 SA |
789 | finfo = finfoblk; |
790 | for (i = 0; i < info_count; i++, finfo++) { | |
be3322eb | 791 | fifo = &mbox->tx_fifo; |
75288cc6 SA |
792 | fifo->msg = MAILBOX_MESSAGE(finfo->tx_id); |
793 | fifo->fifo_stat = MAILBOX_FIFOSTATUS(finfo->tx_id); | |
794 | fifo->intr_bit = MAILBOX_IRQ_NOTFULL(finfo->tx_id); | |
795 | fifo->irqenable = MAILBOX_IRQENABLE(intr_type, finfo->tx_usr); | |
796 | fifo->irqstatus = MAILBOX_IRQSTATUS(intr_type, finfo->tx_usr); | |
797 | fifo->irqdisable = MAILBOX_IRQDISABLE(intr_type, finfo->tx_usr); | |
be3322eb SA |
798 | |
799 | fifo = &mbox->rx_fifo; | |
75288cc6 SA |
800 | fifo->msg = MAILBOX_MESSAGE(finfo->rx_id); |
801 | fifo->msg_stat = MAILBOX_MSGSTATUS(finfo->rx_id); | |
802 | fifo->intr_bit = MAILBOX_IRQ_NEWMSG(finfo->rx_id); | |
803 | fifo->irqenable = MAILBOX_IRQENABLE(intr_type, finfo->rx_usr); | |
804 | fifo->irqstatus = MAILBOX_IRQSTATUS(intr_type, finfo->rx_usr); | |
805 | fifo->irqdisable = MAILBOX_IRQDISABLE(intr_type, finfo->rx_usr); | |
be3322eb | 806 | |
8e3c5952 | 807 | mbox->send_no_irq = finfo->send_no_irq; |
be3322eb SA |
808 | mbox->intr_type = intr_type; |
809 | ||
72c1c817 | 810 | mbox->parent = mdev; |
75288cc6 SA |
811 | mbox->name = finfo->name; |
812 | mbox->irq = platform_get_irq(pdev, finfo->tx_irq); | |
5040f534 SA |
813 | if (mbox->irq < 0) |
814 | return mbox->irq; | |
8841a66a SA |
815 | mbox->chan = &chnls[i]; |
816 | chnls[i].con_priv = mbox; | |
5040f534 SA |
817 | list[i] = mbox++; |
818 | } | |
819 | ||
72c1c817 SA |
820 | mutex_init(&mdev->cfg_lock); |
821 | mdev->dev = &pdev->dev; | |
75288cc6 SA |
822 | mdev->num_users = num_users; |
823 | mdev->num_fifos = num_fifos; | |
2240f8ae | 824 | mdev->intr_type = intr_type; |
72c1c817 | 825 | mdev->mboxes = list; |
8841a66a | 826 | |
9c1f2a5d SA |
827 | /* |
828 | * OMAP/K3 Mailbox IP does not have a Tx-Done IRQ, but rather a Tx-Ready | |
829 | * IRQ and is needed to run the Tx state machine | |
830 | */ | |
8841a66a SA |
831 | mdev->controller.txdone_irq = true; |
832 | mdev->controller.dev = mdev->dev; | |
833 | mdev->controller.ops = &omap_mbox_chan_ops; | |
834 | mdev->controller.chans = chnls; | |
835 | mdev->controller.num_chans = info_count; | |
836 | mdev->controller.of_xlate = omap_mbox_of_xlate; | |
72c1c817 | 837 | ret = omap_mbox_register(mdev); |
5040f534 SA |
838 | if (ret) |
839 | return ret; | |
840 | ||
72c1c817 SA |
841 | platform_set_drvdata(pdev, mdev); |
842 | pm_runtime_enable(mdev->dev); | |
5040f534 | 843 | |
d9512696 | 844 | ret = pm_runtime_resume_and_get(mdev->dev); |
845 | if (ret < 0) | |
5040f534 | 846 | goto unregister; |
5040f534 SA |
847 | |
848 | /* | |
849 | * just print the raw revision register, the format is not | |
850 | * uniform across all SoCs | |
851 | */ | |
72c1c817 SA |
852 | l = mbox_read_reg(mdev, MAILBOX_REVISION); |
853 | dev_info(mdev->dev, "omap mailbox rev 0x%x\n", l); | |
5040f534 | 854 | |
72c1c817 | 855 | ret = pm_runtime_put_sync(mdev->dev); |
0434d3f4 | 856 | if (ret < 0 && ret != -ENOSYS) |
5040f534 SA |
857 | goto unregister; |
858 | ||
75288cc6 | 859 | devm_kfree(&pdev->dev, finfoblk); |
5040f534 SA |
860 | return 0; |
861 | ||
862 | unregister: | |
72c1c817 SA |
863 | pm_runtime_disable(mdev->dev); |
864 | omap_mbox_unregister(mdev); | |
5040f534 SA |
865 | return ret; |
866 | } | |
867 | ||
868 | static int omap_mbox_remove(struct platform_device *pdev) | |
869 | { | |
72c1c817 SA |
870 | struct omap_mbox_device *mdev = platform_get_drvdata(pdev); |
871 | ||
872 | pm_runtime_disable(mdev->dev); | |
873 | omap_mbox_unregister(mdev); | |
5040f534 SA |
874 | |
875 | return 0; | |
876 | } | |
877 | ||
878 | static struct platform_driver omap_mbox_driver = { | |
879 | .probe = omap_mbox_probe, | |
880 | .remove = omap_mbox_remove, | |
881 | .driver = { | |
882 | .name = "omap-mailbox", | |
af1d2f5c | 883 | .pm = &omap_mbox_pm_ops, |
75288cc6 | 884 | .of_match_table = of_match_ptr(omap_mailbox_of_match), |
5040f534 SA |
885 | }, |
886 | }; | |
340a614a | 887 | |
c7c158e5 | 888 | static int __init omap_mbox_init(void) |
340a614a | 889 | { |
6b233985 HD |
890 | int err; |
891 | ||
892 | err = class_register(&omap_mbox_class); | |
893 | if (err) | |
894 | return err; | |
895 | ||
b5bebe41 | 896 | /* kfifo size sanity check: alignment and minimal size */ |
9c1f2a5d SA |
897 | mbox_kfifo_size = ALIGN(mbox_kfifo_size, sizeof(u32)); |
898 | mbox_kfifo_size = max_t(unsigned int, mbox_kfifo_size, sizeof(u32)); | |
b5bebe41 | 899 | |
1f90a216 AY |
900 | err = platform_driver_register(&omap_mbox_driver); |
901 | if (err) | |
902 | class_unregister(&omap_mbox_class); | |
903 | ||
904 | return err; | |
340a614a | 905 | } |
6b233985 | 906 | subsys_initcall(omap_mbox_init); |
340a614a | 907 | |
c7c158e5 | 908 | static void __exit omap_mbox_exit(void) |
340a614a | 909 | { |
5040f534 | 910 | platform_driver_unregister(&omap_mbox_driver); |
6b233985 | 911 | class_unregister(&omap_mbox_class); |
340a614a | 912 | } |
c7c158e5 | 913 | module_exit(omap_mbox_exit); |
340a614a | 914 | |
f48cca87 HD |
915 | MODULE_LICENSE("GPL v2"); |
916 | MODULE_DESCRIPTION("omap mailbox: interrupt driven messaging"); | |
f375325a OBC |
917 | MODULE_AUTHOR("Toshihiro Kobayashi"); |
918 | MODULE_AUTHOR("Hiroshi DOYU"); |