diff options
| author | Houlong Wei <houlong.wei@mediatek.com> | 2018-07-24 21:26:40 -0400 |
|---|---|---|
| committer | Jassi Brar <jaswinder.singh@linaro.org> | 2018-08-03 10:22:14 -0400 |
| commit | 623a6143a845bd485b00ba684f0ccef11835edab (patch) | |
| tree | 4d19effb909e5027a0396cd73790675055f05d93 | |
| parent | 1c82407aa302774b24bf619e56973aa97cbf25bd (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/Kconfig | 10 | ||||
| -rw-r--r-- | drivers/mailbox/Makefile | 2 | ||||
| -rw-r--r-- | drivers/mailbox/mtk-cmdq-mailbox.c | 569 | ||||
| -rw-r--r-- | include/linux/mailbox/mtk-cmdq-mailbox.h | 77 |
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 | |||
| 193 | config 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. | ||
| 192 | endif | 202 | endif |
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 | |||
| 40 | obj-$(CONFIG_TEGRA_HSP_MBOX) += tegra-hsp.o | 40 | obj-$(CONFIG_TEGRA_HSP_MBOX) += tegra-hsp.o |
| 41 | 41 | ||
| 42 | obj-$(CONFIG_STM32_IPCC) += stm32-ipcc.o | 42 | obj-$(CONFIG_STM32_IPCC) += stm32-ipcc.o |
| 43 | |||
| 44 | obj-$(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 | |||
| 53 | struct 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 | |||
| 61 | struct 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 | |||
| 69 | struct 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 | |||
| 79 | static 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 | |||
| 99 | static void cmdq_thread_resume(struct cmdq_thread *thread) | ||
| 100 | { | ||
| 101 | writel(CMDQ_THR_RESUME, thread->base + CMDQ_THR_SUSPEND_TASK); | ||
| 102 | } | ||
| 103 | |||
| 104 | static 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 | |||
| 111 | static 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 | |||
| 127 | static 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 */ | ||
| 134 | static 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 | |||
| 140 | static 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 | |||
| 159 | static 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. */ | ||
| 169 | static void cmdq_task_remove_wfe(struct cmdq_task *task) | ||
| 170 | { | ||
