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