aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHoulong Wei <houlong.wei@mediatek.com>2018-07-24 21:26:40 -0400
committerJassi Brar <jaswinder.singh@linaro.org>2018-08-03 10:22:14 -0400
commit623a6143a845bd485b00ba684f0ccef11835edab (patch)
tree4d19effb909e5027a0396cd73790675055f05d93
parent1c82407aa302774b24bf619e56973aa97cbf25bd (diff)
mailbox: mediatek: Add Mediatek CMDQ driver
This patch is first version of Mediatek Command Queue(CMDQ) driver. The CMDQ is used to help write registers with critical time limitation, such as updating display configuration during the vblank. It controls Global Command Engine (GCE) hardware to achieve this requirement. Currently, CMDQ only supports display related hardwares, but we expect it can be extended to other hardwares for future requirements. Signed-off-by: Houlong Wei <houlong.wei@mediatek.com> Signed-off-by: HS Liao <hs.liao@mediatek.com> Signed-off-by: CK Hu <ck.hu@mediatek.com> Signed-off-by: Jassi Brar <jaswinder.singh@linaro.org>
-rw-r--r--drivers/mailbox/Kconfig10
-rw-r--r--drivers/mailbox/Makefile2
-rw-r--r--drivers/mailbox/mtk-cmdq-mailbox.c569
-rw-r--r--include/linux/mailbox/mtk-cmdq-mailbox.h77
4 files changed, 658 insertions, 0 deletions
diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index e63d29a95e76..2bbabc907add 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -189,4 +189,14 @@ config STM32_IPCC
189 Mailbox implementation for STMicroelectonics STM32 family chips 189 Mailbox implementation for STMicroelectonics STM32 family chips
190 with hardware for Inter-Processor Communication Controller (IPCC) 190 with hardware for Inter-Processor Communication Controller (IPCC)
191 between processors. Say Y here if you want to have this support. 191 between processors. Say Y here if you want to have this support.
192
193config MTK_CMDQ_MBOX
194 tristate "MediaTek CMDQ Mailbox Support"
195 depends on ARCH_MEDIATEK || COMPILE_TEST
196 select MTK_INFRACFG
197 help
198 Say yes here to add support for the MediaTek Command Queue (CMDQ)
199 mailbox driver. The CMDQ is used to help read/write registers with
200 critical time limitation, such as updating display configuration
201 during the vblank.
192endif 202endif
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index 4d501bea7863..4b0080480661 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -40,3 +40,5 @@ obj-$(CONFIG_QCOM_APCS_IPC) += qcom-apcs-ipc-mailbox.o
40obj-$(CONFIG_TEGRA_HSP_MBOX) += tegra-hsp.o 40obj-$(CONFIG_TEGRA_HSP_MBOX) += tegra-hsp.o
41 41
42obj-$(CONFIG_STM32_IPCC) += stm32-ipcc.o 42obj-$(CONFIG_STM32_IPCC) += stm32-ipcc.o
43
44obj-$(CONFIG_MTK_CMDQ_MBOX) += mtk-cmdq-mailbox.o
diff --git a/drivers/mailbox/mtk-cmdq-mailbox.c b/drivers/mailbox/mtk-cmdq-mailbox.c
new file mode 100644
index 000000000000..6f92c5ee12f7
--- /dev/null
+++ b/drivers/mailbox/mtk-cmdq-mailbox.c
@@ -0,0 +1,569 @@
1// SPDX-License-Identifier: GPL-2.0
2//
3// Copyright (c) 2018 MediaTek Inc.
4
5#include <linux/bitops.h>
6#include <linux/clk.h>
7#include <linux/clk-provider.h>
8#include <linux/dma-mapping.h>
9#include <linux/errno.h>
10#include <linux/interrupt.h>
11#include <linux/iopoll.h>
12#include <linux/kernel.h>
13#include <linux/module.h>
14#include <linux/platform_device.h>
15#include <linux/mailbox_controller.h>
16#include <linux/mailbox/mtk-cmdq-mailbox.h>
17#include <linux/of_device.h>
18
19#define CMDQ_OP_CODE_MASK (0xff << CMDQ_OP_CODE_SHIFT)
20#define CMDQ_IRQ_MASK 0xffff
21#define CMDQ_NUM_CMD(t) (t->cmd_buf_size / CMDQ_INST_SIZE)
22
23#define CMDQ_CURR_IRQ_STATUS 0x10
24#define CMDQ_THR_SLOT_CYCLES 0x30
25#define CMDQ_THR_BASE 0x100
26#define CMDQ_THR_SIZE 0x80
27#define CMDQ_THR_WARM_RESET 0x00
28#define CMDQ_THR_ENABLE_TASK 0x04
29#define CMDQ_THR_SUSPEND_TASK 0x08
30#define CMDQ_THR_CURR_STATUS 0x0c
31#define CMDQ_THR_IRQ_STATUS 0x10
32#define CMDQ_THR_IRQ_ENABLE 0x14
33#define CMDQ_THR_CURR_ADDR 0x20
34#define CMDQ_THR_END_ADDR 0x24
35#define CMDQ_THR_WAIT_TOKEN 0x30
36#define CMDQ_THR_PRIORITY 0x40
37
38#define CMDQ_THR_ACTIVE_SLOT_CYCLES 0x3200
39#define CMDQ_THR_ENABLED 0x1
40#define CMDQ_THR_DISABLED 0x0
41#define CMDQ_THR_SUSPEND 0x1
42#define CMDQ_THR_RESUME 0x0
43#define CMDQ_THR_STATUS_SUSPENDED BIT(1)
44#define CMDQ_THR_DO_WARM_RESET BIT(0)
45#define CMDQ_THR_IRQ_DONE 0x1
46#define CMDQ_THR_IRQ_ERROR 0x12
47#define CMDQ_THR_IRQ_EN (CMDQ_THR_IRQ_ERROR | CMDQ_THR_IRQ_DONE)
48#define CMDQ_THR_IS_WAITING BIT(31)
49
50#define CMDQ_JUMP_BY_OFFSET 0x10000000
51#define CMDQ_JUMP_BY_PA 0x10000001
52
53struct cmdq_thread {
54 struct mbox_chan *chan;
55 void __iomem *base;
56 struct list_head task_busy_list;
57 u32 priority;
58 bool atomic_exec;
59};
60
61struct cmdq_task {
62 struct cmdq *cmdq;
63 struct list_head list_entry;
64 dma_addr_t pa_base;
65 struct cmdq_thread *thread;
66 struct cmdq_pkt *pkt; /* the packet sent from mailbox client */
67};
68
69struct cmdq {
70 struct mbox_controller mbox;
71 void __iomem *base;
72 u32 irq;
73 u32 thread_nr;
74 struct cmdq_thread *thread;
75 struct clk *clock;
76 bool suspended;
77};
78
79static int cmdq_thread_suspend(struct cmdq *cmdq, struct cmdq_thread *thread)
80{
81 u32 status;
82
83 writel(CMDQ_THR_SUSPEND, thread->base + CMDQ_THR_SUSPEND_TASK);
84
85 /* If already disabled, treat as suspended successful. */
86 if (!(readl(thread->base + CMDQ_THR_ENABLE_TASK) & CMDQ_THR_ENABLED))
87 return 0;
88
89 if (readl_poll_timeout_atomic(thread->base + CMDQ_THR_CURR_STATUS,
90 status, status & CMDQ_THR_STATUS_SUSPENDED, 0, 10)) {
91 dev_err(cmdq->mbox.dev, "suspend GCE thread 0x%x failed\n",
92 (u32)(thread->base - cmdq->base));
93 return -EFAULT;
94 }
95
96 return 0;
97}
98
99static void cmdq_thread_resume(struct cmdq_thread *thread)
100{
101 writel(CMDQ_THR_RESUME, thread->base + CMDQ_THR_SUSPEND_TASK);
102}
103
104static void cmdq_init(struct cmdq *cmdq)
105{
106 WARN_ON(clk_enable(cmdq->clock) < 0);
107 writel(CMDQ_THR_ACTIVE_SLOT_CYCLES, cmdq->base + CMDQ_THR_SLOT_CYCLES);
108 clk_disable(cmdq->clock);
109}
110
111static int cmdq_thread_reset(struct cmdq *cmdq, struct cmdq_thread *thread)
112{
113 u32 warm_reset;
114
115 writel(CMDQ_THR_DO_WARM_RESET, thread->base + CMDQ_THR_WARM_RESET);
116 if (readl_poll_timeout_atomic(thread->base + CMDQ_THR_WARM_RESET,
117 warm_reset, !(warm_reset & CMDQ_THR_DO_WARM_RESET),
118 0, 10)) {
119 dev_err(cmdq->mbox.dev, "reset GCE thread 0x%x failed\n",
120 (u32)(thread->base - cmdq->base));
121 return -EFAULT;
122 }
123
124 return 0;
125}
126
127static void cmdq_thread_disable(struct cmdq *cmdq, struct cmdq_thread *thread)
128{
129 cmdq_thread_reset(cmdq, thread);
130 writel(CMDQ_THR_DISABLED, thread->base + CMDQ_THR_ENABLE_TASK);
131}
132
133/* notify GCE to re-fetch commands by setting GCE thread PC */
134static void cmdq_thread_invalidate_fetched_data(struct cmdq_thread *thread)
135{
136 writel(readl(thread->base + CMDQ_THR_CURR_ADDR),
137 thread->base + CMDQ_THR_CURR_ADDR);
138}
139
140static void cmdq_task_insert_into_thread(struct cmdq_task *task)
141{
142 struct device *dev = task->cmdq->mbox.dev;
143 struct cmdq_thread *thread = task->thread;
144 struct cmdq_task *prev_task = list_last_entry(
145 &thread->task_busy_list, typeof(*task), list_entry);
146 u64 *prev_task_base = prev_task->pkt->va_base;
147
148 /* let previous task jump to this task */
149 dma_sync_single_for_cpu(dev, prev_task->pa_base,
150 prev_task->pkt->cmd_buf_size, DMA_TO_DEVICE);
151 prev_task_base[CMDQ_NUM_CMD(prev_task->pkt) - 1] =
152 (u64)CMDQ_JUMP_BY_PA << 32 | task->pa_base;
153 dma_sync_single_for_device(dev, prev_task->pa_base,
154 prev_task->pkt->cmd_buf_size, DMA_TO_DEVICE);
155
156 cmdq_thread_invalidate_fetched_data(thread);
157}
158
159static bool cmdq_command_is_wfe(u64 cmd)
160{
161 u64 wfe_option = CMDQ_WFE_UPDATE | CMDQ_WFE_WAIT | CMDQ_WFE_WAIT_VALUE;
162 u64 wfe_op = (u64)(CMDQ_CODE_WFE << CMDQ_OP_CODE_SHIFT) << 32;
163 u64 wfe_mask = (u64)CMDQ_OP_CODE_MASK << 32 | 0xffffffff;
164
165 return ((cmd & wfe_mask) == (wfe_op | wfe_option));
166}
167
168/* we assume tasks in the same display GCE thread are waiting the same event. */
169static void cmdq_task_remove_wfe(struct cmdq_task *task)
170{
171 struct device *dev = task->cmdq->mbox.dev;
172 u64 *base = task->pkt->va_base;
173 int i;
174
175 dma_sync_single_for_cpu(dev, task->pa_base, task->pkt->cmd_buf_size,
176 DMA_TO_DEVICE);
177 for (i = 0; i < CMDQ_NUM_CMD(task->pkt); i++)
178 if (cmdq_command_is_wfe(base[i]))
179 base[i] = (u64)CMDQ_JUMP_BY_OFFSET << 32 |
180 CMDQ_JUMP_PASS;
181 dma_sync_single_for_device(dev, task->pa_base, task->pkt->cmd_buf_size,
182 DMA_TO_DEVICE);
183}
184
185static bool cmdq_thread_is_in_wfe(struct cmdq_thread *thread)
186{
187 return readl(thread->base + CMDQ_THR_WAIT_TOKEN) & CMDQ_THR_IS_WAITING;
188}
189
190static void cmdq_thread_wait_end(struct cmdq_thread *thread,
191 unsigned long end_pa)
192{
193 struct device *dev = thread->chan->mbox->dev;
194 unsigned long curr_pa;
195
196 if (readl_poll_timeout_atomic(thread->base + CMDQ_THR_CURR_ADDR,
197 curr_pa, curr_pa == end_pa, 1, 20))
198 dev_err(dev, "GCE thread cannot run to end.\n");
199}
200
201static void cmdq_task_exec_done(struct cmdq_task *task, enum cmdq_cb_status sta)
202{
203 struct cmdq_task_cb *cb = &task->pkt->async_cb;
204 struct cmdq_cb_data data;
205
206 WARN_ON(cb->cb == (cmdq_async_flush_cb)NULL);
207 data.sta = sta;
208 data.data = cb->data;
209 cb->cb(data);
210
211 list_del(&task->list_entry);
212}
213
214static void cmdq_task_handle_error(struct cmdq_task *task)
215{
216 struct cmdq_thread *thread = task->thread;
217 struct cmdq_task *next_task;
218
219 dev_err(task->cmdq->mbox.dev, "task 0x%p error\n", task);
220 WARN_ON(cmdq_thread_suspend(task->cmdq, thread) < 0);
221 next_task = list_first_entry_or_null(&thread->task_busy_list,
222 struct cmdq_task, list_entry);
223 if (next_task)
224 writel(next_task->pa_base, thread->base + CMDQ_THR_CURR_ADDR);
225 cmdq_thread_resume(thread);
226}
227
228static void cmdq_thread_irq_handler(struct cmdq *cmdq,
229 struct cmdq_thread *thread)
230{
231 struct cmdq_task *task, *tmp, *curr_task = NULL;
232 u32 curr_pa, irq_flag, task_end_pa;
233 bool err;
234
235 irq_flag = readl(thread->base + CMDQ_THR_IRQ_STATUS);
236 writel(~irq_flag, thread->base + CMDQ_THR_IRQ_STATUS);
237
238 /*
239 * When ISR call this function, another CPU core could run
240 * "release task" right before we acquire the spin lock, and thus
241 * reset / disable this GCE thread, so we need to check the enable
242 * bit of this GCE thread.
243 */
244 if (!(readl(thread->base + CMDQ_THR_ENABLE_TASK) & CMDQ_THR_ENABLED))
245 return;
246
247 if (irq_flag & CMDQ_THR_IRQ_ERROR)
248 err = true;
249 else if (irq_flag & CMDQ_THR_IRQ_DONE)
250 err = false;
251 else
252 return;
253
254 curr_pa = readl(thread->base + CMDQ_THR_CURR_ADDR);
255
256 list_for_each_entry_safe(task, tmp, &thread->task_busy_list,
257 list_entry) {
258 task_end_pa = task->pa_base + task->pkt->cmd_buf_size;
259 if (curr_pa >= task->pa_base && curr_pa < task_end_pa)
260 curr_task = task;
261
262 if (!curr_task || curr_pa == task_end_pa - CMDQ_INST_SIZE) {
263 cmdq_task_exec_done(task, CMDQ_CB_NORMAL);
264 kfree(task);
265 } else if (err) {
266 cmdq_task_exec_done(task, CMDQ_CB_ERROR);
267 cmdq_task_handle_error(curr_task);
268 kfree(task);
269 }
270
271 if (curr_task)
272 break;
273 }
274
275 if (list_empty(&thread->task_busy_list)) {
276 cmdq_thread_disable(cmdq, thread);
277 clk_disable(cmdq->clock);
278 }
279}
280
281static irqreturn_t cmdq_irq_handler(int irq, void *dev)
282{
283 struct cmdq *cmdq = dev;
284 unsigned long irq_status, flags = 0L;
285 int bit;
286
287 irq_status = readl(cmdq->base + CMDQ_CURR_IRQ_STATUS) & CMDQ_IRQ_MASK;
288 if (!(irq_status ^ CMDQ_IRQ_MASK))
289 return IRQ_NONE;
290
291 for_each_clear_bit(bit, &irq_status, fls(CMDQ_IRQ_MASK)) {
292 struct cmdq_thread *thread = &cmdq->thread[bit];
293
294 spin_lock_irqsave(&thread->chan->lock, flags);
295 cmdq_thread_irq_handler(cmdq, thread);
296 spin_unlock_irqrestore(&thread->chan->lock, flags);
297 }
298
299 return IRQ_HANDLED;
300}
301
302static int cmdq_suspend(struct device *dev)
303{
304 struct cmdq *cmdq = dev_get_drvdata(dev);
305 struct cmdq_thread *thread;
306 int i;
307 bool task_running = false;
308
309 cmdq->suspended = true;
310
311 for (i = 0; i < cmdq->thread_nr; i++) {
312 thread = &cmdq->thread[i];
313 if (!list_empty(&thread->task_busy_list)) {
314 task_running = true;
315 break;
316 }
317 }
318
319 if (task_running)
320 dev_warn(dev, "exist running task(s) in suspend\n");
321
322 clk_unprepare(cmdq->clock);
323
324 return 0;
325}
326
327static int cmdq_resume(struct device *dev)
328{
329 struct cmdq *cmdq = dev_get_drvdata(dev);
330
331 WARN_ON(clk_prepare(cmdq->clock) < 0);
332 cmdq->suspended = false;
333 return 0;
334}
335
336static int cmdq_remove(struct platform_device *pdev)
337{
338 struct cmdq *cmdq = platform_get_drvdata(pdev);
339
340 mbox_controller_unregister(&cmdq->mbox);
341 clk_unprepare(cmdq->clock);
342
343 if (cmdq->mbox.chans)
344 devm_kfree(&pdev->dev, cmdq->mbox.chans);
345
346 if (cmdq->thread)
347 devm_kfree(&pdev->dev, cmdq->thread);
348
349 devm_kfree(&pdev->dev, cmdq);
350
351 return 0;
352}
353
354static int cmdq_mbox_send_data(struct mbox_chan *chan, void *data)
355{
356 struct cmdq_pkt *pkt = (struct cmdq_pkt *)data;
357 struct cmdq_thread *thread = (struct cmdq_thread *)chan->con_priv;
358 struct cmdq *cmdq = dev_get_drvdata(chan->mbox->dev);
359 struct cmdq_task *task;
360 unsigned long curr_pa, end_pa;
361
362 /* Client should not flush new tasks if suspended. */
363 WARN_ON(cmdq->suspended);
364
365 task = kzalloc(sizeof(*task), GFP_ATOMIC);
366 task->cmdq = cmdq;
367 INIT_LIST_HEAD(&task->list_entry);
368 task->pa_base = pkt->pa_base;
369 task->thread = thread;
370 task->pkt = pkt;
371
372 if (list_empty(&thread->task_busy_list)) {
373 WARN_ON(clk_enable(cmdq->clock) < 0);
374 WARN_ON(cmdq_thread_reset(cmdq, thread) < 0);
375
376 writel(task->pa_base, thread->base + CMDQ_THR_CURR_ADDR);
377 writel(task->pa_base + pkt->cmd_buf_size,
378 thread->base + CMDQ_THR_END_ADDR);
379 writel(thread->priority, thread->base + CMDQ_THR_PRIORITY);
380 writel(CMDQ_THR_IRQ_EN, thread->base + CMDQ_THR_IRQ_ENABLE);
381 writel(CMDQ_THR_ENABLED, thread->base + CMDQ_THR_ENABLE_TASK);
382 } else {
383 WARN_ON(cmdq_thread_suspend(cmdq, thread) < 0);
384 curr_pa = readl(thread->base + CMDQ_THR_CURR_ADDR);
385 end_pa = readl(thread->base + CMDQ_THR_END_ADDR);
386
387 /*
388 * Atomic execution should remove the following wfe, i.e. only
389 * wait event at first task, and prevent to pause when running.
390 */
391 if (thread->atomic_exec) {
392 /* GCE is executing if command is not WFE */
393 if (!cmdq_thread_is_in_wfe(thread)) {
394 cmdq_thread_resume(thread);
395 cmdq_thread_wait_end(thread, end_pa);
396 WARN_ON(cmdq_thread_suspend(cmdq, thread) < 0);
397 /* set to this task directly */
398 writel(task->pa_base,
399 thread->base + CMDQ_THR_CURR_ADDR);
400 } else {
401 cmdq_task_insert_into_thread(task);
402 cmdq_task_remove_wfe(task);
403 smp_mb(); /* modify jump before enable thread */
404 }
405 } else {
406 /* check boundary */
407 if (curr_pa == end_pa - CMDQ_INST_SIZE ||
408 curr_pa == end_pa) {
409 /* set to this task directly */
410 writel(task->pa_base,
411 thread->base + CMDQ_THR_CURR_ADDR);
412 } else {
413 cmdq_task_insert_into_thread(task);
414 smp_mb(); /* modify jump before enable thread */
415 }
416 }
417 writel(task->pa_base + pkt->cmd_buf_size,
418 thread->base + CMDQ_THR_END_ADDR);
419 cmdq_thread_resume(thread);
420 }
421 list_move_tail(&task->list_entry, &thread->task_busy_list);
422
423 return 0;
424}
425
426static int cmdq_mbox_startup(struct mbox_chan *chan)
427{
428 return 0;
429}
430
431static void cmdq_mbox_shutdown(struct mbox_chan *chan)
432{
433}
434
435static const struct mbox_chan_ops cmdq_mbox_chan_ops = {
436 .send_data = cmdq_mbox_send_data,
437 .startup = cmdq_mbox_startup,
438 .shutdown = cmdq_mbox_shutdown,
439};
440
441static struct mbox_chan *cmdq_xlate(struct mbox_controller *mbox,
442 const struct of_phandle_args *sp)
443{
444 int ind = sp->args[0];
445 struct cmdq_thread *thread;
446
447 if (ind >= mbox->num_chans)
448 return ERR_PTR(-EINVAL);
449
450 thread = (struct cmdq_thread *)mbox->chans[ind].con_priv;
451 thread->priority = sp->args[1];
452 thread->atomic_exec = (sp->args[2] != 0);
453 thread->chan = &mbox->chans[ind];
454
455 return &mbox->chans[ind];
456}
457
458static int cmdq_probe(struct platform_device *pdev)
459{
460 struct device *dev = &pdev->dev;
461 struct resource *res;
462 struct cmdq *cmdq;
463 int err, i;
464
465 cmdq = devm_kzalloc(dev, sizeof(*cmdq), GFP_KERNEL);
466 if (!cmdq)
467 return -ENOMEM;
468
469 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
470 cmdq->base = devm_ioremap_resource(dev, res);
471 if (IS_ERR(cmdq->base)) {
472 dev_err(dev, "failed to ioremap gce\n");
473 return PTR_ERR(cmdq->base);
474 }
475
476 cmdq->irq = platform_get_irq(pdev, 0);
477 if (!cmdq->irq) {
478 dev_err(dev, "failed to get irq\n");
479 return -EINVAL;
480 }
481 err = devm_request_irq(dev, cmdq->irq, cmdq_irq_handler, IRQF_SHARED,
482 "mtk_cmdq", cmdq);
483 if (err < 0) {
484 dev_err(dev, "failed to register ISR (%d)\n", err);
485 return err;
486 }
487
488 dev_dbg(dev, "cmdq device: addr:0x%p, va:0x%p, irq:%d\n",
489 dev, cmdq->base, cmdq->irq);
490
491 cmdq->clock = devm_clk_get(dev, "gce");
492 if (IS_ERR(cmdq->clock)) {
493 dev_err(dev, "failed to get gce clk\n");
494 return PTR_ERR(cmdq->clock);
495 }
496
497 cmdq->thread_nr = (u32)(unsigned long)of_device_get_match_data(dev);
498 cmdq->mbox.dev = dev;
499 cmdq->mbox.chans = devm_kcalloc(dev, cmdq->thread_nr,
500 sizeof(*cmdq->mbox.chans), GFP_KERNEL);
501 if (!cmdq->mbox.chans)
502 return -ENOMEM;
503
504 cmdq->mbox.num_chans = cmdq->thread_nr;
505 cmdq->mbox.ops = &cmdq_mbox_chan_ops;
506 cmdq->mbox.of_xlate = cmdq_xlate;
507
508 /* make use of TXDONE_BY_ACK */
509 cmdq->mbox.txdone_irq = false;
510 cmdq->mbox.txdone_poll = false;
511
512 cmdq->thread = devm_kcalloc(dev, cmdq->thread_nr,
513 sizeof(*cmdq->thread), GFP_KERNEL);
514 if (!cmdq->thread)
515 return -ENOMEM;
516
517 for (i = 0; i < cmdq->thread_nr; i++) {
518 cmdq->thread[i].base = cmdq->base + CMDQ_THR_BASE +
519 CMDQ_THR_SIZE * i;
520 INIT_LIST_HEAD(&cmdq->thread[i].task_busy_list);
521 cmdq->mbox.chans[i].con_priv = (void *)&cmdq->thread[i];
522 }
523
524 err = mbox_controller_register(&cmdq->mbox);
525 if (err < 0) {
526 dev_err(dev, "failed to register mailbox: %d\n", err);
527 return err;
528 }
529
530 platform_set_drvdata(pdev, cmdq);
531 WARN_ON(clk_prepare(cmdq->clock) < 0);
532
533 cmdq_init(cmdq);
534
535 return 0;
536}
537
538static const struct dev_pm_ops cmdq_pm_ops = {
539 .suspend = cmdq_suspend,
540 .resume = cmdq_resume,
541};
542
543static const struct of_device_id cmdq_of_ids[] = {
544 {.compatible = "mediatek,mt8173-gce", .data = (void *)16},
545 {}
546};
547
548static struct platform_driver cmdq_drv = {
549 .probe = cmdq_probe,
550 .remove = cmdq_remove,
551 .driver = {
552 .name = "mtk_cmdq",
553 .pm = &cmdq_pm_ops,
554 .of_match_table = cmdq_of_ids,
555 }
556};
557
558static int __init cmdq_drv_init(void)
559{
560 return platform_driver_register(&cmdq_drv);
561}
562
563static void __exit cmdq_drv_exit(void)
564{
565 platform_driver_unregister(&cmdq_drv);
566}
567
568subsys_initcall(cmdq_drv_init);
569module_exit(cmdq_drv_exit);
diff --git a/include/linux/mailbox/mtk-cmdq-mailbox.h b/include/linux/mailbox/mtk-cmdq-mailbox.h
new file mode 100644
index 000000000000..ccb73422c2fa
--- /dev/null
+++ b/include/linux/mailbox/mtk-cmdq-mailbox.h
@@ -0,0 +1,77 @@
1/* SPDX-License-Identifier: GPL-2.0 */
2/*
3 * Copyright (c) 2018 MediaTek Inc.
4 *
5 */
6
7#ifndef __MTK_CMDQ_MAILBOX_H__
8#define __MTK_CMDQ_MAILBOX_H__
9
10#include <linux/platform_device.h>
11#include <linux/slab.h>
12#include <linux/types.h>
13
14#define CMDQ_INST_SIZE 8 /* instruction is 64-bit */
15#define CMDQ_SUBSYS_SHIFT 16
16#define CMDQ_OP_CODE_SHIFT 24
17#define CMDQ_JUMP_PASS CMDQ_INST_SIZE
18
19#define CMDQ_WFE_UPDATE BIT(31)
20#define CMDQ_WFE_WAIT BIT(15)
21#define CMDQ_WFE_WAIT_VALUE 0x1
22
23/*
24 * CMDQ_CODE_MASK:
25 * set write mask
26 * format: op mask
27 * CMDQ_CODE_WRITE:
28 * write value into target register
29 * format: op subsys address value
30 * CMDQ_CODE_JUMP:
31 * jump by offset
32 * format: op offset
33 * CMDQ_CODE_WFE:
34 * wait for event and clear
35 * it is just clear if no wait
36 * format: [wait] op event update:1 to_wait:1 wait:1
37 * [clear] op event update:1 to_wait:0 wait:0
38 * CMDQ_CODE_EOC:
39 * end of command
40 * format: op irq_flag
41 */
42enum cmdq_code {
43 CMDQ_CODE_MASK = 0x02,
44 CMDQ_CODE_WRITE = 0x04,
45 CMDQ_CODE_JUMP = 0x10,
46 CMDQ_CODE_WFE = 0x20,
47 CMDQ_CODE_EOC = 0x40,
48};
49
50enum cmdq_cb_status {
51 CMDQ_CB_NORMAL = 0,
52 CMDQ_CB_ERROR
53};
54
55struct cmdq_cb_data {
56 enum cmdq_cb_status sta;
57 void *data;
58};
59
60typedef void (*cmdq_async_flush_cb)(struct cmdq_cb_data data);
61
62struct cmdq_task_cb {
63 cmdq_async_flush_cb cb;
64 void *data;
65};
66
67struct cmdq_pkt {
68 void *va_base;
69 dma_addr_t pa_base;
70 size_t cmd_buf_size; /* command occupied size */
71 size_t buf_size; /* real buffer size */
72 struct cmdq_task_cb cb;
73 struct cmdq_task_cb async_cb;
74 void *cl;
75};
76
77#endif /* __MTK_CMDQ_MAILBOX_H__ */