Commit | Line | Data |
---|---|---|
340a614a HD |
1 | /* |
2 | * OMAP mailbox driver | |
3 | * | |
f48cca87 | 4 | * Copyright (C) 2006-2009 Nokia Corporation. All rights reserved. |
340a614a | 5 | * |
f48cca87 | 6 | * Contact: Hiroshi DOYU <Hiroshi.DOYU@nokia.com> |
340a614a HD |
7 | * |
8 | * This program is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU General Public License | |
10 | * version 2 as published by the Free Software Foundation. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, but | |
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | * General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with this program; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | |
20 | * 02110-1301 USA | |
21 | * | |
22 | */ | |
23 | ||
340a614a | 24 | #include <linux/module.h> |
340a614a HD |
25 | #include <linux/interrupt.h> |
26 | #include <linux/device.h> | |
340a614a | 27 | #include <linux/delay.h> |
8dff0fa5 | 28 | |
a09e64fb | 29 | #include <mach/mailbox.h> |
340a614a | 30 | |
7a781afd HD |
31 | static int enable_seq_bit; |
32 | module_param(enable_seq_bit, bool, 0); | |
33 | MODULE_PARM_DESC(enable_seq_bit, "Enable sequence bit checking."); | |
34 | ||
340a614a HD |
35 | static struct omap_mbox *mboxes; |
36 | static DEFINE_RWLOCK(mboxes_lock); | |
37 | ||
9ae0ee00 HD |
38 | /* |
39 | * Mailbox sequence bit API | |
40 | */ | |
9ae0ee00 | 41 | |
9ae0ee00 HD |
42 | /* seq_rcv should be initialized with any value other than |
43 | * 0 and 1 << 31, to allow either value for the first | |
44 | * message. */ | |
45 | static inline void mbox_seq_init(struct omap_mbox *mbox) | |
46 | { | |
7a781afd HD |
47 | if (!enable_seq_bit) |
48 | return; | |
49 | ||
9ae0ee00 HD |
50 | /* any value other than 0 and 1 << 31 */ |
51 | mbox->seq_rcv = 0xffffffff; | |
52 | } | |
53 | ||
54 | static inline void mbox_seq_toggle(struct omap_mbox *mbox, mbox_msg_t * msg) | |
55 | { | |
7a781afd HD |
56 | if (!enable_seq_bit) |
57 | return; | |
58 | ||
9ae0ee00 HD |
59 | /* add seq_snd to msg */ |
60 | *msg = (*msg & 0x7fffffff) | mbox->seq_snd; | |
61 | /* flip seq_snd */ | |
62 | mbox->seq_snd ^= 1 << 31; | |
63 | } | |
64 | ||
65 | static inline int mbox_seq_test(struct omap_mbox *mbox, mbox_msg_t msg) | |
66 | { | |
7a781afd HD |
67 | mbox_msg_t seq; |
68 | ||
69 | if (!enable_seq_bit) | |
70 | return 0; | |
71 | ||
72 | seq = msg & (1 << 31); | |
9ae0ee00 HD |
73 | if (seq == mbox->seq_rcv) |
74 | return -1; | |
75 | mbox->seq_rcv = seq; | |
76 | return 0; | |
77 | } | |
9ae0ee00 HD |
78 | |
79 | /* Mailbox FIFO handle functions */ | |
80 | static inline mbox_msg_t mbox_fifo_read(struct omap_mbox *mbox) | |
81 | { | |
82 | return mbox->ops->fifo_read(mbox); | |
83 | } | |
84 | static inline void mbox_fifo_write(struct omap_mbox *mbox, mbox_msg_t msg) | |
85 | { | |
86 | mbox->ops->fifo_write(mbox, msg); | |
87 | } | |
88 | static inline int mbox_fifo_empty(struct omap_mbox *mbox) | |
89 | { | |
90 | return mbox->ops->fifo_empty(mbox); | |
91 | } | |
92 | static inline int mbox_fifo_full(struct omap_mbox *mbox) | |
93 | { | |
94 | return mbox->ops->fifo_full(mbox); | |
95 | } | |
96 | ||
97 | /* Mailbox IRQ handle functions */ | |
98 | static inline void enable_mbox_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) | |
99 | { | |
100 | mbox->ops->enable_irq(mbox, irq); | |
101 | } | |
102 | static inline void disable_mbox_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) | |
103 | { | |
104 | mbox->ops->disable_irq(mbox, irq); | |
105 | } | |
106 | static inline void ack_mbox_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) | |
107 | { | |
108 | if (mbox->ops->ack_irq) | |
109 | mbox->ops->ack_irq(mbox, irq); | |
110 | } | |
111 | static inline int is_mbox_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) | |
112 | { | |
113 | return mbox->ops->is_irq(mbox, irq); | |
114 | } | |
115 | ||
340a614a HD |
116 | /* Mailbox Sequence Bit function */ |
117 | void omap_mbox_init_seq(struct omap_mbox *mbox) | |
118 | { | |
119 | mbox_seq_init(mbox); | |
120 | } | |
121 | EXPORT_SYMBOL(omap_mbox_init_seq); | |
122 | ||
123 | /* | |
124 | * message sender | |
125 | */ | |
126 | static int __mbox_msg_send(struct omap_mbox *mbox, mbox_msg_t msg, void *arg) | |
127 | { | |
128 | int ret = 0, i = 1000; | |
129 | ||
130 | while (mbox_fifo_full(mbox)) { | |
131 | if (mbox->ops->type == OMAP_MBOX_TYPE2) | |
132 | return -1; | |
133 | if (--i == 0) | |
134 | return -1; | |
135 | udelay(1); | |
136 | } | |
137 | ||
138 | if (arg && mbox->txq->callback) { | |
139 | ret = mbox->txq->callback(arg); | |
140 | if (ret) | |
141 | goto out; | |
142 | } | |
143 | ||
144 | mbox_seq_toggle(mbox, &msg); | |
145 | mbox_fifo_write(mbox, msg); | |
146 | out: | |
147 | return ret; | |
148 | } | |
149 | ||
ec24751a TH |
150 | struct omap_msg_tx_data { |
151 | mbox_msg_t msg; | |
152 | void *arg; | |
153 | }; | |
154 | ||
155 | static void omap_msg_tx_end_io(struct request *rq, int error) | |
156 | { | |
157 | kfree(rq->special); | |
158 | __blk_put_request(rq->q, rq); | |
159 | } | |
160 | ||
340a614a HD |
161 | int omap_mbox_msg_send(struct omap_mbox *mbox, mbox_msg_t msg, void* arg) |
162 | { | |
ec24751a | 163 | struct omap_msg_tx_data *tx_data; |
340a614a HD |
164 | struct request *rq; |
165 | struct request_queue *q = mbox->txq->queue; | |
ec24751a TH |
166 | |
167 | tx_data = kmalloc(sizeof(*tx_data), GFP_ATOMIC); | |
168 | if (unlikely(!tx_data)) | |
169 | return -ENOMEM; | |
340a614a HD |
170 | |
171 | rq = blk_get_request(q, WRITE, GFP_ATOMIC); | |
172 | if (unlikely(!rq)) { | |
ec24751a TH |
173 | kfree(tx_data); |
174 | return -ENOMEM; | |
340a614a HD |
175 | } |
176 | ||
ec24751a TH |
177 | tx_data->msg = msg; |
178 | tx_data->arg = arg; | |
179 | rq->end_io = omap_msg_tx_end_io; | |
180 | blk_insert_request(q, rq, 0, tx_data); | |
340a614a HD |
181 | |
182 | schedule_work(&mbox->txq->work); | |
ec24751a | 183 | return 0; |
340a614a HD |
184 | } |
185 | EXPORT_SYMBOL(omap_mbox_msg_send); | |
186 | ||
187 | static void mbox_tx_work(struct work_struct *work) | |
188 | { | |
189 | int ret; | |
190 | struct request *rq; | |
191 | struct omap_mbox_queue *mq = container_of(work, | |
192 | struct omap_mbox_queue, work); | |
193 | struct omap_mbox *mbox = mq->queue->queuedata; | |
194 | struct request_queue *q = mbox->txq->queue; | |
195 | ||
196 | while (1) { | |
ec24751a TH |
197 | struct omap_msg_tx_data *tx_data; |
198 | ||
340a614a | 199 | spin_lock(q->queue_lock); |
9934c8c0 | 200 | rq = blk_fetch_request(q); |
340a614a HD |
201 | spin_unlock(q->queue_lock); |
202 | ||
203 | if (!rq) | |
204 | break; | |
205 | ||
ec24751a TH |
206 | tx_data = rq->special; |
207 | ||
208 | ret = __mbox_msg_send(mbox, tx_data->msg, tx_data->arg); | |
340a614a HD |
209 | if (ret) { |
210 | enable_mbox_irq(mbox, IRQ_TX); | |
296b2f6a TH |
211 | spin_lock(q->queue_lock); |
212 | blk_requeue_request(q, rq); | |
213 | spin_unlock(q->queue_lock); | |
340a614a HD |
214 | return; |
215 | } | |
216 | ||
217 | spin_lock(q->queue_lock); | |
40cbbb78 | 218 | __blk_end_request_all(rq, 0); |
340a614a HD |
219 | spin_unlock(q->queue_lock); |
220 | } | |
221 | } | |
222 | ||
223 | /* | |
224 | * Message receiver(workqueue) | |
225 | */ | |
226 | static void mbox_rx_work(struct work_struct *work) | |
227 | { | |
228 | struct omap_mbox_queue *mq = | |
229 | container_of(work, struct omap_mbox_queue, work); | |
230 | struct omap_mbox *mbox = mq->queue->queuedata; | |
231 | struct request_queue *q = mbox->rxq->queue; | |
232 | struct request *rq; | |
233 | mbox_msg_t msg; | |
234 | unsigned long flags; | |
235 | ||
236 | if (mbox->rxq->callback == NULL) { | |
f48cca87 | 237 | sysfs_notify(&mbox->dev->kobj, NULL, "mbox"); |
340a614a HD |
238 | return; |
239 | } | |
240 | ||
241 | while (1) { | |
242 | spin_lock_irqsave(q->queue_lock, flags); | |
9934c8c0 | 243 | rq = blk_fetch_request(q); |
340a614a HD |
244 | spin_unlock_irqrestore(q->queue_lock, flags); |
245 | if (!rq) | |
246 | break; | |
247 | ||
ec24751a | 248 | msg = (mbox_msg_t)rq->special; |
40cbbb78 | 249 | blk_end_request_all(rq, 0); |
340a614a HD |
250 | mbox->rxq->callback((void *)msg); |
251 | } | |
252 | } | |
253 | ||
254 | /* | |
255 | * Mailbox interrupt handler | |
256 | */ | |
165125e1 | 257 | static void mbox_txq_fn(struct request_queue * q) |
340a614a HD |
258 | { |
259 | } | |
260 | ||
165125e1 | 261 | static void mbox_rxq_fn(struct request_queue * q) |
340a614a HD |
262 | { |
263 | } | |
264 | ||
265 | static void __mbox_tx_interrupt(struct omap_mbox *mbox) | |
266 | { | |
267 | disable_mbox_irq(mbox, IRQ_TX); | |
268 | ack_mbox_irq(mbox, IRQ_TX); | |
269 | schedule_work(&mbox->txq->work); | |
270 | } | |
271 | ||
272 | static void __mbox_rx_interrupt(struct omap_mbox *mbox) | |
273 | { | |
274 | struct request *rq; | |
275 | mbox_msg_t msg; | |
165125e1 | 276 | struct request_queue *q = mbox->rxq->queue; |
340a614a HD |
277 | |
278 | disable_mbox_irq(mbox, IRQ_RX); | |
279 | ||
280 | while (!mbox_fifo_empty(mbox)) { | |
281 | rq = blk_get_request(q, WRITE, GFP_ATOMIC); | |
282 | if (unlikely(!rq)) | |
283 | goto nomem; | |
284 | ||
285 | msg = mbox_fifo_read(mbox); | |
340a614a HD |
286 | |
287 | if (unlikely(mbox_seq_test(mbox, msg))) { | |
288 | pr_info("mbox: Illegal seq bit!(%08x)\n", msg); | |
289 | if (mbox->err_notify) | |
290 | mbox->err_notify(); | |
291 | } | |
292 | ||
ec24751a | 293 | blk_insert_request(q, rq, 0, (void *)msg); |
340a614a HD |
294 | if (mbox->ops->type == OMAP_MBOX_TYPE1) |
295 | break; | |
296 | } | |
297 | ||
298 | /* no more messages in the fifo. clear IRQ source. */ | |
299 | ack_mbox_irq(mbox, IRQ_RX); | |
300 | enable_mbox_irq(mbox, IRQ_RX); | |
f48cca87 | 301 | nomem: |
340a614a HD |
302 | schedule_work(&mbox->rxq->work); |
303 | } | |
304 | ||
305 | static irqreturn_t mbox_interrupt(int irq, void *p) | |
306 | { | |
2a7057e3 | 307 | struct omap_mbox *mbox = p; |
340a614a HD |
308 | |
309 | if (is_mbox_irq(mbox, IRQ_TX)) | |
310 | __mbox_tx_interrupt(mbox); | |
311 | ||
312 | if (is_mbox_irq(mbox, IRQ_RX)) | |
313 | __mbox_rx_interrupt(mbox); | |
314 | ||
315 | return IRQ_HANDLED; | |
316 | } | |
317 | ||
318 | /* | |
319 | * sysfs files | |
320 | */ | |
321 | static ssize_t | |
322 | omap_mbox_write(struct device *dev, struct device_attribute *attr, | |
323 | const char * buf, size_t count) | |
324 | { | |
325 | int ret; | |
326 | mbox_msg_t *p = (mbox_msg_t *)buf; | |
327 | struct omap_mbox *mbox = dev_get_drvdata(dev); | |
328 | ||
329 | for (; count >= sizeof(mbox_msg_t); count -= sizeof(mbox_msg_t)) { | |
330 | ret = omap_mbox_msg_send(mbox, be32_to_cpu(*p), NULL); | |
331 | if (ret) | |
332 | return -EAGAIN; | |
333 | p++; | |
334 | } | |
335 | ||
336 | return (size_t)((char *)p - buf); | |
337 | } | |
338 | ||
339 | static ssize_t | |
340 | omap_mbox_read(struct device *dev, struct device_attribute *attr, char *buf) | |
341 | { | |
342 | unsigned long flags; | |
343 | struct request *rq; | |
344 | mbox_msg_t *p = (mbox_msg_t *) buf; | |
345 | struct omap_mbox *mbox = dev_get_drvdata(dev); | |
346 | struct request_queue *q = mbox->rxq->queue; | |
347 | ||
348 | while (1) { | |
349 | spin_lock_irqsave(q->queue_lock, flags); | |
9934c8c0 | 350 | rq = blk_fetch_request(q); |
340a614a HD |
351 | spin_unlock_irqrestore(q->queue_lock, flags); |
352 | ||
353 | if (!rq) | |
354 | break; | |
355 | ||
ec24751a | 356 | *p = (mbox_msg_t)rq->special; |
340a614a | 357 | |
40cbbb78 | 358 | blk_end_request_all(rq, 0); |
340a614a HD |
359 | |
360 | if (unlikely(mbox_seq_test(mbox, *p))) { | |
361 | pr_info("mbox: Illegal seq bit!(%08x) ignored\n", *p); | |
362 | continue; | |
363 | } | |
364 | p++; | |
365 | } | |
366 | ||
367 | pr_debug("%02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]); | |
368 | ||
369 | return (size_t) ((char *)p - buf); | |
370 | } | |
371 | ||
372 | static DEVICE_ATTR(mbox, S_IRUGO | S_IWUSR, omap_mbox_read, omap_mbox_write); | |
373 | ||
374 | static ssize_t mbox_show(struct class *class, char *buf) | |
375 | { | |
376 | return sprintf(buf, "mbox"); | |
377 | } | |
378 | ||
379 | static CLASS_ATTR(mbox, S_IRUGO, mbox_show, NULL); | |
380 | ||
381 | static struct class omap_mbox_class = { | |
f48cca87 | 382 | .name = "omap-mailbox", |
340a614a HD |
383 | }; |
384 | ||
385 | static struct omap_mbox_queue *mbox_queue_alloc(struct omap_mbox *mbox, | |
386 | request_fn_proc * proc, | |
387 | void (*work) (struct work_struct *)) | |
388 | { | |
165125e1 | 389 | struct request_queue *q; |
340a614a HD |
390 | struct omap_mbox_queue *mq; |
391 | ||
392 | mq = kzalloc(sizeof(struct omap_mbox_queue), GFP_KERNEL); | |
393 | if (!mq) | |
394 | return NULL; | |
395 | ||
396 | spin_lock_init(&mq->lock); | |
397 | ||
398 | q = blk_init_queue(proc, &mq->lock); | |
399 | if (!q) | |
400 | goto error; | |
401 | q->queuedata = mbox; | |
402 | mq->queue = q; | |
403 | ||
404 | INIT_WORK(&mq->work, work); | |
405 | ||
406 | return mq; | |
407 | error: | |
408 | kfree(mq); | |
409 | return NULL; | |
410 | } | |
411 | ||
412 | static void mbox_queue_free(struct omap_mbox_queue *q) | |
413 | { | |
414 | blk_cleanup_queue(q->queue); | |
415 | kfree(q); | |
416 | } | |
417 | ||
418 | static int omap_mbox_init(struct omap_mbox *mbox) | |
419 | { | |
420 | int ret; | |
421 | struct omap_mbox_queue *mq; | |
422 | ||
423 | if (likely(mbox->ops->startup)) { | |
424 | ret = mbox->ops->startup(mbox); | |
425 | if (unlikely(ret)) | |
426 | return ret; | |
427 | } | |
428 | ||
340a614a HD |
429 | ret = request_irq(mbox->irq, mbox_interrupt, IRQF_DISABLED, |
430 | mbox->name, mbox); | |
431 | if (unlikely(ret)) { | |
432 | printk(KERN_ERR | |
433 | "failed to register mailbox interrupt:%d\n", ret); | |
434 | goto fail_request_irq; | |
435 | } | |
340a614a HD |
436 | |
437 | mq = mbox_queue_alloc(mbox, mbox_txq_fn, mbox_tx_work); | |
438 | if (!mq) { | |
439 | ret = -ENOMEM; | |
440 | goto fail_alloc_txq; | |
441 | } | |
442 | mbox->txq = mq; | |
443 | ||
444 | mq = mbox_queue_alloc(mbox, mbox_rxq_fn, mbox_rx_work); | |
445 | if (!mq) { | |
446 | ret = -ENOMEM; | |
447 | goto fail_alloc_rxq; | |
448 | } | |
449 | mbox->rxq = mq; | |
450 | ||
451 | return 0; | |
452 | ||
453 | fail_alloc_rxq: | |
454 | mbox_queue_free(mbox->txq); | |
455 | fail_alloc_txq: | |
456 | free_irq(mbox->irq, mbox); | |
457 | fail_request_irq: | |
340a614a HD |
458 | if (unlikely(mbox->ops->shutdown)) |
459 | mbox->ops->shutdown(mbox); | |
460 | ||
461 | return ret; | |
462 | } | |
463 | ||
464 | static void omap_mbox_fini(struct omap_mbox *mbox) | |
465 | { | |
466 | mbox_queue_free(mbox->txq); | |
467 | mbox_queue_free(mbox->rxq); | |
468 | ||
469 | free_irq(mbox->irq, mbox); | |
340a614a HD |
470 | |
471 | if (unlikely(mbox->ops->shutdown)) | |
472 | mbox->ops->shutdown(mbox); | |
473 | } | |
474 | ||
475 | static struct omap_mbox **find_mboxes(const char *name) | |
476 | { | |
477 | struct omap_mbox **p; | |
478 | ||
479 | for (p = &mboxes; *p; p = &(*p)->next) { | |
480 | if (strcmp((*p)->name, name) == 0) | |
481 | break; | |
482 | } | |
483 | ||
484 | return p; | |
485 | } | |
486 | ||
487 | struct omap_mbox *omap_mbox_get(const char *name) | |
488 | { | |
489 | struct omap_mbox *mbox; | |
490 | int ret; | |
491 | ||
492 | read_lock(&mboxes_lock); | |
493 | mbox = *(find_mboxes(name)); | |
494 | if (mbox == NULL) { | |
495 | read_unlock(&mboxes_lock); | |
496 | return ERR_PTR(-ENOENT); | |
497 | } | |
498 | ||
499 | read_unlock(&mboxes_lock); | |
500 | ||
501 | ret = omap_mbox_init(mbox); | |
502 | if (ret) | |
503 | return ERR_PTR(-ENODEV); | |
504 | ||
505 | return mbox; | |
506 | } | |
507 | EXPORT_SYMBOL(omap_mbox_get); | |
508 | ||
509 | void omap_mbox_put(struct omap_mbox *mbox) | |
510 | { | |
511 | omap_mbox_fini(mbox); | |
512 | } | |
513 | EXPORT_SYMBOL(omap_mbox_put); | |
514 | ||
f48cca87 | 515 | int omap_mbox_register(struct device *parent, struct omap_mbox *mbox) |
340a614a HD |
516 | { |
517 | int ret = 0; | |
518 | struct omap_mbox **tmp; | |
519 | ||
520 | if (!mbox) | |
521 | return -EINVAL; | |
522 | if (mbox->next) | |
523 | return -EBUSY; | |
524 | ||
f48cca87 HD |
525 | mbox->dev = device_create(&omap_mbox_class, |
526 | parent, 0, mbox, "%s", mbox->name); | |
527 | if (IS_ERR(mbox->dev)) | |
528 | return PTR_ERR(mbox->dev); | |
529 | ||
530 | ret = device_create_file(mbox->dev, &dev_attr_mbox); | |
531 | if (ret) | |
532 | goto err_sysfs; | |
533 | ||
340a614a HD |
534 | write_lock(&mboxes_lock); |
535 | tmp = find_mboxes(mbox->name); | |
f48cca87 | 536 | if (*tmp) { |
340a614a | 537 | ret = -EBUSY; |
f48cca87 HD |
538 | write_unlock(&mboxes_lock); |
539 | goto err_find; | |
540 | } | |
541 | *tmp = mbox; | |
340a614a HD |
542 | write_unlock(&mboxes_lock); |
543 | ||
f48cca87 HD |
544 | return 0; |
545 | ||
546 | err_find: | |
547 | device_remove_file(mbox->dev, &dev_attr_mbox); | |
548 | err_sysfs: | |
549 | device_unregister(mbox->dev); | |
340a614a HD |
550 | return ret; |
551 | } | |
552 | EXPORT_SYMBOL(omap_mbox_register); | |
553 | ||
554 | int omap_mbox_unregister(struct omap_mbox *mbox) | |
555 | { | |
556 | struct omap_mbox **tmp; | |
557 | ||
558 | write_lock(&mboxes_lock); | |
559 | tmp = &mboxes; | |
560 | while (*tmp) { | |
561 | if (mbox == *tmp) { | |
562 | *tmp = mbox->next; | |
563 | mbox->next = NULL; | |
564 | write_unlock(&mboxes_lock); | |
f48cca87 HD |
565 | device_remove_file(mbox->dev, &dev_attr_mbox); |
566 | device_unregister(mbox->dev); | |
340a614a HD |
567 | return 0; |
568 | } | |
569 | tmp = &(*tmp)->next; | |
570 | } | |
571 | write_unlock(&mboxes_lock); | |
572 | ||
573 | return -EINVAL; | |
574 | } | |
575 | EXPORT_SYMBOL(omap_mbox_unregister); | |
576 | ||
577 | static int __init omap_mbox_class_init(void) | |
578 | { | |
579 | int ret = class_register(&omap_mbox_class); | |
580 | if (!ret) | |
581 | ret = class_create_file(&omap_mbox_class, &class_attr_mbox); | |
582 | ||
583 | return ret; | |
584 | } | |
585 | ||
586 | static void __exit omap_mbox_class_exit(void) | |
587 | { | |
588 | class_remove_file(&omap_mbox_class, &class_attr_mbox); | |
589 | class_unregister(&omap_mbox_class); | |
590 | } | |
591 | ||
592 | subsys_initcall(omap_mbox_class_init); | |
593 | module_exit(omap_mbox_class_exit); | |
594 | ||
f48cca87 HD |
595 | MODULE_LICENSE("GPL v2"); |
596 | MODULE_DESCRIPTION("omap mailbox: interrupt driven messaging"); | |
597 | MODULE_AUTHOR("Toshihiro Kobayashi and Hiroshi DOYU"); |