diff options
Diffstat (limited to 'drivers/mailbox/mtk-cmdq-mailbox.c')
-rw-r--r-- | drivers/mailbox/mtk-cmdq-mailbox.c | 343 |
1 files changed, 232 insertions, 111 deletions
diff --git a/drivers/mailbox/mtk-cmdq-mailbox.c b/drivers/mailbox/mtk-cmdq-mailbox.c index 9a6ce9f5a7db..9465f9081515 100644 --- a/drivers/mailbox/mtk-cmdq-mailbox.c +++ b/drivers/mailbox/mtk-cmdq-mailbox.c @@ -19,6 +19,7 @@ #define CMDQ_OP_CODE_MASK (0xff << CMDQ_OP_CODE_SHIFT) #define CMDQ_NUM_CMD(t) (t->cmd_buf_size / CMDQ_INST_SIZE) +#define CMDQ_GCE_NUM_MAX (2) #define CMDQ_CURR_IRQ_STATUS 0x10 #define CMDQ_SYNC_TOKEN_UPDATE 0x68 @@ -36,6 +37,8 @@ #define CMDQ_THR_WAIT_TOKEN 0x30 #define CMDQ_THR_PRIORITY 0x40 +#define GCE_GCTL_VALUE 0x48 + #define CMDQ_THR_ACTIVE_SLOT_CYCLES 0x3200 #define CMDQ_THR_ENABLED 0x1 #define CMDQ_THR_DISABLED 0x0 @@ -56,7 +59,6 @@ struct cmdq_thread { void __iomem *base; struct list_head task_busy_list; u32 priority; - bool atomic_exec; }; struct cmdq_task { @@ -70,14 +72,32 @@ struct cmdq_task { struct cmdq { struct mbox_controller mbox; void __iomem *base; - u32 irq; + int irq; u32 thread_nr; u32 irq_mask; struct cmdq_thread *thread; - struct clk *clock; + struct clk_bulk_data clocks[CMDQ_GCE_NUM_MAX]; bool suspended; + u8 shift_pa; + bool control_by_sw; + u32 gce_num; +}; + +struct gce_plat { + u32 thread_nr; + u8 shift; + bool control_by_sw; + u32 gce_num; }; +u8 cmdq_get_shift_pa(struct mbox_chan *chan) +{ + struct cmdq *cmdq = container_of(chan->mbox, struct cmdq, mbox); + + return cmdq->shift_pa; +} +EXPORT_SYMBOL(cmdq_get_shift_pa); + static int cmdq_thread_suspend(struct cmdq *cmdq, struct cmdq_thread *thread) { u32 status; @@ -107,11 +127,13 @@ static void cmdq_init(struct cmdq *cmdq) { int i; - WARN_ON(clk_enable(cmdq->clock) < 0); + WARN_ON(clk_bulk_enable(cmdq->gce_num, cmdq->clocks)); + if (cmdq->control_by_sw) + writel(0x7, cmdq->base + GCE_GCTL_VALUE); writel(CMDQ_THR_ACTIVE_SLOT_CYCLES, cmdq->base + CMDQ_THR_SLOT_CYCLES); for (i = 0; i <= CMDQ_MAX_EVENT; i++) writel(i, cmdq->base + CMDQ_SYNC_TOKEN_UPDATE); - clk_disable(cmdq->clock); + clk_bulk_disable(cmdq->gce_num, cmdq->clocks); } static int cmdq_thread_reset(struct cmdq *cmdq, struct cmdq_thread *thread) @@ -155,64 +177,26 @@ static void cmdq_task_insert_into_thread(struct cmdq_task *task) dma_sync_single_for_cpu(dev, prev_task->pa_base, prev_task->pkt->cmd_buf_size, DMA_TO_DEVICE); prev_task_base[CMDQ_NUM_CMD(prev_task->pkt) - 1] = - (u64)CMDQ_JUMP_BY_PA << 32 | task->pa_base; + (u64)CMDQ_JUMP_BY_PA << 32 | + (task->pa_base >> task->cmdq->shift_pa); dma_sync_single_for_device(dev, prev_task->pa_base, prev_task->pkt->cmd_buf_size, DMA_TO_DEVICE); cmdq_thread_invalidate_fetched_data(thread); } -static bool cmdq_command_is_wfe(u64 cmd) -{ - u64 wfe_option = CMDQ_WFE_UPDATE | CMDQ_WFE_WAIT | CMDQ_WFE_WAIT_VALUE; - u64 wfe_op = (u64)(CMDQ_CODE_WFE << CMDQ_OP_CODE_SHIFT) << 32; - u64 wfe_mask = (u64)CMDQ_OP_CODE_MASK << 32 | 0xffffffff; - - return ((cmd & wfe_mask) == (wfe_op | wfe_option)); -} - -/* we assume tasks in the same display GCE thread are waiting the same event. */ -static void cmdq_task_remove_wfe(struct cmdq_task *task) -{ - struct device *dev = task->cmdq->mbox.dev; - u64 *base = task->pkt->va_base; - int i; - - dma_sync_single_for_cpu(dev, task->pa_base, task->pkt->cmd_buf_size, - DMA_TO_DEVICE); - for (i = 0; i < CMDQ_NUM_CMD(task->pkt); i++) - if (cmdq_command_is_wfe(base[i])) - base[i] = (u64)CMDQ_JUMP_BY_OFFSET << 32 | - CMDQ_JUMP_PASS; - dma_sync_single_for_device(dev, task->pa_base, task->pkt->cmd_buf_size, - DMA_TO_DEVICE); -} - static bool cmdq_thread_is_in_wfe(struct cmdq_thread *thread) { return readl(thread->base + CMDQ_THR_WAIT_TOKEN) & CMDQ_THR_IS_WAITING; } -static void cmdq_thread_wait_end(struct cmdq_thread *thread, - unsigned long end_pa) -{ - struct device *dev = thread->chan->mbox->dev; - unsigned long curr_pa; - - if (readl_poll_timeout_atomic(thread->base + CMDQ_THR_CURR_ADDR, - curr_pa, curr_pa == end_pa, 1, 20)) - dev_err(dev, "GCE thread cannot run to end.\n"); -} - -static void cmdq_task_exec_done(struct cmdq_task *task, enum cmdq_cb_status sta) +static void cmdq_task_exec_done(struct cmdq_task *task, int sta) { - struct cmdq_task_cb *cb = &task->pkt->async_cb; struct cmdq_cb_data data; - WARN_ON(cb->cb == (cmdq_async_flush_cb)NULL); data.sta = sta; - data.data = cb->data; - cb->cb(data); + data.pkt = task->pkt; + mbox_chan_received_data(task->thread->chan, &data); list_del(&task->list_entry); } @@ -221,13 +205,15 @@ static void cmdq_task_handle_error(struct cmdq_task *task) { struct cmdq_thread *thread = task->thread; struct cmdq_task *next_task; + struct cmdq *cmdq = task->cmdq; - dev_err(task->cmdq->mbox.dev, "task 0x%p error\n", task); - WARN_ON(cmdq_thread_suspend(task->cmdq, thread) < 0); + dev_err(cmdq->mbox.dev, "task 0x%p error\n", task); + WARN_ON(cmdq_thread_suspend(cmdq, thread) < 0); next_task = list_first_entry_or_null(&thread->task_busy_list, struct cmdq_task, list_entry); if (next_task) - writel(next_task->pa_base, thread->base + CMDQ_THR_CURR_ADDR); + writel(next_task->pa_base >> cmdq->shift_pa, + thread->base + CMDQ_THR_CURR_ADDR); cmdq_thread_resume(thread); } @@ -257,7 +243,7 @@ static void cmdq_thread_irq_handler(struct cmdq *cmdq, else return; - curr_pa = readl(thread->base + CMDQ_THR_CURR_ADDR); + curr_pa = readl(thread->base + CMDQ_THR_CURR_ADDR) << cmdq->shift_pa; list_for_each_entry_safe(task, tmp, &thread->task_busy_list, list_entry) { @@ -266,10 +252,10 @@ static void cmdq_thread_irq_handler(struct cmdq *cmdq, curr_task = task; if (!curr_task || curr_pa == task_end_pa - CMDQ_INST_SIZE) { - cmdq_task_exec_done(task, CMDQ_CB_NORMAL); + cmdq_task_exec_done(task, 0); kfree(task); } else if (err) { - cmdq_task_exec_done(task, CMDQ_CB_ERROR); + cmdq_task_exec_done(task, -ENOEXEC); cmdq_task_handle_error(curr_task); kfree(task); } @@ -280,7 +266,7 @@ static void cmdq_thread_irq_handler(struct cmdq *cmdq, if (list_empty(&thread->task_busy_list)) { cmdq_thread_disable(cmdq, thread); - clk_disable(cmdq->clock); + clk_bulk_disable(cmdq->gce_num, cmdq->clocks); } } @@ -325,7 +311,7 @@ static int cmdq_suspend(struct device *dev) if (task_running) dev_warn(dev, "exist running task(s) in suspend\n"); - clk_unprepare(cmdq->clock); + clk_bulk_unprepare(cmdq->gce_num, cmdq->clocks); return 0; } @@ -334,7 +320,7 @@ static int cmdq_resume(struct device *dev) { struct cmdq *cmdq = dev_get_drvdata(dev); - WARN_ON(clk_prepare(cmdq->clock) < 0); + WARN_ON(clk_bulk_prepare(cmdq->gce_num, cmdq->clocks)); cmdq->suspended = false; return 0; } @@ -343,8 +329,7 @@ static int cmdq_remove(struct platform_device *pdev) { struct cmdq *cmdq = platform_get_drvdata(pdev); - clk_unprepare(cmdq->clock); - + clk_bulk_unprepare(cmdq->gce_num, cmdq->clocks); return 0; } @@ -370,51 +355,41 @@ static int cmdq_mbox_send_data(struct mbox_chan *chan, void *data) task->pkt = pkt; if (list_empty(&thread->task_busy_list)) { - WARN_ON(clk_enable(cmdq->clock) < 0); + WARN_ON(clk_bulk_enable(cmdq->gce_num, cmdq->clocks)); + + /* + * The thread reset will clear thread related register to 0, + * including pc, end, priority, irq, suspend and enable. Thus + * set CMDQ_THR_ENABLED to CMDQ_THR_ENABLE_TASK will enable + * thread and make it running. + */ WARN_ON(cmdq_thread_reset(cmdq, thread) < 0); - writel(task->pa_base, thread->base + CMDQ_THR_CURR_ADDR); - writel(task->pa_base + pkt->cmd_buf_size, + writel(task->pa_base >> cmdq->shift_pa, + thread->base + CMDQ_THR_CURR_ADDR); + writel((task->pa_base + pkt->cmd_buf_size) >> cmdq->shift_pa, thread->base + CMDQ_THR_END_ADDR); + writel(thread->priority, thread->base + CMDQ_THR_PRIORITY); writel(CMDQ_THR_IRQ_EN, thread->base + CMDQ_THR_IRQ_ENABLE); writel(CMDQ_THR_ENABLED, thread->base + CMDQ_THR_ENABLE_TASK); } else { WARN_ON(cmdq_thread_suspend(cmdq, thread) < 0); - curr_pa = readl(thread->base + CMDQ_THR_CURR_ADDR); - end_pa = readl(thread->base + CMDQ_THR_END_ADDR); - - /* - * Atomic execution should remove the following wfe, i.e. only - * wait event at first task, and prevent to pause when running. - */ - if (thread->atomic_exec) { - /* GCE is executing if command is not WFE */ - if (!cmdq_thread_is_in_wfe(thread)) { - cmdq_thread_resume(thread); - cmdq_thread_wait_end(thread, end_pa); - WARN_ON(cmdq_thread_suspend(cmdq, thread) < 0); - /* set to this task directly */ - writel(task->pa_base, - thread->base + CMDQ_THR_CURR_ADDR); - } else { - cmdq_task_insert_into_thread(task); - cmdq_task_remove_wfe(task); - smp_mb(); /* modify jump before enable thread */ - } + curr_pa = readl(thread->base + CMDQ_THR_CURR_ADDR) << + cmdq->shift_pa; + end_pa = readl(thread->base + CMDQ_THR_END_ADDR) << + cmdq->shift_pa; + /* check boundary */ + if (curr_pa == end_pa - CMDQ_INST_SIZE || + curr_pa == end_pa) { + /* set to this task directly */ + writel(task->pa_base >> cmdq->shift_pa, + thread->base + CMDQ_THR_CURR_ADDR); } else { - /* check boundary */ - if (curr_pa == end_pa - CMDQ_INST_SIZE || - curr_pa == end_pa) { - /* set to this task directly */ - writel(task->pa_base, - thread->base + CMDQ_THR_CURR_ADDR); - } else { - cmdq_task_insert_into_thread(task); - smp_mb(); /* modify jump before enable thread */ - } + cmdq_task_insert_into_thread(task); + smp_mb(); /* modify jump before enable thread */ } - writel(task->pa_base + pkt->cmd_buf_size, + writel((task->pa_base + pkt->cmd_buf_size) >> cmdq->shift_pa, thread->base + CMDQ_THR_END_ADDR); cmdq_thread_resume(thread); } @@ -430,12 +405,93 @@ static int cmdq_mbox_startup(struct mbox_chan *chan) static void cmdq_mbox_shutdown(struct mbox_chan *chan) { + struct cmdq_thread *thread = (struct cmdq_thread *)chan->con_priv; + struct cmdq *cmdq = dev_get_drvdata(chan->mbox->dev); + struct cmdq_task *task, *tmp; + unsigned long flags; + + spin_lock_irqsave(&thread->chan->lock, flags); + if (list_empty(&thread->task_busy_list)) + goto done; + + WARN_ON(cmdq_thread_suspend(cmdq, thread) < 0); + + /* make sure executed tasks have success callback */ + cmdq_thread_irq_handler(cmdq, thread); + if (list_empty(&thread->task_busy_list)) + goto done; + + list_for_each_entry_safe(task, tmp, &thread->task_busy_list, + list_entry) { + cmdq_task_exec_done(task, -ECONNABORTED); + kfree(task); + } + + cmdq_thread_disable(cmdq, thread); + clk_bulk_disable(cmdq->gce_num, cmdq->clocks); + +done: + /* + * The thread->task_busy_list empty means thread already disable. The + * cmdq_mbox_send_data() always reset thread which clear disable and + * suspend statue when first pkt send to channel, so there is no need + * to do any operation here, only unlock and leave. + */ + spin_unlock_irqrestore(&thread->chan->lock, flags); +} + +static int cmdq_mbox_flush(struct mbox_chan *chan, unsigned long timeout) +{ + struct cmdq_thread *thread = (struct cmdq_thread *)chan->con_priv; + struct cmdq_cb_data data; + struct cmdq *cmdq = dev_get_drvdata(chan->mbox->dev); + struct cmdq_task *task, *tmp; + unsigned long flags; + u32 enable; + + spin_lock_irqsave(&thread->chan->lock, flags); + if (list_empty(&thread->task_busy_list)) + goto out; + + WARN_ON(cmdq_thread_suspend(cmdq, thread) < 0); + if (!cmdq_thread_is_in_wfe(thread)) + goto wait; + + list_for_each_entry_safe(task, tmp, &thread->task_busy_list, + list_entry) { + data.sta = -ECONNABORTED; + data.pkt = task->pkt; + mbox_chan_received_data(task->thread->chan, &data); + list_del(&task->list_entry); + kfree(task); + } + + cmdq_thread_resume(thread); + cmdq_thread_disable(cmdq, thread); + clk_bulk_disable(cmdq->gce_num, cmdq->clocks); + +out: + spin_unlock_irqrestore(&thread->chan->lock, flags); + return 0; + +wait: + cmdq_thread_resume(thread); + spin_unlock_irqrestore(&thread->chan->lock, flags); + if (readl_poll_timeout_atomic(thread->base + CMDQ_THR_ENABLE_TASK, + enable, enable == 0, 1, timeout)) { + dev_err(cmdq->mbox.dev, "Fail to wait GCE thread 0x%x done\n", + (u32)(thread->base - cmdq->base)); + + return -EFAULT; + } + return 0; } static const struct mbox_chan_ops cmdq_mbox_chan_ops = { .send_data = cmdq_mbox_send_data, .startup = cmdq_mbox_startup, .shutdown = cmdq_mbox_shutdown, + .flush = cmdq_mbox_flush, }; static struct mbox_chan *cmdq_xlate(struct mbox_controller *mbox, @@ -449,7 +505,6 @@ static struct mbox_chan *cmdq_xlate(struct mbox_controller *mbox, thread = (struct cmdq_thread *)mbox->chans[ind].con_priv; thread->priority = sp->args[1]; - thread->atomic_exec = (sp->args[2] != 0); thread->chan = &mbox->chans[ind]; return &mbox->chans[ind]; @@ -458,28 +513,37 @@ static struct mbox_chan *cmdq_xlate(struct mbox_controller *mbox, static int cmdq_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct resource *res; struct cmdq *cmdq; int err, i; + struct gce_plat *plat_data; + struct device_node *phandle = dev->of_node; + struct device_node *node; + int alias_id = 0; + static const char * const clk_name = "gce"; + static const char * const clk_names[] = { "gce0", "gce1" }; cmdq = devm_kzalloc(dev, sizeof(*cmdq), GFP_KERNEL); if (!cmdq) return -ENOMEM; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - cmdq->base = devm_ioremap_resource(dev, res); - if (IS_ERR(cmdq->base)) { - dev_err(dev, "failed to ioremap gce\n"); + cmdq->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(cmdq->base)) return PTR_ERR(cmdq->base); - } cmdq->irq = platform_get_irq(pdev, 0); - if (!cmdq->irq) { - dev_err(dev, "failed to get irq\n"); + if (cmdq->irq < 0) + return cmdq->irq; + + plat_data = (struct gce_plat *)of_device_get_match_data(dev); + if (!plat_data) { + dev_err(dev, "failed to get match data\n"); return -EINVAL; } - cmdq->thread_nr = (u32)(unsigned long)of_device_get_match_data(dev); + cmdq->thread_nr = plat_data->thread_nr; + cmdq->shift_pa = plat_data->shift; + cmdq->control_by_sw = plat_data->control_by_sw; + cmdq->gce_num = plat_data->gce_num; cmdq->irq_mask = GENMASK(cmdq->thread_nr - 1, 0); err = devm_request_irq(dev, cmdq->irq, cmdq_irq_handler, IRQF_SHARED, "mtk_cmdq", cmdq); @@ -491,10 +555,28 @@ static int cmdq_probe(struct platform_device *pdev) dev_dbg(dev, "cmdq device: addr:0x%p, va:0x%p, irq:%d\n", dev, cmdq->base, cmdq->irq); - cmdq->clock = devm_clk_get(dev, "gce"); - if (IS_ERR(cmdq->clock)) { - dev_err(dev, "failed to get gce clk\n"); - return PTR_ERR(cmdq->clock); + if (cmdq->gce_num > 1) { + for_each_child_of_node(phandle->parent, node) { + alias_id = of_alias_get_id(node, clk_name); + if (alias_id >= 0 && alias_id < cmdq->gce_num) { + cmdq->clocks[alias_id].id = clk_names[alias_id]; + cmdq->clocks[alias_id].clk = of_clk_get(node, 0); + if (IS_ERR(cmdq->clocks[alias_id].clk)) { + of_node_put(node); + return dev_err_probe(dev, + PTR_ERR(cmdq->clocks[alias_id].clk), + "failed to get gce clk: %d\n", + alias_id); + } + } + } + } else { + cmdq->clocks[alias_id].id = clk_name; + cmdq->clocks[alias_id].clk = devm_clk_get(&pdev->dev, clk_name); + if (IS_ERR(cmdq->clocks[alias_id].clk)) { + return dev_err_probe(dev, PTR_ERR(cmdq->clocks[alias_id].clk), + "failed to get gce clk\n"); + } } cmdq->mbox.dev = dev; @@ -530,7 +612,8 @@ static int cmdq_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, cmdq); - WARN_ON(clk_prepare(cmdq->clock) < 0); + + WARN_ON(clk_bulk_prepare(cmdq->gce_num, cmdq->clocks)); cmdq_init(cmdq); @@ -542,9 +625,47 @@ static const struct dev_pm_ops cmdq_pm_ops = { .resume = cmdq_resume, }; +static const struct gce_plat gce_plat_v2 = { + .thread_nr = 16, + .shift = 0, + .control_by_sw = false, + .gce_num = 1 +}; + +static const struct gce_plat gce_plat_v3 = { + .thread_nr = 24, + .shift = 0, + .control_by_sw = false, + .gce_num = 1 +}; + +static const struct gce_plat gce_plat_v4 = { + .thread_nr = 24, + .shift = 3, + .control_by_sw = false, + .gce_num = 1 +}; + +static const struct gce_plat gce_plat_v5 = { + .thread_nr = 24, + .shift = 3, + .control_by_sw = true, + .gce_num = 1 +}; + +static const struct gce_plat gce_plat_v6 = { + .thread_nr = 24, + .shift = 3, + .control_by_sw = true, + .gce_num = 2 +}; + static const struct of_device_id cmdq_of_ids[] = { - {.compatible = "mediatek,mt8173-gce", .data = (void *)16}, - {.compatible = "mediatek,mt8183-gce", .data = (void *)24}, + {.compatible = "mediatek,mt8173-gce", .data = (void *)&gce_plat_v2}, + {.compatible = "mediatek,mt8183-gce", .data = (void *)&gce_plat_v3}, + {.compatible = "mediatek,mt6779-gce", .data = (void *)&gce_plat_v4}, + {.compatible = "mediatek,mt8192-gce", .data = (void *)&gce_plat_v5}, + {.compatible = "mediatek,mt8195-gce", .data = (void *)&gce_plat_v6}, {} }; |