diff mbox

[RFC,3/3] CMDQ: Mediatek CMDQ driver

Message ID 1453266881-16849-4-git-send-email-hs.liao@mediatek.com (mailing list archive)
State New, archived
Headers show

Commit Message

hs.liao@mediatek.com Jan. 20, 2016, 5:14 a.m. UTC
From: HS Liao <hs.liao@mediatek.com>

This patch is first version of Mediatek Command Queue(CMDQ) driver. The
CMDQ is used to help read/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: HS Liao <hs.liao@mediatek.com>
---
 drivers/soc/mediatek/Kconfig    |   10 +
 drivers/soc/mediatek/Makefile   |    1 +
 drivers/soc/mediatek/mtk-cmdq.c | 3532 +++++++++++++++++++++++++++++++++++++++
 include/soc/mediatek/cmdq.h     |  225 +++
 4 files changed, 3768 insertions(+)
 create mode 100644 drivers/soc/mediatek/mtk-cmdq.c
 create mode 100644 include/soc/mediatek/cmdq.h

Comments

Daniel Kurtz Jan. 28, 2016, 4:49 a.m. UTC | #1
Hi HS,

Sorry for the delay.  It is hard to find time to review a >3700 line
driver :-o in detail....

Some review comments inline, although I still do not completely
understand how all that this driver does and how it works.
I'll try to find time to go through this driver in detail again next
time you post it for review.

On Tue, Jan 19, 2016 at 9:14 PM,  <hs.liao@mediatek.com> wrote:
> From: HS Liao <hs.liao@mediatek.com>
>
> This patch is first version of Mediatek Command Queue(CMDQ) driver. The
> CMDQ is used to help read/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: HS Liao <hs.liao@mediatek.com>

[snip]

> diff --git a/drivers/soc/mediatek/mtk-cmdq.c b/drivers/soc/mediatek/mtk-cmdq.c
> new file mode 100644
> index 0000000..7570f00
> --- /dev/null
> +++ b/drivers/soc/mediatek/mtk-cmdq.c

[snip]

> +/*
> + * Maximum prefetch buffer size.
> + * Unit is instructions.
> + */
> +#define CMDQ_MAX_PREFETCH_INSTUCTION   240

INSTRUCTION

[snip]

> +
> +/* get lsb for subsys encoding in arg_a (range: 0 - 31) */
> +
> +enum cmdq_eng {
> +       CMDQ_ENG_DISP_UFOE = 0,
> +       CMDQ_ENG_DISP_AAL,
> +       CMDQ_ENG_DISP_COLOR0,
> +       CMDQ_ENG_DISP_COLOR1,
> +       CMDQ_ENG_DISP_RDMA0,
> +       CMDQ_ENG_DISP_RDMA1,
> +       CMDQ_ENG_DISP_RDMA2,
> +       CMDQ_ENG_DISP_WDMA0,
> +       CMDQ_ENG_DISP_WDMA1,
> +       CMDQ_ENG_DISP_OVL0,
> +       CMDQ_ENG_DISP_OVL1,
> +       CMDQ_ENG_DISP_GAMMA,
> +       CMDQ_ENG_DISP_DSI0_CMD,
> +       CMDQ_ENG_DISP_DSI1_CMD,

Why do these last two have "_CMD" at the end?

> +       CMDQ_MAX_ENGINE_COUNT   /* ALWAYS keep at the end */
> +};
> +
> +struct cmdq_command {
> +       struct cmdq             *cqctx;
> +       u32                     scenario;
> +       /* task priority (NOT HW thread priority) */
> +       u32                     priority;
> +       /* bit flag of used engines */
> +       u64                     engine_flag;
> +       /*
> +        * pointer of instruction buffer
> +        * This must point to an 64-bit aligned u32 array
> +        */
> +       u32                     *va_base;

All of your "va" and "va_base" should be void *, not u32 *.

> +       /* size of instruction buffer, in bytes. */
> +       u32                     block_size;

Better to use size_t for "size in bytes".

> +};
> +
> +enum cmdq_code {
> +       /* These are actual HW op code. */
> +       CMDQ_CODE_MOVE = 0x02,
> +       CMDQ_CODE_WRITE = 0x04,
> +       CMDQ_CODE_JUMP = 0x10,
> +       CMDQ_CODE_WFE = 0x20,   /* wait for event (and clear) */
> +       CMDQ_CODE_CLEAR_EVENT = 0x21,   /* clear event */
> +       CMDQ_CODE_EOC = 0x40,   /* end of command */
> +};
> +
> +enum cmdq_task_state {
> +       TASK_STATE_IDLE,        /* free task */
> +       TASK_STATE_BUSY,        /* task running on a thread */
> +       TASK_STATE_KILLED,      /* task process being killed */
> +       TASK_STATE_ERROR,       /* task execution error */
> +       TASK_STATE_DONE,        /* task finished */
> +       TASK_STATE_WAITING,     /* allocated but waiting for available thread */
> +};
> +
> +struct cmdq_cmd_buf {
> +       atomic_t                used;
> +       void                    *va;
> +       dma_addr_t              pa;
> +};
> +
> +struct cmdq_task_cb {
> +       /* called by isr */
> +       cmdq_async_flush_cb     isr_cb;
> +       void                    *isr_data;
> +       /* called by releasing task */
> +       cmdq_async_flush_cb     done_cb;
> +       void                    *done_data;
> +};
> +
> +struct cmdq_task {
> +       struct cmdq             *cqctx;
> +       struct list_head        list_entry;
> +
> +       /* state for task life cycle */
> +       enum cmdq_task_state    task_state;
> +       /* virtual address of command buffer */
> +       u32                     *va_base;
> +       /* physical address of command buffer */
> +       dma_addr_t              mva_base;
> +       /* size of allocated command buffer */
> +       u32                     buf_size;

size_t

> +       /* It points to a cmdq_cmd_buf if this task use command buffer pool. */
> +       struct cmdq_cmd_buf     *cmd_buf;
> +
> +       int                     scenario;
> +       int                     priority;
> +       u64                     engine_flag;
> +       u32                     command_size;
> +       u32                     num_cmd;
> +       int                     reorder;
> +       /* HW thread ID; CMDQ_INVALID_THREAD if not running */
> +       int                     thread;

I think this driver will be a lot more clear if you do this:

struct cmdq_thread *thread;

And then use "NULL" for "invalid thread" instead of CMDQ_INVALID_THREAD.

> +       /* flag of IRQ received */
> +       int                     irq_flag;
> +       /* callback functions */
> +       struct cmdq_task_cb     cb;
> +       /* work item when auto release is used */
> +       struct work_struct      auto_release_work;
> +
> +       unsigned long long      submit; /* submit time */
> +
> +       pid_t                   caller_pid;
> +       char                    caller_name[TASK_COMM_LEN];
> +};
> +
> +struct cmdq_thread {
> +       u32                     task_count;
> +       u32                     wait_cookie;
> +       u32                     next_cookie;
> +       struct cmdq_task        *cur_task[CMDQ_MAX_TASK_IN_THREAD];
> +};
> +
> +struct cmdq {
> +       struct device           *dev;
> +       struct notifier_block   pm_notifier;
> +
> +       void __iomem            *base_va;
> +       unsigned long           base_pa;

I think we can remove base_pa (it is only used in a debug print), and just call
"base_va" as "base".

> +       u32                     irq;
> +
> +       /*
> +        * task information
> +        * task_cache: struct cmdq_task object cache
> +        * task_free_list: unused free tasks
> +        * task_active_list: active tasks
> +        * task_consume_wait_queue_item: task consumption work item
> +        * task_auto_release_wq: auto-release workqueue
> +        * task_consume_wq: task consumption workqueue (for queued tasks)
> +        */
> +       struct kmem_cache       *task_cache;
> +       struct list_head        task_free_list;
> +       struct list_head        task_active_list;
> +       struct list_head        task_wait_list;
> +       struct work_struct      task_consume_wait_queue_item;
> +       struct workqueue_struct *task_auto_release_wq;
> +       struct workqueue_struct *task_consume_wq;
> +       u16                     task_count[CMDQ_MAX_THREAD_COUNT];

AFAICT, this task_count is not used?

> +
> +       struct cmdq_thread      thread[CMDQ_MAX_THREAD_COUNT];
> +
> +       /* mutex, spinlock, flag */
> +       struct mutex            task_mutex;     /* for task list */
> +       struct mutex            clock_mutex;    /* for clock operation */
> +       spinlock_t              thread_lock;    /* for cmdq hardware thread */
> +       int                     thread_usage;
> +       spinlock_t              exec_lock;      /* for exec task */
> +
> +       /* suspend */
> +       bool                    suspended;
> +
> +       /* command buffer pool */
> +       struct cmdq_cmd_buf     cmd_buf_pool[CMDQ_CMD_BUF_POOL_BUF_NUM];
> +
> +       /*
> +        * notification
> +        * wait_queue: for task done
> +        * thread_dispatch_queue: for thread acquiring
> +        */
> +       wait_queue_head_t       wait_queue[CMDQ_MAX_THREAD_COUNT];
> +       wait_queue_head_t       thread_dispatch_queue;
> +
> +       /* ccf */
> +       struct clk              *clock;
> +};
> +
> +struct cmdq_event_name {
> +       enum cmdq_event event;
> +       char            *name;

const char *

> +};
> +
> +struct cmdq_subsys {
> +       u32     base_addr;
> +       int     id;
> +       char    *grp_name;

const char *

> +};
> +
> +static const struct cmdq_event_name g_event_name[] = {
> +       /* Display start of frame(SOF) events */
> +       {CMDQ_EVENT_DISP_OVL0_SOF, "CMDQ_EVENT_DISP_OVL0_SOF",},

You can drop the "," inside "}".

> +       {CMDQ_EVENT_DISP_OVL1_SOF, "CMDQ_EVENT_DISP_OVL1_SOF",},
> +       {CMDQ_EVENT_DISP_RDMA0_SOF, "CMDQ_EVENT_DISP_RDMA0_SOF",},
> +       {CMDQ_EVENT_DISP_RDMA1_SOF, "CMDQ_EVENT_DISP_RDMA1_SOF",},
> +       {CMDQ_EVENT_DISP_RDMA2_SOF, "CMDQ_EVENT_DISP_RDMA2_SOF",},
> +       {CMDQ_EVENT_DISP_WDMA0_SOF, "CMDQ_EVENT_DISP_WDMA0_SOF",},
> +       {CMDQ_EVENT_DISP_WDMA1_SOF, "CMDQ_EVENT_DISP_WDMA1_SOF",},
> +       /* Display end of frame(EOF) events */
> +       {CMDQ_EVENT_DISP_OVL0_EOF, "CMDQ_EVENT_DISP_OVL0_EOF",},
> +       {CMDQ_EVENT_DISP_OVL1_EOF, "CMDQ_EVENT_DISP_OVL1_EOF",},
> +       {CMDQ_EVENT_DISP_RDMA0_EOF, "CMDQ_EVENT_DISP_RDMA0_EOF",},
> +       {CMDQ_EVENT_DISP_RDMA1_EOF, "CMDQ_EVENT_DISP_RDMA1_EOF",},
> +       {CMDQ_EVENT_DISP_RDMA2_EOF, "CMDQ_EVENT_DISP_RDMA2_EOF",},
> +       {CMDQ_EVENT_DISP_WDMA0_EOF, "CMDQ_EVENT_DISP_WDMA0_EOF",},
> +       {CMDQ_EVENT_DISP_WDMA1_EOF, "CMDQ_EVENT_DISP_WDMA1_EOF",},
> +       /* Mutex end of frame(EOF) events */
> +       {CMDQ_EVENT_MUTEX0_STREAM_EOF, "CMDQ_EVENT_MUTEX0_STREAM_EOF",},
> +       {CMDQ_EVENT_MUTEX1_STREAM_EOF, "CMDQ_EVENT_MUTEX1_STREAM_EOF",},
> +       {CMDQ_EVENT_MUTEX2_STREAM_EOF, "CMDQ_EVENT_MUTEX2_STREAM_EOF",},
> +       {CMDQ_EVENT_MUTEX3_STREAM_EOF, "CMDQ_EVENT_MUTEX3_STREAM_EOF",},
> +       {CMDQ_EVENT_MUTEX4_STREAM_EOF, "CMDQ_EVENT_MUTEX4_STREAM_EOF",},
> +       /* Display underrun events */
> +       {CMDQ_EVENT_DISP_RDMA0_UNDERRUN, "CMDQ_EVENT_DISP_RDMA0_UNDERRUN",},
> +       {CMDQ_EVENT_DISP_RDMA1_UNDERRUN, "CMDQ_EVENT_DISP_RDMA1_UNDERRUN",},
> +       {CMDQ_EVENT_DISP_RDMA2_UNDERRUN, "CMDQ_EVENT_DISP_RDMA2_UNDERRUN",},
> +       /* Keep this at the end of HW events */
> +       {CMDQ_MAX_HW_EVENT_COUNT, "CMDQ_MAX_HW_EVENT_COUNT",},
> +       /* GPR events */

What are "GPR" events?

> +       {CMDQ_SYNC_TOKEN_GPR_SET_0, "CMDQ_SYNC_TOKEN_GPR_SET_0",},
> +       {CMDQ_SYNC_TOKEN_GPR_SET_1, "CMDQ_SYNC_TOKEN_GPR_SET_1",},
> +       {CMDQ_SYNC_TOKEN_GPR_SET_2, "CMDQ_SYNC_TOKEN_GPR_SET_2",},
> +       {CMDQ_SYNC_TOKEN_GPR_SET_3, "CMDQ_SYNC_TOKEN_GPR_SET_3",},
> +       {CMDQ_SYNC_TOKEN_GPR_SET_4, "CMDQ_SYNC_TOKEN_GPR_SET_4",},
> +       /* This is max event and also can be used as mask. */
> +       {CMDQ_SYNC_TOKEN_MAX, "CMDQ_SYNC_TOKEN_MAX",},
> +       /* Invalid event */
> +       {CMDQ_SYNC_TOKEN_INVALID, "CMDQ_SYNC_TOKEN_INVALID",},
> +};
> +
> +static const struct cmdq_subsys g_subsys[] = {
> +       {0x1400, 1, "MMSYS"},
> +       {0x1401, 2, "DISP"},
> +       {0x1402, 3, "DISP"},

This isn't going to scale.  These addresses could be different on
different chips.
Instead of a static table like this, we probably need specify to the
connection between gce and other devices via devicetree phandles, and
then use the phandles to lookup the corresponding device address
range.

> +};
> +
> +static const char *cmdq_event_get_name(enum cmdq_event event)
> +{
> +       int i;
> +
> +       for (i = 0; i < ARRAY_SIZE(g_event_name); i++)
> +               if (g_event_name[i].event == event)
> +                       return g_event_name[i].name;
> +
> +       return "CMDQ_EVENT_UNKNOWN";
> +}
> +
> +static void cmdq_event_set(void __iomem *gce_base_va, enum cmdq_event event)
> +{
> +       writel(CMDQ_SYNC_TOKEN_SET | event,
> +              gce_base_va + CMDQ_SYNC_TOKEN_UPD_OFFSET);
> +}
> +

For any cmdq helper like this, just pass cmdq, and extract base in the
helper, eg:

static void cmdq_event_set(struct cmdq *cmdq, enum cmdq_event event)
{
writel(CMDQ_SYNC_TOKEN_SET | event,
cqctx->base + CMDQ_SYNC_TOKEN_UPD_OFFSET);
}

[snip]

> +
> +static bool cmdq_scenario_is_prefetch(enum cmdq_scenario scenario)
> +{
> +       switch (scenario) {
> +       case CMDQ_SCENARIO_PRIMARY_DISP:
> +               /*
> +                * Primary DISP HW should enable prefetch.
> +                * Since thread 0/1 shares one prefetch buffer,
> +                * we allow only PRIMARY path to use prefetch.
> +                */

I don't do think we want to hard code this policy in the cmdq driver.
There are systems that will only use the "sub" display channel, these systems
should be able to do prefetch for the "SUB" display if it is the only
one in use.

> +               return true;
> +       default:
> +               return false;
> +       }
> +
> +       return false;
> +}

[snip]

> +static u64 cmdq_scenario_get_flag(struct cmdq *cqctx,
> +                                 enum cmdq_scenario scenario)
> +{
> +       struct device *dev = cqctx->dev;
> +       u64 flag;
> +
> +       switch (scenario) {
> +       case CMDQ_SCENARIO_PRIMARY_DISP:
> +               flag = ((1LL << CMDQ_ENG_DISP_OVL0) |
> +                       (1LL << CMDQ_ENG_DISP_COLOR0) |
> +                       (1LL << CMDQ_ENG_DISP_AAL) |
> +                       (1LL << CMDQ_ENG_DISP_RDMA0) |
> +                       (1LL << CMDQ_ENG_DISP_UFOE) |
> +                       (1LL << CMDQ_ENG_DISP_DSI0_CMD));
> +               break;
> +       case CMDQ_SCENARIO_SUB_DISP:
> +               flag = ((1LL << CMDQ_ENG_DISP_OVL1) |
> +                       (1LL << CMDQ_ENG_DISP_COLOR1) |
> +                       (1LL << CMDQ_ENG_DISP_GAMMA) |
> +                       (1LL << CMDQ_ENG_DISP_RDMA1) |
> +                       (1LL << CMDQ_ENG_DISP_DSI1_CMD));
> +               break;

Instead of encoding policy (these arbitrary "scenarios"), perhaps the
cmdq driver should just provide these flag bits, and the display
subsystem can use them to build the "flag" parameter itself.

After all, the exact configuration of mmsys components is somewhat flexible.

> +       default:
> +               dev_err(dev, "unknown scenario type %d\n", scenario);
> +               flag = 0LL;
> +               break;
> +       }
> +
> +       return flag;
> +}

[snip]

> +static void cmdq_task_release_unlocked(struct cmdq_task *task)
> +{
> +       struct cmdq *cqctx = task->cqctx;
> +
> +       /* This func should be inside cqctx->task_mutex mutex */
> +       lockdep_assert_held(&cqctx->task_mutex);
> +
> +       task->task_state = TASK_STATE_IDLE;
> +       task->thread = CMDQ_INVALID_THREAD;
> +
> +       cmdq_task_free_command_buffer(task);
> +
> +       /* remove from active/waiting list */
> +       list_del_init(&task->list_entry);
> +       /* insert into free list. Currently we don't shrink free list. */
> +       list_add_tail(&task->list_entry, &cqctx->task_free_list);

list_move_tail()

> +}

[snip]

> +static struct cmdq_task *cmdq_core_acquire_task(struct cmdq_command *cmd_desc,
> +                                               struct cmdq_task_cb *cb)
> +{
> +       struct cmdq *cqctx = cmd_desc->cqctx;
> +       struct device *dev = cqctx->dev;
> +       int status;
> +       struct cmdq_task *task;
> +
> +       task = cmdq_core_find_free_task(cqctx);
> +       if (!task) {
> +               dev_err(dev, "can't acquire task info\n");
> +               return NULL;
> +       }
> +
> +       /* initialize field values */
> +       task->scenario = cmd_desc->scenario;
> +       task->priority = cmd_desc->priority;
> +       task->engine_flag = cmd_desc->engine_flag;
> +       task->task_state = TASK_STATE_WAITING;
> +       task->reorder = 0;
> +       task->thread = CMDQ_INVALID_THREAD;
> +       task->irq_flag = 0x0;
> +       memcpy(&task->cb, cb, sizeof(*cb));

task->cb = *cb;

> +       task->command_size = cmd_desc->block_size;
> +
> +       /* store caller info for debug */
> +       if (current) {
> +               task->caller_pid = current->pid;
> +               memcpy(task->caller_name, current->comm, sizeof(current->comm));
> +       }
> +
> +       status = cmdq_core_sync_command(task, cmd_desc);
> +       if (status < 0) {
> +               dev_err(dev, "fail to sync command\n");
> +               cmdq_task_release_internal(task);
> +               return NULL;
> +       }
> +
> +       /* insert into waiting list to process */
> +       mutex_lock(&cqctx->task_mutex);
> +       if (task) {
> +               struct list_head *in_item = &cqctx->task_wait_list;
> +               struct cmdq_task *wait_task = NULL;
> +               struct list_head *p = NULL;
> +
> +               task->submit = sched_clock();
> +
> +               /*
> +                * add to waiting list, keep it sorted by priority
> +                * so that we add high-priority tasks first.
> +                */
> +               list_for_each(p, &cqctx->task_wait_list) {
> +                       wait_task = list_entry(p, struct cmdq_task, list_entry);
> +                       /*
> +                        * keep the list sorted.
> +                        * higher priority tasks are inserted
> +                        * in front of the queue
> +                        */
> +                       if (wait_task->priority < task->priority)
> +                               break;
> +
> +                       in_item = p;
> +               }
> +
> +               list_add(&task->list_entry, in_item);
> +       }
> +       mutex_unlock(&cqctx->task_mutex);
> +
> +       return task;
> +}
> +
> +static int cmdq_clk_enable(struct cmdq *cqctx)
> +{
> +       struct device *dev = cqctx->dev;
> +       int ret = 0;
> +
> +       if (!cqctx->thread_usage) {
> +               ret = clk_prepare_enable(cqctx->clock);
> +               if (ret) {
> +                       dev_err(dev, "prepare and enable clk:%s fail\n",
> +                               CMDQ_CLK_NAME);
> +                       return ret;
> +               }
> +               cmdq_event_reset(cqctx);

AFAICT, we only need to reset the device when it power cycles, not
whenever we re-enable its clock.
I think cmdq_event_reset() here should really be a pm_runtime resume handler,
and "cmdq_clk_enable()" should just do a clk_prepare_enable() and
pm_runtime_get().

Then you won't need your own custom refcounting (thread_usage).
You won't need clock_mutex, either.

> +       }
> +       cqctx->thread_usage++;
> +
> +       return ret;
> +}
> +
> +static void cmdq_clk_disable(struct cmdq *cqctx)
> +{
> +       cqctx->thread_usage--;
> +       if (cqctx->thread_usage <= 0)
> +               clk_disable_unprepare(cqctx->clock);
> +}
> +
> +static int cmdq_core_find_free_thread(struct cmdq *cqctx, u32 scenario)
> +{
> +       struct device *dev = cqctx->dev;
> +       struct cmdq_thread *thread;
> +       int tid;
> +       u32 next_cookie;
> +
> +       thread = cqctx->thread;
> +       tid = cmdq_scenario_get_thread(scenario);

cmdq_core_acquire_thread() and cmdq_scenario_get_thread() should take
cmdq, and return a struct cmdq_thread * instead of tid.

On error, they should either return a standard linux error
(-EBUSY/-EINVAL, etc) as ERR_PTR(), or if fine-grained error detection
is not required, they can just return NULL.

> +
> +       /*
> +        * Currently, we only support disp,
> +        * so it's error if tid is CMDQ_INVALID_THREAD.
> +        */
> +       if (tid == CMDQ_INVALID_THREAD) {
> +               dev_err(dev, "got CMDQ_INVALID_THREAD!!!\n");
> +               return tid;
> +       }
> +
> +       /*
> +        * make sure the found thread has enough space for the task;
> +        * cmdq_thread->cur_task has size limitation.
> +        */
> +       if (thread[tid].task_count >= CMDQ_MAX_TASK_IN_THREAD)
> +               tid = CMDQ_INVALID_THREAD;
> +
> +       next_cookie = thread[tid].next_cookie % CMDQ_MAX_TASK_IN_THREAD;
> +       if (thread[tid].cur_task[next_cookie])
> +               tid = CMDQ_INVALID_THREAD;
> +
> +       return tid;
> +}

[snip]

> +static int cmdq_thread_suspend(struct cmdq *cqctx, int tid)
> +{
> +       struct device *dev = cqctx->dev;
> +       void __iomem *gce_base_va = cqctx->base_va;
> +       u32 enabled;
> +       u32 status;
> +
> +       /* write suspend bit */
> +       writel(CMDQ_THR_SUSPEND,
> +              gce_base_va + CMDQ_THR_SUSPEND_TASK_OFFSET +
> +              CMDQ_THR_SHIFT * tid);

All of these per-thread register accesses would be cleaner if we just
they were in helper functions on the struct cmdq_thread *:


First, when initializing cmdq->thread, save the thread's base:

thread->base = cmdq->base + cmdq_thread_to_tid(cmdq, thread) * CMDQ_THR_SHIFT;

Then, use these simple helper functions:

static void cmdq_thread_writel(struct cmdq_thread *thread, u32 value,
      unsigned offset)
{
writel(value, thread->base + offset);
}

static u32 cmdq_thread_readl(struct cmdq_thread *thread, unsigned offset)
{
return readl(thread->base + offset);
}

cmdq_thread_writel(thread, CMDQ_THR_SUSPEND, CMDQ_THR_SUSPEND_TASK_OFFSET);
enable = cmdq_thread_readl(thread, CMDQ_THR_ENABLE_TASK_OFFSET);

> +
> +       /* If already disabled, treat as suspended successful. */
> +       enabled = readl(gce_base_va + CMDQ_THR_ENABLE_TASK_OFFSET +
> +                       CMDQ_THR_SHIFT * tid);
> +       if (!(enabled & CMDQ_THR_ENABLED))
> +               return 0;
> +
> +       /* poll suspended status */
> +       if (readl_poll_timeout_atomic(gce_base_va +
> +                                     CMDQ_THR_CURR_STATUS_OFFSET +
> +                                     CMDQ_THR_SHIFT * tid,
> +                                     status,
> +                                     status & CMDQ_THR_STATUS_SUSPENDED,
> +                                     0, 10)) {
> +               dev_err(dev, "Suspend HW thread %d failed\n", tid);
> +               return -EFAULT;
> +       }
> +
> +       return 0;
> +}

[snip]

> +static void cmdq_core_parse_error(struct cmdq_task *task, int tid,
> +                                 const char **module_name, int *flag,
> +                                 u32 *inst_a, u32 *inst_b)

Hmm. Instead of back calculating details of the failing task, I think it might
be more natural to record these details when creating the command

 shouldn't you be
able to use the task's cookie to look it up on the list of tasks to
get its details?

> +{
> +       int irq_flag = task->irq_flag;
> +       u32 insts[2] = { 0 };
> +       const char *module;
> +
> +       /*
> +        * other cases, use instruction to judge
> +        * because scenario / HW flag are not sufficient
> +        */
> +       if (cmdq_task_get_pc_and_inst(task, tid, insts)) {
> +               u32 op, arg_a, arg_b;
> +
> +               op = insts[1] >> CMDQ_OP_CODE_SHIFT;
> +               arg_a = insts[1] & CMDQ_ARG_A_MASK;
> +               arg_b = insts[0];
> +
> +               switch (op) {
> +               case CMDQ_CODE_WRITE:
> +                       module = cmdq_core_parse_module_from_subsys(arg_a);
> +                       break;
> +               case CMDQ_CODE_WFE:
> +                       /* arg_a is the event id */
> +                       module = cmdq_event_get_module((enum cmdq_event)arg_a);
> +                       break;
> +               case CMDQ_CODE_MOVE:
> +               case CMDQ_CODE_JUMP:
> +               case CMDQ_CODE_EOC:
> +               default:
> +                       module = "CMDQ";
> +                       break;
> +               }
> +       } else {
> +               module = "CMDQ";
> +       }
> +
> +       /* fill output parameter */
> +       *module_name = module;
> +       *flag = irq_flag;
> +       *inst_a = insts[1];
> +       *inst_b = insts[0];
> +}

[snip]

> +static int cmdq_task_insert_into_thread(struct cmdq_task *last,
> +                                       struct cmdq_task *task,
> +                                       int tid, int loop)
> +{
> +       struct cmdq *cqctx = task->cqctx;
> +       struct device *dev = cqctx->dev;
> +       void __iomem *gce_base_va = cqctx->base_va;
> +       int status = 0;
> +       struct cmdq_thread *thread;
> +       struct cmdq_task *prev_task;
> +       int index;
> +       int prev;
> +       int cookie;
> +
> +       thread = &cqctx->thread[tid];
> +       cookie = thread->next_cookie;
> +
> +       /*
> +        * Traverse forward to adjust tasks' order
> +        * according to their priorities.
> +        */

AFAICT, this driver does not actually use priorities, so this can all
be simplified.
If we ever implement priorities, we can add this back, since there
will then be a way
to test it.

> +       for (prev = (cookie % CMDQ_MAX_TASK_IN_THREAD); loop > 0; loop--) {
> +               index = prev;
> +               if (index < 0)
> +                       index = CMDQ_MAX_TASK_IN_THREAD - 1;
> +
> +               prev = index - 1;
> +               if (prev < 0)
> +                       prev = CMDQ_MAX_TASK_IN_THREAD - 1;
> +
> +               prev_task = thread->cur_task[prev];
> +
> +               /* maybe the job is killed, search a new one */
> +               for (; !prev_task && loop > 1; loop--) {
> +                       dev_err(dev,
> +                               "prev_task is NULL, prev:%d, loop:%d, index:%d\n",
> +                               prev, loop, index);
> +
> +                       prev--;
> +                       if (prev < 0)
> +                               prev = CMDQ_MAX_TASK_IN_THREAD - 1;
> +
> +                       prev_task = thread->cur_task[prev];
> +               }
> +
> +               if (!prev_task) {
> +                       dev_err(dev,
> +                               "invalid task state for reorder %d %d\n",
> +                               index, loop);
> +                       status = -EFAULT;
> +                       break;
> +               }
> +
> +               /* insert this task */
> +               if (loop <= 1) {
> +                       thread->cur_task[index] = task;
> +                       /* Jump: Absolute */
> +                       prev_task->va_base[prev_task->num_cmd - 1] =
> +                                       CMDQ_JUMP_BY_PA;
> +                       /* Jump to here */
> +                       prev_task->va_base[prev_task->num_cmd - 2] =
> +                                       task->mva_base;
> +
> +                       /* re-fetch command buffer again. */
> +                       cmdq_core_invalidate_hw_fetched_buffer(
> +                                       gce_base_va, tid);
> +
> +                       break;
> +               }
> +
> +               if (prev_task->priority < task->priority) {
> +                       /* new task has higher priority */
> +
> +                       thread->cur_task[index] = prev_task;
> +                       prev_task->va_base[prev_task->num_cmd - 1] =
> +                                       task->va_base[task->num_cmd - 1];
> +                       prev_task->va_base[prev_task->num_cmd - 2] =
> +                                       task->va_base[task->num_cmd - 2];
> +
> +                       /* Boot priority for the task */
> +                       prev_task->priority += CMDQ_MIN_AGE_VALUE;
> +                       prev_task->reorder++;
> +
> +                       thread->cur_task[prev] = task;
> +                       /* Jump: Absolute */
> +                       task->va_base[task->num_cmd - 1] = CMDQ_JUMP_BY_PA;
> +                       /* Jump to here */
> +                       task->va_base[task->num_cmd - 2] = prev_task->mva_base;
> +
> +                       /* re-fetch command buffer again. */
> +                       cmdq_core_invalidate_hw_fetched_buffer(
> +                                       gce_base_va, tid);
> +
> +                       if (last == task)
> +                               last = prev_task;
> +               } else {
> +                       /* previous task has higher priority */
> +
> +                       thread->cur_task[index] = task;
> +                       /* Jump: Absolute */
> +                       prev_task->va_base[prev_task->num_cmd - 1] =
> +                                       CMDQ_JUMP_BY_PA;
> +                       /* Jump to here */
> +                       prev_task->va_base[prev_task->num_cmd - 2] =
> +                                       task->mva_base;
> +
> +                       /* re-fetch command buffer again. */
> +                       cmdq_core_invalidate_hw_fetched_buffer(
> +                                       gce_base_va, tid);
> +
> +                       break;
> +               }
> +       }
> +
> +       return status;
> +}
> +
> +static int cmdq_task_exec_async_impl(struct cmdq_task *task, int tid)
> +{
> +       struct cmdq *cqctx = task->cqctx;
> +       struct device *dev = cqctx->dev;
> +       void __iomem *gce_base_va = cqctx->base_va;
> +       int status;
> +       struct cmdq_thread *thread;
> +       struct cmdq_task *last_task;
> +       unsigned long flags;
> +       int loop;
> +       int minimum;
> +       int cookie;
> +       int thread_prio;
> +
> +       status = 0;
> +       thread = &cqctx->thread[tid];
> +
> +       spin_lock_irqsave(&cqctx->exec_lock, flags);
> +
> +       /* update task's thread info */
> +       task->thread = tid;
> +       task->irq_flag = 0;
> +       task->task_state = TASK_STATE_BUSY;
> +
> +       if (thread->task_count <= 0) {
> +               bool is_prefetch;
> +
> +               if (cmdq_thread_reset(cqctx, tid) < 0) {

Do we really have to reset with the spin lock held and irqs disabled?
This could take a while, right?

> +                       spin_unlock_irqrestore(&cqctx->exec_lock, flags);
> +                       return -EFAULT;
> +               }
> +
> +               writel(CMDQ_THR_NO_TIMEOUT,
> +                      gce_base_va + CMDQ_THR_INST_CYCLES_OFFSET +
> +                      CMDQ_THR_SHIFT * tid);
> +
> +               is_prefetch = cmdq_scenario_is_prefetch(task->scenario);
> +               if (is_prefetch) {
> +                       writel(CMDQ_THR_PREFETCH_EN,
> +                              gce_base_va + CMDQ_THR_PREFETCH_OFFSET +
> +                              CMDQ_THR_SHIFT * tid);
> +               }
> +
> +               thread_prio = CMDQ_THR_PRIO_DISPLAY_CONFIG;
> +               writel(task->mva_base,
> +                      gce_base_va + CMDQ_THR_CURR_ADDR_OFFSET +
> +                      CMDQ_THR_SHIFT * tid);
> +               writel(task->mva_base + task->command_size,
> +                      gce_base_va + CMDQ_THR_END_ADDR_OFFSET +
> +                      CMDQ_THR_SHIFT * tid);
> +               writel(thread_prio & CMDQ_THR_PRIORITY_MASK,
> +                      gce_base_va + CMDQ_THR_CFG_OFFSET +
> +                      CMDQ_THR_SHIFT * tid);
> +
> +               writel(CMDQ_THR_IRQ_EN,
> +                      gce_base_va + CMDQ_THR_IRQ_ENABLE_OFFSET +
> +                      CMDQ_THR_SHIFT * tid);
> +
> +               minimum = cmdq_thread_get_cookie(gce_base_va, tid);
> +               cmdq_thread_insert_task_by_cookie(
> +                               thread, task, (minimum + 1), true);
> +
> +               /* verify that we don't corrupt EOC + JUMP pattern */
> +               cmdq_core_verfiy_task_command_end(task);
> +
> +               /* enable HW thread */
> +               writel(CMDQ_THR_ENABLED,
> +                      gce_base_va + CMDQ_THR_ENABLE_TASK_OFFSET +
> +                      CMDQ_THR_SHIFT * tid);
> +       } else {
> +               unsigned long curr_pa, end_pa;
> +
> +               status = cmdq_thread_suspend(cqctx, tid);
> +               if (status < 0) {
> +                       spin_unlock_irqrestore(&cqctx->exec_lock, flags);
> +                       return status;
> +               }
> +
> +               writel(CMDQ_THR_NO_TIMEOUT,
> +                      gce_base_va + CMDQ_THR_INST_CYCLES_OFFSET +
> +                      CMDQ_THR_SHIFT * tid);
> +
> +               cookie = thread->next_cookie;
> +
> +               /*
> +                * Boundary case tested: EOC have been executed,
> +                *                       but JUMP is not executed
> +                * Thread PC: 0x9edc0dd8, End: 0x9edc0de0,
> +                * Curr Cookie: 1, Next Cookie: 2
> +                * PC = END - 8, EOC is executed
> +                * PC = END - 0, All CMDs are executed
> +                */
> +
> +               curr_pa = (unsigned long)readl(gce_base_va +
> +                                              CMDQ_THR_CURR_ADDR_OFFSET +
> +                                              CMDQ_THR_SHIFT * tid);
> +               end_pa = (unsigned long)readl(gce_base_va +
> +                                             CMDQ_THR_END_ADDR_OFFSET +
> +                                             CMDQ_THR_SHIFT * tid);
> +               if ((curr_pa == end_pa - 8) || (curr_pa == end_pa - 0)) {
> +                       /* set to task directly */
> +                       writel(task->mva_base,
> +                              gce_base_va + CMDQ_THR_CURR_ADDR_OFFSET +
> +                              CMDQ_THR_SHIFT * tid);
> +                       writel(task->mva_base + task->command_size,
> +                              gce_base_va + CMDQ_THR_END_ADDR_OFFSET +
> +                              CMDQ_THR_SHIFT * tid);
> +                       thread->cur_task[cookie % CMDQ_MAX_TASK_IN_THREAD] = task;
> +                       thread->task_count++;
> +               } else {
> +                       /* Current task that shuld be processed */
> +                       minimum = cmdq_thread_get_cookie(gce_base_va, tid) + 1;
> +                       if (minimum > CMDQ_MAX_COOKIE_VALUE)
> +                               minimum = 0;
> +
> +                       /* Calculate loop count to adjust the tasks' order */
> +                       if (minimum <= cookie)
> +                               loop = cookie - minimum;
> +                       else
> +                               /* Counter wrapped */
> +                               loop = (CMDQ_MAX_COOKIE_VALUE - minimum + 1) +
> +                                      cookie;
> +
> +                       if (loop < 0) {
> +                               dev_err(dev, "reorder fail:\n");
> +                               dev_err(dev, "  task count=%d\n", loop);
> +                               dev_err(dev, "  thread=%d\n", tid);
> +                               dev_err(dev, "  next cookie=%d\n",
> +                                       thread->next_cookie);
> +                               dev_err(dev, "  (HW) next cookie=%d\n",
> +                                       minimum);
> +                               dev_err(dev, "  task=0x%p\n", task);
> +
> +                               spin_unlock_irqrestore(&cqctx->exec_lock,
> +                                                      flags);
> +                               return -EFAULT;
> +                       }
> +
> +                       if (loop > CMDQ_MAX_TASK_IN_THREAD)
> +                               loop %= CMDQ_MAX_TASK_IN_THREAD;
> +
> +                       /*
> +                        * By default, task is the last task,
> +                        * and insert [cookie % CMDQ_MAX_TASK_IN_THREAD]
> +                        */
> +                       last_task = task;       /* Default last task */
> +
> +                       status = cmdq_task_insert_into_thread(
> +                                       last_task, task, tid, loop);
> +                       if (status < 0) {
> +                               spin_unlock_irqrestore(
> +                                               &cqctx->exec_lock, flags);
> +                               dev_err(dev,
> +                                       "invalid task state for reorder.\n");
> +                               return status;
> +                       }
> +
> +                       smp_mb(); /* modify jump before enable thread */
> +
> +                       writel(task->mva_base + task->command_size,
> +                              gce_base_va + CMDQ_THR_END_ADDR_OFFSET +
> +                              CMDQ_THR_SHIFT * tid);
> +                       thread->task_count++;
> +               }
> +
> +               thread->next_cookie += 1;
> +               if (thread->next_cookie > CMDQ_MAX_COOKIE_VALUE)
> +                       thread->next_cookie = 0;
> +
> +               /* verify that we don't corrupt EOC + JUMP pattern */
> +               cmdq_core_verfiy_task_command_end(task);
> +
> +               /* resume HW thread */
> +               cmdq_thread_resume(cqctx, tid);
> +       }
> +
> +       spin_unlock_irqrestore(&cqctx->exec_lock, flags);
> +
> +       return status;
> +}

[snip]

> +static int cmdq_core_resumed_notifier(struct cmdq *cqctx)
> +{
> +       /*
> +        * Note:
> +        * delay resume timing until process-unfreeze done in order to
> +        * ensure M4U driver had restore M4U port setting
> +        */
> +
> +       unsigned long flags = 0L;
> +
> +       spin_lock_irqsave(&cqctx->thread_lock, flags);
> +       cqctx->suspended = false;
> +
> +       /*
> +        * during suspending, there may be queued tasks.
> +        * we should process them if any.
> +        */
> +       if (!work_pending(&cqctx->task_consume_wait_queue_item))
> +               /* we use system global work queue (kernel thread kworker/n) */
> +               queue_work(cqctx->task_consume_wq,
> +                          &cqctx->task_consume_wait_queue_item);
> +
> +       spin_unlock_irqrestore(&cqctx->thread_lock, flags);
> +
> +       return 0;
> +}
> +
> +static int cmdq_task_exec_async_with_retry(struct cmdq_task *task, int tid)

Why do we want to automatically retry cmds that failed?

> +{
> +       struct device *dev = task->cqctx->dev;
> +       int retry;
> +       int status;
> +
> +       retry = 0;
> +       status = -EFAULT;
> +       do {
> +               cmdq_core_verfiy_task_command_end(task);
> +
> +               status = cmdq_task_exec_async_impl(task, tid);
> +
> +               if (status >= 0)
> +                       break;
> +
> +               if ((task->task_state == TASK_STATE_KILLED) ||
> +                   (task->task_state == TASK_STATE_ERROR)) {
> +                       dev_err(dev, "cmdq_task_exec_async_impl fail\n");
> +                       status = -EFAULT;
> +                       break;
> +               }
> +
> +               retry++;
> +       } while (retry < CMDQ_MAX_RETRY_COUNT);
> +
> +       return status;
> +}
> +
> +static int cmdq_core_get_time_in_ms(unsigned long long start,
> +                                   unsigned long long end)
> +{
> +       unsigned long long _duration = end - start;
> +
> +       do_div(_duration, 1000000);
> +       return (int)_duration;
> +}
> +
> +static void cmdq_core_consume_waiting_list(struct work_struct *work)
> +{
> +       struct list_head *p, *n = NULL;
> +       bool thread_acquired;
> +       unsigned long long consume_time;
> +       int waiting_time_ms;
> +       bool need_log;
> +       struct cmdq *cqctx;
> +       struct device *dev;
> +
> +       cqctx = container_of(work, struct cmdq,
> +                            task_consume_wait_queue_item);
> +       dev = cqctx->dev;
> +
> +       /*
> +        * when we're suspending,
> +        * do not execute any tasks. delay & hold them.
> +        */
> +       if (cqctx->suspended)
> +               return;
> +
> +       consume_time = sched_clock();
> +
> +       mutex_lock(&cqctx->task_mutex);
> +
> +       thread_acquired = false;
> +
> +       /* scan and remove (if executed) waiting tasks */
> +       list_for_each_safe(p, n, &cqctx->task_wait_list) {
> +               struct cmdq_task *task;
> +               struct cmdq_thread *thread = NULL;
> +               int tid;
> +               int status;
> +               enum cmdq_hw_thread_priority thread_prio;
> +
> +               task = list_entry(p, struct cmdq_task, list_entry);
> +
> +               thread_prio = CMDQ_THR_PRIO_DISPLAY_CONFIG;
> +
> +               waiting_time_ms = cmdq_core_get_time_in_ms(
> +                               task->submit, consume_time);
> +               need_log = waiting_time_ms >= CMDQ_PREALARM_TIMEOUT_MS;
> +               /* allocate hw thread */
> +               tid = cmdq_core_acquire_thread(cqctx, task->scenario);
> +               if (tid != CMDQ_INVALID_THREAD)
> +                       thread = &cqctx->thread[tid];
> +
> +               if (tid == CMDQ_INVALID_THREAD || !thread) {

Move computation of consume_time, waiting_time_ms & need_log here, when
printing a warning message, since this is the only place you need them.
Also, why bother converting to ms?  Just leave time in unsigned long long.

BTW, why sched_clock() instead of ktime?


> +                       /* have to wait, remain in wait list */
> +                       dev_warn(dev, "acquire thread fail, need to wait\n");
> +                       if (need_log) /* task wait too long */
> +                               dev_warn(dev, "waiting:%dms, task:0x%p\n",
> +                                        waiting_time_ms, task);
> +                       continue;
> +               }
> +
> +               /* some task is ready to run */
> +               thread_acquired = true;
> +
> +               /*
> +                * start execution
> +                * remove from wait list and put into active list
> +                */
> +               list_del_init(&task->list_entry);
> +               list_add_tail(&task->list_entry,
> +                             &cqctx->task_active_list);

list_move_tail(&task->list_entry, &cqctx->task_active_list);

> +
> +               /* run task on thread */
> +               status = cmdq_task_exec_async_with_retry(task, tid);
> +               if (status < 0) {
> +                       dev_err(dev, "%s fail, release task 0x%p\n",
> +                               __func__, task);
> +                       cmdq_task_remove_thread(task);
> +                       cmdq_task_release_unlocked(task);
> +                       task = NULL;
> +               }
> +       }
> +
> +       if (thread_acquired) {
> +               /*
> +                * notify some task's sw thread to change their waiting state.
> +                * (if they have already called cmdq_task_wait_and_release())
> +                */
> +               wake_up_all(&cqctx->thread_dispatch_queue);
> +       }
> +
> +       mutex_unlock(&cqctx->task_mutex);
> +}

[snip]


> +static int cmdq_task_wait_result(struct cmdq_task *task, int tid, int wait_q)
> +{
> +       struct cmdq *cqctx = task->cqctx;
> +       struct cmdq_thread *thread = &cqctx->thread[tid];
> +       int status = 0;
> +       unsigned long flags;
> +       struct cmdq_task_error_report error_report = {
> +               .throw_err = false,
> +               .module = NULL,
> +               .inst_a = 0,
> +               .inst_b = 0,
> +               .irq_flag = 0,
> +       };

This should be sufficient:
 struct cmdq_task_error_report error_report = { 0 };

> +
> +       /*
> +        * Note that although we disable IRQ, HW continues to execute
> +        * so it's possible to have pending IRQ
> +        */
> +       spin_lock_irqsave(&cqctx->exec_lock, flags);
> +
> +       if (task->task_state != TASK_STATE_DONE)
> +               status = cmdq_task_handle_error_result(
> +                               task, tid, wait_q, &error_report);

This error handling does too much with the spin lock held and irqs disabled.

> +
> +       if (thread->task_count <= 0)
> +               cmdq_thread_disable(cqctx, tid);
> +       else
> +               cmdq_thread_resume(cqctx, tid);
> +
> +       spin_unlock_irqrestore(&cqctx->exec_lock, flags);
> +
> +       if (error_report.throw_err) {
> +               u32 op = error_report.inst_a >> CMDQ_OP_CODE_SHIFT;
> +
> +               switch (op) {
> +               case CMDQ_CODE_WFE:
> +                       dev_err(cqctx->dev,
> +                               "%s in CMDQ IRQ:0x%02x, INST:(0x%08x, 0x%08x), OP:WAIT EVENT:%s\n",
> +                               error_report.module, error_report.irq_flag,
> +                               error_report.inst_a, error_report.inst_b,
> +                               cmdq_event_get_name(error_report.inst_a &
> +                                                   CMDQ_ARG_A_MASK));
> +                       break;
> +               default:
> +                       dev_err(cqctx->dev,
> +                               "%s in CMDQ IRQ:0x%02x, INST:(0x%08x, 0x%08x), OP:%s\n",
> +                               error_report.module, error_report.irq_flag,
> +                               error_report.inst_a, error_report.inst_b,
> +                               cmdq_core_parse_op(op));
> +                       break;
> +               }
> +       }
> +
> +       return status;
> +}
> +
> +static int cmdq_task_wait_done(struct cmdq_task *task)
> +{
> +       struct cmdq *cqctx = task->cqctx;
> +       struct device *dev = cqctx->dev;
> +       int wait_q;
> +       int tid;
> +       unsigned long timeout = msecs_to_jiffies(
> +                       CMDQ_ACQUIRE_THREAD_TIMEOUT_MS);
> +
> +       tid = task->thread;
> +       if (tid == CMDQ_INVALID_THREAD) {

You don't need this first "if" check.
Just do the wait_event_timeout() here.
If tid != CMDQ_INVALID_THREAD, it will return immediately.

> +               /*
> +                * wait for acquire thread
> +                * (this is done by cmdq_core_consume_waiting_list);
> +                */
> +               wait_q = wait_event_timeout(
> +                               cqctx->thread_dispatch_queue,
> +                               (task->thread != CMDQ_INVALID_THREAD), timeout);
> +
> +               if (!wait_q || task->thread == CMDQ_INVALID_THREAD) {

Why "task->thread == CMDQ_INVALID_THREAD" check here?  It is either
not needed or racy.

> +                       mutex_lock(&cqctx->task_mutex);
> +
> +                       /*
> +                        * it's possible that the task was just consumed now.
> +                        * so check again.
> +                        */
> +                       if (task->thread == CMDQ_INVALID_THREAD) {
> +                               /*
> +                                * Task may have released,
> +                                * or starved to death.
> +                                */
> +                               dev_err(dev,
> +                                       "task(0x%p) timeout with invalid thread\n",
> +                                       task);
> +
> +                               /*
> +                                * remove from waiting list,
> +                                * so that it won't be consumed in the future
> +                                */
> +                               list_del_init(&task->list_entry);
> +
> +                               mutex_unlock(&cqctx->task_mutex);
> +                               return -EINVAL;
> +                       }
> +
> +                       /* valid thread, so we keep going */
> +                       mutex_unlock(&cqctx->task_mutex);
> +               }
> +       }
> +
> +       tid = task->thread;
> +       if (tid < 0 || tid >= CMDQ_MAX_THREAD_COUNT) {
> +               dev_err(dev, "invalid thread %d in %s\n", tid, __func__);
> +               return -EINVAL;
> +       }
> +
> +       /* start to wait */
> +       wait_q = wait_event_timeout(task->cqctx->wait_queue[tid],
> +                                   (task->task_state != TASK_STATE_BUSY &&
> +                                    task->task_state != TASK_STATE_WAITING),
> +                                   msecs_to_jiffies(CMDQ_DEFAULT_TIMEOUT_MS));
> +       if (!wait_q)
> +               dev_dbg(dev, "timeout!\n");
> +
> +       /* wake up and continue */
> +       return cmdq_task_wait_result(task, tid, wait_q);
> +}
> +
> +static int cmdq_task_wait_and_release(struct cmdq_task *task)
> +{
> +       struct cmdq *cqctx;
> +       int status;
> +       int tid;
> +
> +       if (!task) {
> +               pr_err("%s err ptr=0x%p\n", __func__, task);
> +               return -EFAULT;
> +       }
> +
> +       if (task->task_state == TASK_STATE_IDLE) {
> +               pr_err("%s task=0x%p is IDLE\n", __func__, task);
> +               return -EFAULT;
> +       }
> +
> +       cqctx = task->cqctx;
> +
> +       /* wait for task finish */
> +       tid = task->thread;

tid is not used.

> +       status = cmdq_task_wait_done(task);
> +
> +       /* release */
> +       cmdq_task_remove_thread(task);
> +       cmdq_task_release_internal(task);
> +
> +       return status;
> +}
> +
> +static void cmdq_core_auto_release_work(struct work_struct *work_item)
> +{
> +       struct cmdq_task *task;
> +       int status;
> +       struct cmdq_task_cb cb;
> +
> +       task = container_of(work_item, struct cmdq_task, auto_release_work);
> +       memcpy(&cb, &task->cb, sizeof(cb));

Just:
  cb = task->cb;

But, why do you need to make a copy?
Can't you just access task->cb directly?

> +       status = cmdq_task_wait_and_release(task);
> +       task = NULL;

task is not used again, don't set to NULL.

> +
> +       /* isr fail, so call isr_cb here to prevent lock */
> +       if (status && cb.isr_cb)
> +               cb.isr_cb(cb.isr_data);
> +
> +       if (cb.done_cb)
> +               cb.done_cb(cb.done_data);
> +}

[snip]


> +static irqreturn_t cmdq_irq_handler(int irq, void *dev)
> +{
> +       struct cmdq *cqctx = dev;
> +       int i;
> +       u32 irq_status;
> +       bool handled = false;
> +
> +       if (cqctx->irq == irq) {
> +               irq_status = readl(cqctx->base_va +
> +                                  CMDQ_CURR_IRQ_STATUS_OFFSET);
> +               irq_status &= CMDQ_IRQ_MASK;
> +               for (i = 0;
> +                    (irq_status != CMDQ_IRQ_MASK) && i < CMDQ_MAX_THREAD_COUNT;
> +                    i++) {
> +                       /* STATUS bit set to 0 means IRQ asserted */
> +                       if (irq_status & BIT(i))
> +                               continue;
> +
> +                       /*
> +                        * We mark irq_status to 1 to denote finished
> +                        * processing, and we can early-exit if no more
> +                        * threads being asserted.
> +                        */
> +                       irq_status |= BIT(i);
> +
> +                       cmdq_core_handle_irq(cqctx, i);
> +                       handled = true;
> +               }
> +       }
> +
> +       if (handled) {
> +               queue_work(cqctx->task_consume_wq,
> +                          &cqctx->task_consume_wait_queue_item);

Since you need to queue work anyway, why no just do this all in a threaded irq
and use a mutex instead of spinlock for exec_lock.

> +               return IRQ_HANDLED;
> +       }
> +
> +       return IRQ_NONE;
> +}
> +
> +static int cmdq_core_initialize(struct platform_device *pdev,
> +                               struct cmdq **cqctx)
> +{
> +       struct cmdq *lcqctx; /* local cmdq context */
> +       int i;
> +       int ret = 0;
> +
> +       lcqctx = devm_kzalloc(&pdev->dev, sizeof(*lcqctx), GFP_KERNEL);
> +
> +       /* save dev */
> +       lcqctx->dev = &pdev->dev;
> +
> +       /* initial cmdq device related data */
> +       ret = cmdq_dev_init(pdev, lcqctx);
> +       if (ret) {
> +               dev_err(&pdev->dev, "failed to init cmdq device\n");
> +               goto fail_dev;
> +       }
> +
> +       /* initial mutex, spinlock */
> +       mutex_init(&lcqctx->task_mutex);
> +       mutex_init(&lcqctx->clock_mutex);
> +       spin_lock_init(&lcqctx->thread_lock);
> +       spin_lock_init(&lcqctx->exec_lock);
> +
> +       /* initial wait queue for notification */
> +       for (i = 0; i < ARRAY_SIZE(lcqctx->wait_queue); i++)
> +               init_waitqueue_head(&lcqctx->wait_queue[i]);
> +       init_waitqueue_head(&lcqctx->thread_dispatch_queue);
> +
> +       /* create task pool */
> +       lcqctx->task_cache = kmem_cache_create(
> +                       CMDQ_DRIVER_DEVICE_NAME "_task",
> +                       sizeof(struct cmdq_task),
> +                       __alignof__(struct cmdq_task),
> +                       SLAB_POISON | SLAB_HWCACHE_ALIGN | SLAB_RED_ZONE,
> +                       &cmdq_task_ctor);
> +
> +       /* initialize task lists */
> +       INIT_LIST_HEAD(&lcqctx->task_free_list);
> +       INIT_LIST_HEAD(&lcqctx->task_active_list);
> +       INIT_LIST_HEAD(&lcqctx->task_wait_list);
> +       INIT_WORK(&lcqctx->task_consume_wait_queue_item,
> +                 cmdq_core_consume_waiting_list);
> +
> +       /* initialize command buffer pool */
> +       ret = cmdq_cmd_buf_pool_init(lcqctx);
> +       if (ret) {
> +               dev_err(&pdev->dev, "failed to init command buffer pool\n");
> +               goto fail_cmd_buf_pool;
> +       }
> +
> +       lcqctx->task_auto_release_wq = alloc_ordered_workqueue(
> +                       "%s", WQ_MEM_RECLAIM | WQ_HIGHPRI, "cmdq_auto_release");
> +       lcqctx->task_consume_wq = alloc_ordered_workqueue(
> +                       "%s", WQ_MEM_RECLAIM | WQ_HIGHPRI, "cmdq_task");
> +
> +       *cqctx = lcqctx;
> +       return ret;
> +
> +fail_cmd_buf_pool:
> +       destroy_workqueue(lcqctx->task_auto_release_wq);
> +       destroy_workqueue(lcqctx->task_consume_wq);
> +       kmem_cache_destroy(lcqctx->task_cache);
> +
> +fail_dev:
> +       return ret;
> +}
> +
> +static int cmdq_rec_realloc_cmd_buffer(struct cmdq_rec *handle, u32 size)
> +{
> +       void *new_buf;
> +
> +       new_buf = krealloc(handle->buf_ptr, size, GFP_KERNEL | __GFP_ZERO);
> +       if (!new_buf)
> +               return -ENOMEM;
> +       handle->buf_ptr = new_buf;
> +       handle->buf_size = size;
> +       return 0;
> +}
> +
> +static struct cmdq *cmdq_rec_get_valid_ctx(struct cmdq_rec *handle)
> +{

Make the caller for all cmdq_rec() APIs pass in struct cmdq *, also, and get rid
of all of this NULL checking.

> +       if (!handle) {
> +               WARN_ON(1);
> +               return NULL;
> +       }
> +
> +       WARN_ON(!handle->cqctx);
> +       return handle->cqctx;
> +}
> +
> +static int cmdq_rec_stop_running_task(struct cmdq_rec *handle)
> +{
> +       int status;
> +
> +       status = cmdq_core_release_task(handle->running_task_ptr);
> +       handle->running_task_ptr = NULL;
> +       return status;
> +}
> +
> +int cmdq_rec_create(struct platform_device *pdev,

Use struct device *, not struct platform_device *.
Although, I think it would be better if this API was:

struct cmdq_rec *cmdq_rec_create(struct cmdq *cmdq, enum cmdq_scenario scenario)

> +                   enum cmdq_scenario scenario,
> +                   struct cmdq_rec **handle_ptr)
> +{
> +       struct cmdq *cqctx;
> +       struct device *dev = &pdev->dev;
> +       struct cmdq_rec *handle;
> +       int ret;
> +
> +       cqctx = platform_get_drvdata(pdev);
> +       if (!cqctx) {
> +               dev_err(dev, "cmdq context is NULL\n");
> +               return -EINVAL;
> +       }
> +
> +       if (scenario < 0 || scenario >= CMDQ_MAX_SCENARIO_COUNT) {
> +               dev_err(dev, "unknown scenario type %d\n", scenario);
> +               return -EINVAL;
> +       }
> +
> +       handle = kzalloc(sizeof(*handle), GFP_KERNEL);
> +       if (!handle)
> +               return -ENOMEM;
> +
> +       handle->cqctx = platform_get_drvdata(pdev);
> +       handle->scenario = scenario;
> +       handle->engine_flag = cmdq_scenario_get_flag(cqctx, scenario);
> +       handle->priority = CMDQ_THR_PRIO_NORMAL;
> +
> +       ret = cmdq_rec_realloc_cmd_buffer(handle, CMDQ_INITIAL_CMD_BLOCK_SIZE);
> +       if (ret) {
> +               kfree(handle);
> +               return ret;
> +       }
> +
> +       *handle_ptr = handle;
> +
> +       return 0;
> +}
> +EXPORT_SYMBOL(cmdq_rec_create);

[snip]

> +static int cmdq_suspend(struct device *dev)
> +{
> +       struct cmdq *cqctx;
> +       void __iomem *gce_base_va;
> +       unsigned long flags = 0L;
> +       u32 exec_threads;
> +       int ref_count;
> +       bool kill_tasks = false;
> +       struct cmdq_task *task;
> +       struct list_head *p;
> +       int i;
> +
> +       cqctx = dev_get_drvdata(dev);
> +       gce_base_va = cqctx->base_va;
> +       exec_threads = readl(gce_base_va + CMDQ_CURR_LOADED_THR_OFFSET);
> +       ref_count = cqctx->thread_usage;
> +
> +       if ((ref_count > 0) || (exec_threads & CMDQ_THR_EXECUTING)) {
> +               dev_err(dev,
> +                       "[SUSPEND] other running, kill tasks. threads:0x%08x, ref:%d\n",
> +                       exec_threads, ref_count);
> +               kill_tasks = true;
> +       }
> +
> +       /*
> +        * We need to ensure the system is ready to suspend,
> +        * so kill all running CMDQ tasks
> +        * and release HW engines.
> +        */
> +       if (kill_tasks) {
> +               /* remove all active task from thread */
> +               dev_err(dev, "[SUSPEND] remove all active tasks\n");
> +               list_for_each(p, &cqctx->task_active_list) {
> +                       task = list_entry(p, struct cmdq_task, list_entry);
> +                       if (task->thread != CMDQ_INVALID_THREAD) {
> +                               spin_lock_irqsave(&cqctx->exec_lock, flags);
> +                               cmdq_thread_force_remove_task(
> +                                               task, task->thread);
> +                               task->task_state = TASK_STATE_KILLED;
> +                               spin_unlock_irqrestore(
> +                                               &cqctx->exec_lock, flags);
> +
> +                               /*
> +                                * release all thread and
> +                                * mark all active tasks as "KILLED"
> +                                * (so that thread won't release again)
> +                                */
> +                               dev_err(dev,
> +                                       "[SUSPEND] release all threads and HW clocks\n");
> +                               cmdq_task_remove_thread(task);
> +                       }
> +               }
> +
> +               /* disable all HW thread */
> +               dev_err(dev, "[SUSPEND] disable all HW threads\n");
> +               for (i = 0; i < CMDQ_MAX_THREAD_COUNT; i++)
> +                       cmdq_thread_disable(cqctx, i);
> +
> +               /* reset all cmdq_thread */
> +               memset(&cqctx->thread[0], 0, sizeof(cqctx->thread));

This is actually confusing, because this actually sets the tids to 0,
which is a valid thread.  Do you really want this?:

memset(cqctx->thread, -1, sizeof(cqctx->thread));

Which would all the threads to CMDQ_INVALID_THREAD ?

> +       }
> +
> +       spin_lock_irqsave(&cqctx->thread_lock, flags);
> +       cqctx->suspended = true;
> +       spin_unlock_irqrestore(&cqctx->thread_lock, flags);
> +
> +       /* ALWAYS allow suspend */
> +       return 0;
> +}

[snip]

> diff --git a/include/soc/mediatek/cmdq.h b/include/soc/mediatek/cmdq.h
> new file mode 100644
> index 0000000..2ec349f
> --- /dev/null
> +++ b/include/soc/mediatek/cmdq.h
> @@ -0,0 +1,225 @@
> +/*
> + * Copyright (c) 2015 MediaTek Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#ifndef __MTK_CMDQ_H__
> +#define __MTK_CMDQ_H__
> +
> +#include <linux/platform_device.h>
> +#include <linux/types.h>
> +
> +enum cmdq_scenario {
> +       CMDQ_SCENARIO_PRIMARY_DISP,
> +       CMDQ_SCENARIO_SUB_DISP,
> +       CMDQ_MAX_SCENARIO_COUNT
> +};
> +
> +enum cmdq_hw_thread_priority {
> +       CMDQ_THR_PRIO_NORMAL = 0, /* nomral (low) priority */
> +       CMDQ_THR_PRIO_DISPLAY_CONFIG = 3, /* display config (high) priority */
> +       CMDQ_THR_PRIO_MAX = 7, /* maximum possible priority */
> +};

We only ever use CMDQ_THR_PRIO_DISPLAY_CONFIG.
I suggest you add support for priorities later in a follow-up patch when/if they
are useful.

> +
> +/* events for CMDQ and display */
> +enum cmdq_event {
> +       /* Display start of frame(SOF) events */
> +       CMDQ_EVENT_DISP_OVL0_SOF = 11,
> +       CMDQ_EVENT_DISP_OVL1_SOF = 12,
> +       CMDQ_EVENT_DISP_RDMA0_SOF = 13,
> +       CMDQ_EVENT_DISP_RDMA1_SOF = 14,
> +       CMDQ_EVENT_DISP_RDMA2_SOF = 15,
> +       CMDQ_EVENT_DISP_WDMA0_SOF = 16,
> +       CMDQ_EVENT_DISP_WDMA1_SOF = 17,
> +       /* Display end of frame(EOF) events */
> +       CMDQ_EVENT_DISP_OVL0_EOF = 39,
> +       CMDQ_EVENT_DISP_OVL1_EOF = 40,
> +       CMDQ_EVENT_DISP_RDMA0_EOF = 41,
> +       CMDQ_EVENT_DISP_RDMA1_EOF = 42,
> +       CMDQ_EVENT_DISP_RDMA2_EOF = 43,
> +       CMDQ_EVENT_DISP_WDMA0_EOF = 44,
> +       CMDQ_EVENT_DISP_WDMA1_EOF = 45,
> +       /* Mutex end of frame(EOF) events */
> +       CMDQ_EVENT_MUTEX0_STREAM_EOF = 53,
> +       CMDQ_EVENT_MUTEX1_STREAM_EOF = 54,
> +       CMDQ_EVENT_MUTEX2_STREAM_EOF = 55,
> +       CMDQ_EVENT_MUTEX3_STREAM_EOF = 56,
> +       CMDQ_EVENT_MUTEX4_STREAM_EOF = 57,
> +       /* Display underrun events */
> +       CMDQ_EVENT_DISP_RDMA0_UNDERRUN = 63,
> +       CMDQ_EVENT_DISP_RDMA1_UNDERRUN = 64,
> +       CMDQ_EVENT_DISP_RDMA2_UNDERRUN = 65,
> +       /* Keep this at the end of HW events */
> +       CMDQ_MAX_HW_EVENT_COUNT = 260,
> +       /* GPR events */
> +       CMDQ_SYNC_TOKEN_GPR_SET_0 = 400,
> +       CMDQ_SYNC_TOKEN_GPR_SET_1 = 401,
> +       CMDQ_SYNC_TOKEN_GPR_SET_2 = 402,
> +       CMDQ_SYNC_TOKEN_GPR_SET_3 = 403,
> +       CMDQ_SYNC_TOKEN_GPR_SET_4 = 404,
> +       /* This is max event and also can be used as mask. */
> +       CMDQ_SYNC_TOKEN_MAX = (0x1ff),
> +       /* Invalid event */
> +       CMDQ_SYNC_TOKEN_INVALID = (-1),
> +};
> +
> +/* called after isr done or task done */
> +typedef int (*cmdq_async_flush_cb)(void *data);
> +
> +struct cmdq_task;
> +struct cmdq;
> +
> +struct cmdq_rec {
> +       struct cmdq             *cqctx;
> +       u64                     engine_flag;
> +       int                     scenario;
> +       u32                     block_size; /* command size */
> +       void                    *buf_ptr;
> +       u32                     buf_size;
> +       /* running task after flush */
> +       struct cmdq_task        *running_task_ptr;
> +       /*
> +        * HW thread priority
> +        * high priority implies prefetch
> +        */
> +       enum cmdq_hw_thread_priority    priority;
> +       bool                    finalized;
> +       u32                     prefetch_count;
> +};
> +
> +/**
> + * cmdq_rec_create() - create command queue recorder handle

For all of these comments, I think you mean "command queue record",
not recorder.

Thanks,
-Dan
hs.liao@mediatek.com Jan. 29, 2016, 7:39 a.m. UTC | #2
Hi Dan,

Many thanks for your comments and time.
I reply my plan inline.


On Thu, 2016-01-28 at 12:49 +0800, Daniel Kurtz wrote:
> Hi HS,
> 
> Sorry for the delay.  It is hard to find time to review a >3700 line
> driver :-o in detail....
> 
> Some review comments inline, although I still do not completely
> understand how all that this driver does and how it works.
> I'll try to find time to go through this driver in detail again next
> time you post it for review.
> 
> On Tue, Jan 19, 2016 at 9:14 PM,  <hs.liao@mediatek.com> wrote:
> > From: HS Liao <hs.liao@mediatek.com>
> >
> > This patch is first version of Mediatek Command Queue(CMDQ) driver. The
> > CMDQ is used to help read/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: HS Liao <hs.liao@mediatek.com>
> 
> [snip]
> 
> > diff --git a/drivers/soc/mediatek/mtk-cmdq.c b/drivers/soc/mediatek/mtk-cmdq.c
> > new file mode 100644
> > index 0000000..7570f00
> > --- /dev/null
> > +++ b/drivers/soc/mediatek/mtk-cmdq.c
> 
> [snip]
> 
> > +/*
> > + * Maximum prefetch buffer size.
> > + * Unit is instructions.
> > + */
> > +#define CMDQ_MAX_PREFETCH_INSTUCTION   240
> 
> INSTRUCTION

will fix

> [snip]
> 
> > +
> > +/* get lsb for subsys encoding in arg_a (range: 0 - 31) */
> > +
> > +enum cmdq_eng {
> > +       CMDQ_ENG_DISP_UFOE = 0,
> > +       CMDQ_ENG_DISP_AAL,
> > +       CMDQ_ENG_DISP_COLOR0,
> > +       CMDQ_ENG_DISP_COLOR1,
> > +       CMDQ_ENG_DISP_RDMA0,
> > +       CMDQ_ENG_DISP_RDMA1,
> > +       CMDQ_ENG_DISP_RDMA2,
> > +       CMDQ_ENG_DISP_WDMA0,
> > +       CMDQ_ENG_DISP_WDMA1,
> > +       CMDQ_ENG_DISP_OVL0,
> > +       CMDQ_ENG_DISP_OVL1,
> > +       CMDQ_ENG_DISP_GAMMA,
> > +       CMDQ_ENG_DISP_DSI0_CMD,
> > +       CMDQ_ENG_DISP_DSI1_CMD,
> 
> Why do these last two have "_CMD" at the end?

will remove

> > +       CMDQ_MAX_ENGINE_COUNT   /* ALWAYS keep at the end */
> > +};
> > +
> > +struct cmdq_command {
> > +       struct cmdq             *cqctx;
> > +       u32                     scenario;
> > +       /* task priority (NOT HW thread priority) */
> > +       u32                     priority;
> > +       /* bit flag of used engines */
> > +       u64                     engine_flag;
> > +       /*
> > +        * pointer of instruction buffer
> > +        * This must point to an 64-bit aligned u32 array
> > +        */
> > +       u32                     *va_base;
> 
> All of your "va" and "va_base" should be void *, not u32 *.

will do

> > +       /* size of instruction buffer, in bytes. */
> > +       u32                     block_size;
> 
> Better to use size_t for "size in bytes".

will fix

> > +};
> > +
> > +enum cmdq_code {
> > +       /* These are actual HW op code. */
> > +       CMDQ_CODE_MOVE = 0x02,
> > +       CMDQ_CODE_WRITE = 0x04,
> > +       CMDQ_CODE_JUMP = 0x10,
> > +       CMDQ_CODE_WFE = 0x20,   /* wait for event (and clear) */
> > +       CMDQ_CODE_CLEAR_EVENT = 0x21,   /* clear event */
> > +       CMDQ_CODE_EOC = 0x40,   /* end of command */
> > +};
> > +
> > +enum cmdq_task_state {
> > +       TASK_STATE_IDLE,        /* free task */
> > +       TASK_STATE_BUSY,        /* task running on a thread */
> > +       TASK_STATE_KILLED,      /* task process being killed */
> > +       TASK_STATE_ERROR,       /* task execution error */
> > +       TASK_STATE_DONE,        /* task finished */
> > +       TASK_STATE_WAITING,     /* allocated but waiting for available thread */
> > +};
> > +
> > +struct cmdq_cmd_buf {
> > +       atomic_t                used;
> > +       void                    *va;
> > +       dma_addr_t              pa;
> > +};
> > +
> > +struct cmdq_task_cb {
> > +       /* called by isr */
> > +       cmdq_async_flush_cb     isr_cb;
> > +       void                    *isr_data;
> > +       /* called by releasing task */
> > +       cmdq_async_flush_cb     done_cb;
> > +       void                    *done_data;
> > +};
> > +
> > +struct cmdq_task {
> > +       struct cmdq             *cqctx;
> > +       struct list_head        list_entry;
> > +
> > +       /* state for task life cycle */
> > +       enum cmdq_task_state    task_state;
> > +       /* virtual address of command buffer */
> > +       u32                     *va_base;
> > +       /* physical address of command buffer */
> > +       dma_addr_t              mva_base;
> > +       /* size of allocated command buffer */
> > +       u32                     buf_size;
> 
> size_t

will fix

> > +       /* It points to a cmdq_cmd_buf if this task use command buffer pool. */
> > +       struct cmdq_cmd_buf     *cmd_buf;
> > +
> > +       int                     scenario;
> > +       int                     priority;
> > +       u64                     engine_flag;
> > +       u32                     command_size;
> > +       u32                     num_cmd;
> > +       int                     reorder;
> > +       /* HW thread ID; CMDQ_INVALID_THREAD if not running */
> > +       int                     thread;
> 
> I think this driver will be a lot more clear if you do this:
> 
> struct cmdq_thread *thread;
> 
> And then use "NULL" for "invalid thread" instead of CMDQ_INVALID_THREAD.

I will try to use cmdq_thread instead of thread id if possible.
If CMDQ_INVALID_THREAD id is not necessary any more, I will remove it.

> > +       /* flag of IRQ received */
> > +       int                     irq_flag;
> > +       /* callback functions */
> > +       struct cmdq_task_cb     cb;
> > +       /* work item when auto release is used */
> > +       struct work_struct      auto_release_work;
> > +
> > +       unsigned long long      submit; /* submit time */
> > +
> > +       pid_t                   caller_pid;
> > +       char                    caller_name[TASK_COMM_LEN];
> > +};
> > +
> > +struct cmdq_thread {
> > +       u32                     task_count;
> > +       u32                     wait_cookie;
> > +       u32                     next_cookie;
> > +       struct cmdq_task        *cur_task[CMDQ_MAX_TASK_IN_THREAD];
> > +};
> > +
> > +struct cmdq {
> > +       struct device           *dev;
> > +       struct notifier_block   pm_notifier;
> > +
> > +       void __iomem            *base_va;
> > +       unsigned long           base_pa;
> 
> I think we can remove base_pa (it is only used in a debug print), and just call
> "base_va" as "base".

will do

> > +       u32                     irq;
> > +
> > +       /*
> > +        * task information
> > +        * task_cache: struct cmdq_task object cache
> > +        * task_free_list: unused free tasks
> > +        * task_active_list: active tasks
> > +        * task_consume_wait_queue_item: task consumption work item
> > +        * task_auto_release_wq: auto-release workqueue
> > +        * task_consume_wq: task consumption workqueue (for queued tasks)
> > +        */
> > +       struct kmem_cache       *task_cache;
> > +       struct list_head        task_free_list;
> > +       struct list_head        task_active_list;
> > +       struct list_head        task_wait_list;
> > +       struct work_struct      task_consume_wait_queue_item;
> > +       struct workqueue_struct *task_auto_release_wq;
> > +       struct workqueue_struct *task_consume_wq;
> > +       u16                     task_count[CMDQ_MAX_THREAD_COUNT];
> 
> AFAICT, this task_count is not used?

will remove

> > +
> > +       struct cmdq_thread      thread[CMDQ_MAX_THREAD_COUNT];
> > +
> > +       /* mutex, spinlock, flag */
> > +       struct mutex            task_mutex;     /* for task list */
> > +       struct mutex            clock_mutex;    /* for clock operation */
> > +       spinlock_t              thread_lock;    /* for cmdq hardware thread */
> > +       int                     thread_usage;
> > +       spinlock_t              exec_lock;      /* for exec task */
> > +
> > +       /* suspend */
> > +       bool                    suspended;
> > +
> > +       /* command buffer pool */
> > +       struct cmdq_cmd_buf     cmd_buf_pool[CMDQ_CMD_BUF_POOL_BUF_NUM];
> > +
> > +       /*
> > +        * notification
> > +        * wait_queue: for task done
> > +        * thread_dispatch_queue: for thread acquiring
> > +        */
> > +       wait_queue_head_t       wait_queue[CMDQ_MAX_THREAD_COUNT];
> > +       wait_queue_head_t       thread_dispatch_queue;
> > +
> > +       /* ccf */
> > +       struct clk              *clock;
> > +};
> > +
> > +struct cmdq_event_name {
> > +       enum cmdq_event event;
> > +       char            *name;
> 
> const char *

will do

> > +};
> > +
> > +struct cmdq_subsys {
> > +       u32     base_addr;
> > +       int     id;
> > +       char    *grp_name;
> 
> const char *

will do

> > +};
> > +
> > +static const struct cmdq_event_name g_event_name[] = {
> > +       /* Display start of frame(SOF) events */
> > +       {CMDQ_EVENT_DISP_OVL0_SOF, "CMDQ_EVENT_DISP_OVL0_SOF",},
> 
> You can drop the "," inside "}".

will do

> > +       {CMDQ_EVENT_DISP_OVL1_SOF, "CMDQ_EVENT_DISP_OVL1_SOF",},
> > +       {CMDQ_EVENT_DISP_RDMA0_SOF, "CMDQ_EVENT_DISP_RDMA0_SOF",},
> > +       {CMDQ_EVENT_DISP_RDMA1_SOF, "CMDQ_EVENT_DISP_RDMA1_SOF",},
> > +       {CMDQ_EVENT_DISP_RDMA2_SOF, "CMDQ_EVENT_DISP_RDMA2_SOF",},
> > +       {CMDQ_EVENT_DISP_WDMA0_SOF, "CMDQ_EVENT_DISP_WDMA0_SOF",},
> > +       {CMDQ_EVENT_DISP_WDMA1_SOF, "CMDQ_EVENT_DISP_WDMA1_SOF",},
> > +       /* Display end of frame(EOF) events */
> > +       {CMDQ_EVENT_DISP_OVL0_EOF, "CMDQ_EVENT_DISP_OVL0_EOF",},
> > +       {CMDQ_EVENT_DISP_OVL1_EOF, "CMDQ_EVENT_DISP_OVL1_EOF",},
> > +       {CMDQ_EVENT_DISP_RDMA0_EOF, "CMDQ_EVENT_DISP_RDMA0_EOF",},
> > +       {CMDQ_EVENT_DISP_RDMA1_EOF, "CMDQ_EVENT_DISP_RDMA1_EOF",},
> > +       {CMDQ_EVENT_DISP_RDMA2_EOF, "CMDQ_EVENT_DISP_RDMA2_EOF",},
> > +       {CMDQ_EVENT_DISP_WDMA0_EOF, "CMDQ_EVENT_DISP_WDMA0_EOF",},
> > +       {CMDQ_EVENT_DISP_WDMA1_EOF, "CMDQ_EVENT_DISP_WDMA1_EOF",},
> > +       /* Mutex end of frame(EOF) events */
> > +       {CMDQ_EVENT_MUTEX0_STREAM_EOF, "CMDQ_EVENT_MUTEX0_STREAM_EOF",},
> > +       {CMDQ_EVENT_MUTEX1_STREAM_EOF, "CMDQ_EVENT_MUTEX1_STREAM_EOF",},
> > +       {CMDQ_EVENT_MUTEX2_STREAM_EOF, "CMDQ_EVENT_MUTEX2_STREAM_EOF",},
> > +       {CMDQ_EVENT_MUTEX3_STREAM_EOF, "CMDQ_EVENT_MUTEX3_STREAM_EOF",},
> > +       {CMDQ_EVENT_MUTEX4_STREAM_EOF, "CMDQ_EVENT_MUTEX4_STREAM_EOF",},
> > +       /* Display underrun events */
> > +       {CMDQ_EVENT_DISP_RDMA0_UNDERRUN, "CMDQ_EVENT_DISP_RDMA0_UNDERRUN",},
> > +       {CMDQ_EVENT_DISP_RDMA1_UNDERRUN, "CMDQ_EVENT_DISP_RDMA1_UNDERRUN",},
> > +       {CMDQ_EVENT_DISP_RDMA2_UNDERRUN, "CMDQ_EVENT_DISP_RDMA2_UNDERRUN",},
> > +       /* Keep this at the end of HW events */
> > +       {CMDQ_MAX_HW_EVENT_COUNT, "CMDQ_MAX_HW_EVENT_COUNT",},
> > +       /* GPR events */
> 
> What are "GPR" events?

They are events of General Purpose Register of GCE.
I will remove them if driver doesn't need to take care of them.

> > +       {CMDQ_SYNC_TOKEN_GPR_SET_0, "CMDQ_SYNC_TOKEN_GPR_SET_0",},
> > +       {CMDQ_SYNC_TOKEN_GPR_SET_1, "CMDQ_SYNC_TOKEN_GPR_SET_1",},
> > +       {CMDQ_SYNC_TOKEN_GPR_SET_2, "CMDQ_SYNC_TOKEN_GPR_SET_2",},
> > +       {CMDQ_SYNC_TOKEN_GPR_SET_3, "CMDQ_SYNC_TOKEN_GPR_SET_3",},
> > +       {CMDQ_SYNC_TOKEN_GPR_SET_4, "CMDQ_SYNC_TOKEN_GPR_SET_4",},
> > +       /* This is max event and also can be used as mask. */
> > +       {CMDQ_SYNC_TOKEN_MAX, "CMDQ_SYNC_TOKEN_MAX",},
> > +       /* Invalid event */
> > +       {CMDQ_SYNC_TOKEN_INVALID, "CMDQ_SYNC_TOKEN_INVALID",},
> > +};
> > +
> > +static const struct cmdq_subsys g_subsys[] = {
> > +       {0x1400, 1, "MMSYS"},
> > +       {0x1401, 2, "DISP"},
> > +       {0x1402, 3, "DISP"},
> 
> This isn't going to scale.  These addresses could be different on
> different chips.
> Instead of a static table like this, we probably need specify to the
> connection between gce and other devices via devicetree phandles, and
> then use the phandles to lookup the corresponding device address
> range.

I will define them in device tree.
E.g.
cmdq {
  reg_domain = 0x14000000, 0x14010000, 0x14020000
}

> > +};
> > +
> > +static const char *cmdq_event_get_name(enum cmdq_event event)
> > +{
> > +       int i;
> > +
> > +       for (i = 0; i < ARRAY_SIZE(g_event_name); i++)
> > +               if (g_event_name[i].event == event)
> > +                       return g_event_name[i].name;
> > +
> > +       return "CMDQ_EVENT_UNKNOWN";
> > +}
> > +
> > +static void cmdq_event_set(void __iomem *gce_base_va, enum cmdq_event event)
> > +{
> > +       writel(CMDQ_SYNC_TOKEN_SET | event,
> > +              gce_base_va + CMDQ_SYNC_TOKEN_UPD_OFFSET);
> > +}
> > +
> 
> For any cmdq helper like this, just pass cmdq, and extract base in the
> helper, eg:
> 
> static void cmdq_event_set(struct cmdq *cmdq, enum cmdq_event event)
> {
> writel(CMDQ_SYNC_TOKEN_SET | event,
> cqctx->base + CMDQ_SYNC_TOKEN_UPD_OFFSET);
> }

will do

> [snip]
> 
> > +
> > +static bool cmdq_scenario_is_prefetch(enum cmdq_scenario scenario)
> > +{
> > +       switch (scenario) {
> > +       case CMDQ_SCENARIO_PRIMARY_DISP:
> > +               /*
> > +                * Primary DISP HW should enable prefetch.
> > +                * Since thread 0/1 shares one prefetch buffer,
> > +                * we allow only PRIMARY path to use prefetch.
> > +                */
> 
> I don't do think we want to hard code this policy in the cmdq driver.
> There are systems that will only use the "sub" display channel, these systems
> should be able to do prefetch for the "SUB" display if it is the only
> one in use.

After discussed with Mediatek display owner,
we think prefetch function can be removed.
If we really need it in the future,
I will add back it and give controllable interfaces.

> > +               return true;
> > +       default:
> > +               return false;
> > +       }
> > +
> > +       return false;
> > +}
> 
> [snip]
> 
> > +static u64 cmdq_scenario_get_flag(struct cmdq *cqctx,
> > +                                 enum cmdq_scenario scenario)
> > +{
> > +       struct device *dev = cqctx->dev;
> > +       u64 flag;
> > +
> > +       switch (scenario) {
> > +       case CMDQ_SCENARIO_PRIMARY_DISP:
> > +               flag = ((1LL << CMDQ_ENG_DISP_OVL0) |
> > +                       (1LL << CMDQ_ENG_DISP_COLOR0) |
> > +                       (1LL << CMDQ_ENG_DISP_AAL) |
> > +                       (1LL << CMDQ_ENG_DISP_RDMA0) |
> > +                       (1LL << CMDQ_ENG_DISP_UFOE) |
> > +                       (1LL << CMDQ_ENG_DISP_DSI0_CMD));
> > +               break;
> > +       case CMDQ_SCENARIO_SUB_DISP:
> > +               flag = ((1LL << CMDQ_ENG_DISP_OVL1) |
> > +                       (1LL << CMDQ_ENG_DISP_COLOR1) |
> > +                       (1LL << CMDQ_ENG_DISP_GAMMA) |
> > +                       (1LL << CMDQ_ENG_DISP_RDMA1) |
> > +                       (1LL << CMDQ_ENG_DISP_DSI1_CMD));
> > +               break;
> 
> Instead of encoding policy (these arbitrary "scenarios"), perhaps the
> cmdq driver should just provide these flag bits, and the display
> subsystem can use them to build the "flag" parameter itself.
> 
> After all, the exact configuration of mmsys components is somewhat flexible.

I will remove scenario and provide flags for display driver.

> > +       default:
> > +               dev_err(dev, "unknown scenario type %d\n", scenario);
> > +               flag = 0LL;
> > +               break;
> > +       }
> > +
> > +       return flag;
> > +}
> 
> [snip]
> 
> > +static void cmdq_task_release_unlocked(struct cmdq_task *task)
> > +{
> > +       struct cmdq *cqctx = task->cqctx;
> > +
> > +       /* This func should be inside cqctx->task_mutex mutex */
> > +       lockdep_assert_held(&cqctx->task_mutex);
> > +
> > +       task->task_state = TASK_STATE_IDLE;
> > +       task->thread = CMDQ_INVALID_THREAD;
> > +
> > +       cmdq_task_free_command_buffer(task);
> > +
> > +       /* remove from active/waiting list */
> > +       list_del_init(&task->list_entry);
> > +       /* insert into free list. Currently we don't shrink free list. */
> > +       list_add_tail(&task->list_entry, &cqctx->task_free_list);
> 
> list_move_tail()

will do

> > +}
> 
> [snip]
> 
> > +static struct cmdq_task *cmdq_core_acquire_task(struct cmdq_command *cmd_desc,
> > +                                               struct cmdq_task_cb *cb)
> > +{
> > +       struct cmdq *cqctx = cmd_desc->cqctx;
> > +       struct device *dev = cqctx->dev;
> > +       int status;
> > +       struct cmdq_task *task;
> > +
> > +       task = cmdq_core_find_free_task(cqctx);
> > +       if (!task) {
> > +               dev_err(dev, "can't acquire task info\n");
> > +               return NULL;
> > +       }
> > +
> > +       /* initialize field values */
> > +       task->scenario = cmd_desc->scenario;
> > +       task->priority = cmd_desc->priority;
> > +       task->engine_flag = cmd_desc->engine_flag;
> > +       task->task_state = TASK_STATE_WAITING;
> > +       task->reorder = 0;
> > +       task->thread = CMDQ_INVALID_THREAD;
> > +       task->irq_flag = 0x0;
> > +       memcpy(&task->cb, cb, sizeof(*cb));
> 
> task->cb = *cb;

will do

> > +       task->command_size = cmd_desc->block_size;
> > +
> > +       /* store caller info for debug */
> > +       if (current) {
> > +               task->caller_pid = current->pid;
> > +               memcpy(task->caller_name, current->comm, sizeof(current->comm));
> > +       }
> > +
> > +       status = cmdq_core_sync_command(task, cmd_desc);
> > +       if (status < 0) {
> > +               dev_err(dev, "fail to sync command\n");
> > +               cmdq_task_release_internal(task);
> > +               return NULL;
> > +       }
> > +
> > +       /* insert into waiting list to process */
> > +       mutex_lock(&cqctx->task_mutex);
> > +       if (task) {
> > +               struct list_head *in_item = &cqctx->task_wait_list;
> > +               struct cmdq_task *wait_task = NULL;
> > +               struct list_head *p = NULL;
> > +
> > +               task->submit = sched_clock();
> > +
> > +               /*
> > +                * add to waiting list, keep it sorted by priority
> > +                * so that we add high-priority tasks first.
> > +                */
> > +               list_for_each(p, &cqctx->task_wait_list) {
> > +                       wait_task = list_entry(p, struct cmdq_task, list_entry);
> > +                       /*
> > +                        * keep the list sorted.
> > +                        * higher priority tasks are inserted
> > +                        * in front of the queue
> > +                        */
> > +                       if (wait_task->priority < task->priority)
> > +                               break;
> > +
> > +                       in_item = p;
> > +               }
> > +
> > +               list_add(&task->list_entry, in_item);
> > +       }
> > +       mutex_unlock(&cqctx->task_mutex);
> > +
> > +       return task;
> > +}
> > +
> > +static int cmdq_clk_enable(struct cmdq *cqctx)
> > +{
> > +       struct device *dev = cqctx->dev;
> > +       int ret = 0;
> > +
> > +       if (!cqctx->thread_usage) {
> > +               ret = clk_prepare_enable(cqctx->clock);
> > +               if (ret) {
> > +                       dev_err(dev, "prepare and enable clk:%s fail\n",
> > +                               CMDQ_CLK_NAME);
> > +                       return ret;
> > +               }
> > +               cmdq_event_reset(cqctx);
> 
> AFAICT, we only need to reset the device when it power cycles, not
> whenever we re-enable its clock.
> I think cmdq_event_reset() here should really be a pm_runtime resume handler,
> and "cmdq_clk_enable()" should just do a clk_prepare_enable() and
> pm_runtime_get().
> 
> Then you won't need your own custom refcounting (thread_usage).
> You won't need clock_mutex, either.

I will remove it and let display driver to control clear event.

> > +       }
> > +       cqctx->thread_usage++;
> > +
> > +       return ret;
> > +}
> > +
> > +static void cmdq_clk_disable(struct cmdq *cqctx)
> > +{
> > +       cqctx->thread_usage--;
> > +       if (cqctx->thread_usage <= 0)
> > +               clk_disable_unprepare(cqctx->clock);
> > +}
> > +
> > +static int cmdq_core_find_free_thread(struct cmdq *cqctx, u32 scenario)
> > +{
> > +       struct device *dev = cqctx->dev;
> > +       struct cmdq_thread *thread;
> > +       int tid;
> > +       u32 next_cookie;
> > +
> > +       thread = cqctx->thread;
> > +       tid = cmdq_scenario_get_thread(scenario);
> 
> cmdq_core_acquire_thread() and cmdq_scenario_get_thread() should take
> cmdq, and return a struct cmdq_thread * instead of tid.
> 
> On error, they should either return a standard linux error
> (-EBUSY/-EINVAL, etc) as ERR_PTR(), or if fine-grained error detection
> is not required, they can just return NULL.

will do

> > +
> > +       /*
> > +        * Currently, we only support disp,
> > +        * so it's error if tid is CMDQ_INVALID_THREAD.
> > +        */
> > +       if (tid == CMDQ_INVALID_THREAD) {
> > +               dev_err(dev, "got CMDQ_INVALID_THREAD!!!\n");
> > +               return tid;
> > +       }
> > +
> > +       /*
> > +        * make sure the found thread has enough space for the task;
> > +        * cmdq_thread->cur_task has size limitation.
> > +        */
> > +       if (thread[tid].task_count >= CMDQ_MAX_TASK_IN_THREAD)
> > +               tid = CMDQ_INVALID_THREAD;
> > +
> > +       next_cookie = thread[tid].next_cookie % CMDQ_MAX_TASK_IN_THREAD;
> > +       if (thread[tid].cur_task[next_cookie])
> > +               tid = CMDQ_INVALID_THREAD;
> > +
> > +       return tid;
> > +}
> 
> [snip]
> 
> > +static int cmdq_thread_suspend(struct cmdq *cqctx, int tid)
> > +{
> > +       struct device *dev = cqctx->dev;
> > +       void __iomem *gce_base_va = cqctx->base_va;
> > +       u32 enabled;
> > +       u32 status;
> > +
> > +       /* write suspend bit */
> > +       writel(CMDQ_THR_SUSPEND,
> > +              gce_base_va + CMDQ_THR_SUSPEND_TASK_OFFSET +
> > +              CMDQ_THR_SHIFT * tid);
> 
> All of these per-thread register accesses would be cleaner if we just
> they were in helper functions on the struct cmdq_thread *:
> 
> 
> First, when initializing cmdq->thread, save the thread's base:
> 
> thread->base = cmdq->base + cmdq_thread_to_tid(cmdq, thread) * CMDQ_THR_SHIFT;
> 
> Then, use these simple helper functions:
> 
> static void cmdq_thread_writel(struct cmdq_thread *thread, u32 value,
>       unsigned offset)
> {
> writel(value, thread->base + offset);
> }
> 
> static u32 cmdq_thread_readl(struct cmdq_thread *thread, unsigned offset)
> {
> return readl(thread->base + offset);
> }
> 
> cmdq_thread_writel(thread, CMDQ_THR_SUSPEND, CMDQ_THR_SUSPEND_TASK_OFFSET);
> enable = cmdq_thread_readl(thread, CMDQ_THR_ENABLE_TASK_OFFSET);

will do

> > +
> > +       /* If already disabled, treat as suspended successful. */
> > +       enabled = readl(gce_base_va + CMDQ_THR_ENABLE_TASK_OFFSET +
> > +                       CMDQ_THR_SHIFT * tid);
> > +       if (!(enabled & CMDQ_THR_ENABLED))
> > +               return 0;
> > +
> > +       /* poll suspended status */
> > +       if (readl_poll_timeout_atomic(gce_base_va +
> > +                                     CMDQ_THR_CURR_STATUS_OFFSET +
> > +                                     CMDQ_THR_SHIFT * tid,
> > +                                     status,
> > +                                     status & CMDQ_THR_STATUS_SUSPENDED,
> > +                                     0, 10)) {
> > +               dev_err(dev, "Suspend HW thread %d failed\n", tid);
> > +               return -EFAULT;
> > +       }
> > +
> > +       return 0;
> > +}
> 
> [snip]
> 
> > +static void cmdq_core_parse_error(struct cmdq_task *task, int tid,
> > +                                 const char **module_name, int *flag,
> > +                                 u32 *inst_a, u32 *inst_b)
> 
> Hmm. Instead of back calculating details of the failing task, I think it might
> be more natural to record these details when creating the command

Because CMDQ needs to deal with time critical situation,
we only parse command into human readable log if failed.

>  shouldn't you be
> able to use the task's cookie to look it up on the list of tasks to
> get its details?

We have known task here, so we don't need to get task from cookie.

> > +{
> > +       int irq_flag = task->irq_flag;
> > +       u32 insts[2] = { 0 };
> > +       const char *module;
> > +
> > +       /*
> > +        * other cases, use instruction to judge
> > +        * because scenario / HW flag are not sufficient
> > +        */
> > +       if (cmdq_task_get_pc_and_inst(task, tid, insts)) {
> > +               u32 op, arg_a, arg_b;
> > +
> > +               op = insts[1] >> CMDQ_OP_CODE_SHIFT;
> > +               arg_a = insts[1] & CMDQ_ARG_A_MASK;
> > +               arg_b = insts[0];
> > +
> > +               switch (op) {
> > +               case CMDQ_CODE_WRITE:
> > +                       module = cmdq_core_parse_module_from_subsys(arg_a);
> > +                       break;
> > +               case CMDQ_CODE_WFE:
> > +                       /* arg_a is the event id */
> > +                       module = cmdq_event_get_module((enum cmdq_event)arg_a);
> > +                       break;
> > +               case CMDQ_CODE_MOVE:
> > +               case CMDQ_CODE_JUMP:
> > +               case CMDQ_CODE_EOC:
> > +               default:
> > +                       module = "CMDQ";
> > +                       break;
> > +               }
> > +       } else {
> > +               module = "CMDQ";
> > +       }
> > +
> > +       /* fill output parameter */
> > +       *module_name = module;
> > +       *flag = irq_flag;
> > +       *inst_a = insts[1];
> > +       *inst_b = insts[0];
> > +}
> 
> [snip]
> 
> > +static int cmdq_task_insert_into_thread(struct cmdq_task *last,
> > +                                       struct cmdq_task *task,
> > +                                       int tid, int loop)
> > +{
> > +       struct cmdq *cqctx = task->cqctx;
> > +       struct device *dev = cqctx->dev;
> > +       void __iomem *gce_base_va = cqctx->base_va;
> > +       int status = 0;
> > +       struct cmdq_thread *thread;
> > +       struct cmdq_task *prev_task;
> > +       int index;
> > +       int prev;
> > +       int cookie;
> > +
> > +       thread = &cqctx->thread[tid];
> > +       cookie = thread->next_cookie;
> > +
> > +       /*
> > +        * Traverse forward to adjust tasks' order
> > +        * according to their priorities.
> > +        */
> 
> AFAICT, this driver does not actually use priorities, so this can all
> be simplified.
> If we ever implement priorities, we can add this back, since there
> will then be a way
> to test it.

will remove

> > +       for (prev = (cookie % CMDQ_MAX_TASK_IN_THREAD); loop > 0; loop--) {
> > +               index = prev;
> > +               if (index < 0)
> > +                       index = CMDQ_MAX_TASK_IN_THREAD - 1;
> > +
> > +               prev = index - 1;
> > +               if (prev < 0)
> > +                       prev = CMDQ_MAX_TASK_IN_THREAD - 1;
> > +
> > +               prev_task = thread->cur_task[prev];
> > +
> > +               /* maybe the job is killed, search a new one */
> > +               for (; !prev_task && loop > 1; loop--) {
> > +                       dev_err(dev,
> > +                               "prev_task is NULL, prev:%d, loop:%d, index:%d\n",
> > +                               prev, loop, index);
> > +
> > +                       prev--;
> > +                       if (prev < 0)
> > +                               prev = CMDQ_MAX_TASK_IN_THREAD - 1;
> > +
> > +                       prev_task = thread->cur_task[prev];
> > +               }
> > +
> > +               if (!prev_task) {
> > +                       dev_err(dev,
> > +                               "invalid task state for reorder %d %d\n",
> > +                               index, loop);
> > +                       status = -EFAULT;
> > +                       break;
> > +               }
> > +
> > +               /* insert this task */
> > +               if (loop <= 1) {
> > +                       thread->cur_task[index] = task;
> > +                       /* Jump: Absolute */
> > +                       prev_task->va_base[prev_task->num_cmd - 1] =
> > +                                       CMDQ_JUMP_BY_PA;
> > +                       /* Jump to here */
> > +                       prev_task->va_base[prev_task->num_cmd - 2] =
> > +                                       task->mva_base;
> > +
> > +                       /* re-fetch command buffer again. */
> > +                       cmdq_core_invalidate_hw_fetched_buffer(
> > +                                       gce_base_va, tid);
> > +
> > +                       break;
> > +               }
> > +
> > +               if (prev_task->priority < task->priority) {
> > +                       /* new task has higher priority */
> > +
> > +                       thread->cur_task[index] = prev_task;
> > +                       prev_task->va_base[prev_task->num_cmd - 1] =
> > +                                       task->va_base[task->num_cmd - 1];
> > +                       prev_task->va_base[prev_task->num_cmd - 2] =
> > +                                       task->va_base[task->num_cmd - 2];
> > +
> > +                       /* Boot priority for the task */
> > +                       prev_task->priority += CMDQ_MIN_AGE_VALUE;
> > +                       prev_task->reorder++;
> > +
> > +                       thread->cur_task[prev] = task;
> > +                       /* Jump: Absolute */
> > +                       task->va_base[task->num_cmd - 1] = CMDQ_JUMP_BY_PA;
> > +                       /* Jump to here */
> > +                       task->va_base[task->num_cmd - 2] = prev_task->mva_base;
> > +
> > +                       /* re-fetch command buffer again. */
> > +                       cmdq_core_invalidate_hw_fetched_buffer(
> > +                                       gce_base_va, tid);
> > +
> > +                       if (last == task)
> > +                               last = prev_task;
> > +               } else {
> > +                       /* previous task has higher priority */
> > +
> > +                       thread->cur_task[index] = task;
> > +                       /* Jump: Absolute */
> > +                       prev_task->va_base[prev_task->num_cmd - 1] =
> > +                                       CMDQ_JUMP_BY_PA;
> > +                       /* Jump to here */
> > +                       prev_task->va_base[prev_task->num_cmd - 2] =
> > +                                       task->mva_base;
> > +
> > +                       /* re-fetch command buffer again. */
> > +                       cmdq_core_invalidate_hw_fetched_buffer(
> > +                                       gce_base_va, tid);
> > +
> > +                       break;
> > +               }
> > +       }
> > +
> > +       return status;
> > +}
> > +
> > +static int cmdq_task_exec_async_impl(struct cmdq_task *task, int tid)
> > +{
> > +       struct cmdq *cqctx = task->cqctx;
> > +       struct device *dev = cqctx->dev;
> > +       void __iomem *gce_base_va = cqctx->base_va;
> > +       int status;
> > +       struct cmdq_thread *thread;
> > +       struct cmdq_task *last_task;
> > +       unsigned long flags;
> > +       int loop;
> > +       int minimum;
> > +       int cookie;
> > +       int thread_prio;
> > +
> > +       status = 0;
> > +       thread = &cqctx->thread[tid];
> > +
> > +       spin_lock_irqsave(&cqctx->exec_lock, flags);
> > +
> > +       /* update task's thread info */
> > +       task->thread = tid;
> > +       task->irq_flag = 0;
> > +       task->task_state = TASK_STATE_BUSY;
> > +
> > +       if (thread->task_count <= 0) {
> > +               bool is_prefetch;
> > +
> > +               if (cmdq_thread_reset(cqctx, tid) < 0) {
> 
> Do we really have to reset with the spin lock held and irqs disabled?
> This could take a while, right?

About exec_lock spinlock, I will rewrite it,
but need more time to make sure the correctness of new method.

> > +                       spin_unlock_irqrestore(&cqctx->exec_lock, flags);
> > +                       return -EFAULT;
> > +               }
> > +
> > +               writel(CMDQ_THR_NO_TIMEOUT,
> > +                      gce_base_va + CMDQ_THR_INST_CYCLES_OFFSET +
> > +                      CMDQ_THR_SHIFT * tid);
> > +
> > +               is_prefetch = cmdq_scenario_is_prefetch(task->scenario);
> > +               if (is_prefetch) {
> > +                       writel(CMDQ_THR_PREFETCH_EN,
> > +                              gce_base_va + CMDQ_THR_PREFETCH_OFFSET +
> > +                              CMDQ_THR_SHIFT * tid);
> > +               }
> > +
> > +               thread_prio = CMDQ_THR_PRIO_DISPLAY_CONFIG;
> > +               writel(task->mva_base,
> > +                      gce_base_va + CMDQ_THR_CURR_ADDR_OFFSET +
> > +                      CMDQ_THR_SHIFT * tid);
> > +               writel(task->mva_base + task->command_size,
> > +                      gce_base_va + CMDQ_THR_END_ADDR_OFFSET +
> > +                      CMDQ_THR_SHIFT * tid);
> > +               writel(thread_prio & CMDQ_THR_PRIORITY_MASK,
> > +                      gce_base_va + CMDQ_THR_CFG_OFFSET +
> > +                      CMDQ_THR_SHIFT * tid);
> > +
> > +               writel(CMDQ_THR_IRQ_EN,
> > +                      gce_base_va + CMDQ_THR_IRQ_ENABLE_OFFSET +
> > +                      CMDQ_THR_SHIFT * tid);
> > +
> > +               minimum = cmdq_thread_get_cookie(gce_base_va, tid);
> > +               cmdq_thread_insert_task_by_cookie(
> > +                               thread, task, (minimum + 1), true);
> > +
> > +               /* verify that we don't corrupt EOC + JUMP pattern */
> > +               cmdq_core_verfiy_task_command_end(task);
> > +
> > +               /* enable HW thread */
> > +               writel(CMDQ_THR_ENABLED,
> > +                      gce_base_va + CMDQ_THR_ENABLE_TASK_OFFSET +
> > +                      CMDQ_THR_SHIFT * tid);
> > +       } else {
> > +               unsigned long curr_pa, end_pa;
> > +
> > +               status = cmdq_thread_suspend(cqctx, tid);
> > +               if (status < 0) {
> > +                       spin_unlock_irqrestore(&cqctx->exec_lock, flags);
> > +                       return status;
> > +               }
> > +
> > +               writel(CMDQ_THR_NO_TIMEOUT,
> > +                      gce_base_va + CMDQ_THR_INST_CYCLES_OFFSET +
> > +                      CMDQ_THR_SHIFT * tid);
> > +
> > +               cookie = thread->next_cookie;
> > +
> > +               /*
> > +                * Boundary case tested: EOC have been executed,
> > +                *                       but JUMP is not executed
> > +                * Thread PC: 0x9edc0dd8, End: 0x9edc0de0,
> > +                * Curr Cookie: 1, Next Cookie: 2
> > +                * PC = END - 8, EOC is executed
> > +                * PC = END - 0, All CMDs are executed
> > +                */
> > +
> > +               curr_pa = (unsigned long)readl(gce_base_va +
> > +                                              CMDQ_THR_CURR_ADDR_OFFSET +
> > +                                              CMDQ_THR_SHIFT * tid);
> > +               end_pa = (unsigned long)readl(gce_base_va +
> > +                                             CMDQ_THR_END_ADDR_OFFSET +
> > +                                             CMDQ_THR_SHIFT * tid);
> > +               if ((curr_pa == end_pa - 8) || (curr_pa == end_pa - 0)) {
> > +                       /* set to task directly */
> > +                       writel(task->mva_base,
> > +                              gce_base_va + CMDQ_THR_CURR_ADDR_OFFSET +
> > +                              CMDQ_THR_SHIFT * tid);
> > +                       writel(task->mva_base + task->command_size,
> > +                              gce_base_va + CMDQ_THR_END_ADDR_OFFSET +
> > +                              CMDQ_THR_SHIFT * tid);
> > +                       thread->cur_task[cookie % CMDQ_MAX_TASK_IN_THREAD] = task;
> > +                       thread->task_count++;
> > +               } else {
> > +                       /* Current task that shuld be processed */
> > +                       minimum = cmdq_thread_get_cookie(gce_base_va, tid) + 1;
> > +                       if (minimum > CMDQ_MAX_COOKIE_VALUE)
> > +                               minimum = 0;
> > +
> > +                       /* Calculate loop count to adjust the tasks' order */
> > +                       if (minimum <= cookie)
> > +                               loop = cookie - minimum;
> > +                       else
> > +                               /* Counter wrapped */
> > +                               loop = (CMDQ_MAX_COOKIE_VALUE - minimum + 1) +
> > +                                      cookie;
> > +
> > +                       if (loop < 0) {
> > +                               dev_err(dev, "reorder fail:\n");
> > +                               dev_err(dev, "  task count=%d\n", loop);
> > +                               dev_err(dev, "  thread=%d\n", tid);
> > +                               dev_err(dev, "  next cookie=%d\n",
> > +                                       thread->next_cookie);
> > +                               dev_err(dev, "  (HW) next cookie=%d\n",
> > +                                       minimum);
> > +                               dev_err(dev, "  task=0x%p\n", task);
> > +
> > +                               spin_unlock_irqrestore(&cqctx->exec_lock,
> > +                                                      flags);
> > +                               return -EFAULT;
> > +                       }
> > +
> > +                       if (loop > CMDQ_MAX_TASK_IN_THREAD)
> > +                               loop %= CMDQ_MAX_TASK_IN_THREAD;
> > +
> > +                       /*
> > +                        * By default, task is the last task,
> > +                        * and insert [cookie % CMDQ_MAX_TASK_IN_THREAD]
> > +                        */
> > +                       last_task = task;       /* Default last task */
> > +
> > +                       status = cmdq_task_insert_into_thread(
> > +                                       last_task, task, tid, loop);
> > +                       if (status < 0) {
> > +                               spin_unlock_irqrestore(
> > +                                               &cqctx->exec_lock, flags);
> > +                               dev_err(dev,
> > +                                       "invalid task state for reorder.\n");
> > +                               return status;
> > +                       }
> > +
> > +                       smp_mb(); /* modify jump before enable thread */
> > +
> > +                       writel(task->mva_base + task->command_size,
> > +                              gce_base_va + CMDQ_THR_END_ADDR_OFFSET +
> > +                              CMDQ_THR_SHIFT * tid);
> > +                       thread->task_count++;
> > +               }
> > +
> > +               thread->next_cookie += 1;
> > +               if (thread->next_cookie > CMDQ_MAX_COOKIE_VALUE)
> > +                       thread->next_cookie = 0;
> > +
> > +               /* verify that we don't corrupt EOC + JUMP pattern */
> > +               cmdq_core_verfiy_task_command_end(task);
> > +
> > +               /* resume HW thread */
> > +               cmdq_thread_resume(cqctx, tid);
> > +       }
> > +
> > +       spin_unlock_irqrestore(&cqctx->exec_lock, flags);
> > +
> > +       return status;
> > +}
> 
> [snip]
> 
> > +static int cmdq_core_resumed_notifier(struct cmdq *cqctx)
> > +{
> > +       /*
> > +        * Note:
> > +        * delay resume timing until process-unfreeze done in order to
> > +        * ensure M4U driver had restore M4U port setting
> > +        */
> > +
> > +       unsigned long flags = 0L;
> > +
> > +       spin_lock_irqsave(&cqctx->thread_lock, flags);
> > +       cqctx->suspended = false;
> > +
> > +       /*
> > +        * during suspending, there may be queued tasks.
> > +        * we should process them if any.
> > +        */
> > +       if (!work_pending(&cqctx->task_consume_wait_queue_item))
> > +               /* we use system global work queue (kernel thread kworker/n) */
> > +               queue_work(cqctx->task_consume_wq,
> > +                          &cqctx->task_consume_wait_queue_item);
> > +
> > +       spin_unlock_irqrestore(&cqctx->thread_lock, flags);
> > +
> > +       return 0;
> > +}
> > +
> > +static int cmdq_task_exec_async_with_retry(struct cmdq_task *task, int tid)
> 
> Why do we want to automatically retry cmds that failed?

will remove

> > +{
> > +       struct device *dev = task->cqctx->dev;
> > +       int retry;
> > +       int status;
> > +
> > +       retry = 0;
> > +       status = -EFAULT;
> > +       do {
> > +               cmdq_core_verfiy_task_command_end(task);
> > +
> > +               status = cmdq_task_exec_async_impl(task, tid);
> > +
> > +               if (status >= 0)
> > +                       break;
> > +
> > +               if ((task->task_state == TASK_STATE_KILLED) ||
> > +                   (task->task_state == TASK_STATE_ERROR)) {
> > +                       dev_err(dev, "cmdq_task_exec_async_impl fail\n");
> > +                       status = -EFAULT;
> > +                       break;
> > +               }
> > +
> > +               retry++;
> > +       } while (retry < CMDQ_MAX_RETRY_COUNT);
> > +
> > +       return status;
> > +}
> > +
> > +static int cmdq_core_get_time_in_ms(unsigned long long start,
> > +                                   unsigned long long end)
> > +{
> > +       unsigned long long _duration = end - start;
> > +
> > +       do_div(_duration, 1000000);
> > +       return (int)_duration;
> > +}
> > +
> > +static void cmdq_core_consume_waiting_list(struct work_struct *work)
> > +{
> > +       struct list_head *p, *n = NULL;
> > +       bool thread_acquired;
> > +       unsigned long long consume_time;
> > +       int waiting_time_ms;
> > +       bool need_log;
> > +       struct cmdq *cqctx;
> > +       struct device *dev;
> > +
> > +       cqctx = container_of(work, struct cmdq,
> > +                            task_consume_wait_queue_item);
> > +       dev = cqctx->dev;
> > +
> > +       /*
> > +        * when we're suspending,
> > +        * do not execute any tasks. delay & hold them.
> > +        */
> > +       if (cqctx->suspended)
> > +               return;
> > +
> > +       consume_time = sched_clock();
> > +
> > +       mutex_lock(&cqctx->task_mutex);
> > +
> > +       thread_acquired = false;
> > +
> > +       /* scan and remove (if executed) waiting tasks */
> > +       list_for_each_safe(p, n, &cqctx->task_wait_list) {
> > +               struct cmdq_task *task;
> > +               struct cmdq_thread *thread = NULL;
> > +               int tid;
> > +               int status;
> > +               enum cmdq_hw_thread_priority thread_prio;
> > +
> > +               task = list_entry(p, struct cmdq_task, list_entry);
> > +
> > +               thread_prio = CMDQ_THR_PRIO_DISPLAY_CONFIG;
> > +
> > +               waiting_time_ms = cmdq_core_get_time_in_ms(
> > +                               task->submit, consume_time);
> > +               need_log = waiting_time_ms >= CMDQ_PREALARM_TIMEOUT_MS;
> > +               /* allocate hw thread */
> > +               tid = cmdq_core_acquire_thread(cqctx, task->scenario);
> > +               if (tid != CMDQ_INVALID_THREAD)
> > +                       thread = &cqctx->thread[tid];
> > +
> > +               if (tid == CMDQ_INVALID_THREAD || !thread) {
> 
> Move computation of consume_time, waiting_time_ms & need_log here, when
> printing a warning message, since this is the only place you need them.
> Also, why bother converting to ms?  Just leave time in unsigned long long.

will remove converting

> BTW, why sched_clock() instead of ktime?

will use ktime

> > +                       /* have to wait, remain in wait list */
> > +                       dev_warn(dev, "acquire thread fail, need to wait\n");
> > +                       if (need_log) /* task wait too long */
> > +                               dev_warn(dev, "waiting:%dms, task:0x%p\n",
> > +                                        waiting_time_ms, task);
> > +                       continue;
> > +               }
> > +
> > +               /* some task is ready to run */
> > +               thread_acquired = true;
> > +
> > +               /*
> > +                * start execution
> > +                * remove from wait list and put into active list
> > +                */
> > +               list_del_init(&task->list_entry);
> > +               list_add_tail(&task->list_entry,
> > +                             &cqctx->task_active_list);
> 
> list_move_tail(&task->list_entry, &cqctx->task_active_list);

will do

> > +
> > +               /* run task on thread */
> > +               status = cmdq_task_exec_async_with_retry(task, tid);
> > +               if (status < 0) {
> > +                       dev_err(dev, "%s fail, release task 0x%p\n",
> > +                               __func__, task);
> > +                       cmdq_task_remove_thread(task);
> > +                       cmdq_task_release_unlocked(task);
> > +                       task = NULL;
> > +               }
> > +       }
> > +
> > +       if (thread_acquired) {
> > +               /*
> > +                * notify some task's sw thread to change their waiting state.
> > +                * (if they have already called cmdq_task_wait_and_release())
> > +                */
> > +               wake_up_all(&cqctx->thread_dispatch_queue);
> > +       }
> > +
> > +       mutex_unlock(&cqctx->task_mutex);
> > +}
> 
> [snip]
> 
> 
> > +static int cmdq_task_wait_result(struct cmdq_task *task, int tid, int wait_q)
> > +{
> > +       struct cmdq *cqctx = task->cqctx;
> > +       struct cmdq_thread *thread = &cqctx->thread[tid];
> > +       int status = 0;
> > +       unsigned long flags;
> > +       struct cmdq_task_error_report error_report = {
> > +               .throw_err = false,
> > +               .module = NULL,
> > +               .inst_a = 0,
> > +               .inst_b = 0,
> > +               .irq_flag = 0,
> > +       };
> 
> This should be sufficient:
>  struct cmdq_task_error_report error_report = { 0 };

will do

> > +
> > +       /*
> > +        * Note that although we disable IRQ, HW continues to execute
> > +        * so it's possible to have pending IRQ
> > +        */
> > +       spin_lock_irqsave(&cqctx->exec_lock, flags);
> > +
> > +       if (task->task_state != TASK_STATE_DONE)
> > +               status = cmdq_task_handle_error_result(
> > +                               task, tid, wait_q, &error_report);
> 
> This error handling does too much with the spin lock held and irqs disabled.

About exec_lock spinlock, I will rewrite it.

> > +
> > +       if (thread->task_count <= 0)
> > +               cmdq_thread_disable(cqctx, tid);
> > +       else
> > +               cmdq_thread_resume(cqctx, tid);
> > +
> > +       spin_unlock_irqrestore(&cqctx->exec_lock, flags);
> > +
> > +       if (error_report.throw_err) {
> > +               u32 op = error_report.inst_a >> CMDQ_OP_CODE_SHIFT;
> > +
> > +               switch (op) {
> > +               case CMDQ_CODE_WFE:
> > +                       dev_err(cqctx->dev,
> > +                               "%s in CMDQ IRQ:0x%02x, INST:(0x%08x, 0x%08x), OP:WAIT EVENT:%s\n",
> > +                               error_report.module, error_report.irq_flag,
> > +                               error_report.inst_a, error_report.inst_b,
> > +                               cmdq_event_get_name(error_report.inst_a &
> > +                                                   CMDQ_ARG_A_MASK));
> > +                       break;
> > +               default:
> > +                       dev_err(cqctx->dev,
> > +                               "%s in CMDQ IRQ:0x%02x, INST:(0x%08x, 0x%08x), OP:%s\n",
> > +                               error_report.module, error_report.irq_flag,
> > +                               error_report.inst_a, error_report.inst_b,
> > +                               cmdq_core_parse_op(op));
> > +                       break;
> > +               }
> > +       }
> > +
> > +       return status;
> > +}
> > +
> > +static int cmdq_task_wait_done(struct cmdq_task *task)
> > +{
> > +       struct cmdq *cqctx = task->cqctx;
> > +       struct device *dev = cqctx->dev;
> > +       int wait_q;
> > +       int tid;
> > +       unsigned long timeout = msecs_to_jiffies(
> > +                       CMDQ_ACQUIRE_THREAD_TIMEOUT_MS);
> > +
> > +       tid = task->thread;
> > +       if (tid == CMDQ_INVALID_THREAD) {
> 
> You don't need this first "if" check.
> Just do the wait_event_timeout() here.
> If tid != CMDQ_INVALID_THREAD, it will return immediately.

will do

> > +               /*
> > +                * wait for acquire thread
> > +                * (this is done by cmdq_core_consume_waiting_list);
> > +                */
> > +               wait_q = wait_event_timeout(
> > +                               cqctx->thread_dispatch_queue,
> > +                               (task->thread != CMDQ_INVALID_THREAD), timeout);
> > +
> > +               if (!wait_q || task->thread == CMDQ_INVALID_THREAD) {
> 
> Why "task->thread == CMDQ_INVALID_THREAD" check here?  It is either
> not needed or racy.

will remove

> > +                       mutex_lock(&cqctx->task_mutex);
> > +
> > +                       /*
> > +                        * it's possible that the task was just consumed now.
> > +                        * so check again.
> > +                        */
> > +                       if (task->thread == CMDQ_INVALID_THREAD) {
> > +                               /*
> > +                                * Task may have released,
> > +                                * or starved to death.
> > +                                */
> > +                               dev_err(dev,
> > +                                       "task(0x%p) timeout with invalid thread\n",
> > +                                       task);
> > +
> > +                               /*
> > +                                * remove from waiting list,
> > +                                * so that it won't be consumed in the future
> > +                                */
> > +                               list_del_init(&task->list_entry);
> > +
> > +                               mutex_unlock(&cqctx->task_mutex);
> > +                               return -EINVAL;
> > +                       }
> > +
> > +                       /* valid thread, so we keep going */
> > +                       mutex_unlock(&cqctx->task_mutex);
> > +               }
> > +       }
> > +
> > +       tid = task->thread;
> > +       if (tid < 0 || tid >= CMDQ_MAX_THREAD_COUNT) {
> > +               dev_err(dev, "invalid thread %d in %s\n", tid, __func__);
> > +               return -EINVAL;
> > +       }
> > +
> > +       /* start to wait */
> > +       wait_q = wait_event_timeout(task->cqctx->wait_queue[tid],
> > +                                   (task->task_state != TASK_STATE_BUSY &&
> > +                                    task->task_state != TASK_STATE_WAITING),
> > +                                   msecs_to_jiffies(CMDQ_DEFAULT_TIMEOUT_MS));
> > +       if (!wait_q)
> > +               dev_dbg(dev, "timeout!\n");
> > +
> > +       /* wake up and continue */
> > +       return cmdq_task_wait_result(task, tid, wait_q);
> > +}
> > +
> > +static int cmdq_task_wait_and_release(struct cmdq_task *task)
> > +{
> > +       struct cmdq *cqctx;
> > +       int status;
> > +       int tid;
> > +
> > +       if (!task) {
> > +               pr_err("%s err ptr=0x%p\n", __func__, task);
> > +               return -EFAULT;
> > +       }
> > +
> > +       if (task->task_state == TASK_STATE_IDLE) {
> > +               pr_err("%s task=0x%p is IDLE\n", __func__, task);
> > +               return -EFAULT;
> > +       }
> > +
> > +       cqctx = task->cqctx;
> > +
> > +       /* wait for task finish */
> > +       tid = task->thread;
> 
> tid is not used.

will remove

> > +       status = cmdq_task_wait_done(task);
> > +
> > +       /* release */
> > +       cmdq_task_remove_thread(task);
> > +       cmdq_task_release_internal(task);
> > +
> > +       return status;
> > +}
> > +
> > +static void cmdq_core_auto_release_work(struct work_struct *work_item)
> > +{
> > +       struct cmdq_task *task;
> > +       int status;
> > +       struct cmdq_task_cb cb;
> > +
> > +       task = container_of(work_item, struct cmdq_task, auto_release_work);
> > +       memcpy(&cb, &task->cb, sizeof(cb));
> 
> Just:
>   cb = task->cb;

will do

> But, why do you need to make a copy?
> Can't you just access task->cb directly?

This is because task will be released before cb is called.

> > +       status = cmdq_task_wait_and_release(task);
> > +       task = NULL;
> 
> task is not used again, don't set to NULL.

will do

> > +
> > +       /* isr fail, so call isr_cb here to prevent lock */
> > +       if (status && cb.isr_cb)
> > +               cb.isr_cb(cb.isr_data);
> > +
> > +       if (cb.done_cb)
> > +               cb.done_cb(cb.done_data);
> > +}
> 
> [snip]
> 
> 
> > +static irqreturn_t cmdq_irq_handler(int irq, void *dev)
> > +{
> > +       struct cmdq *cqctx = dev;
> > +       int i;
> > +       u32 irq_status;
> > +       bool handled = false;
> > +
> > +       if (cqctx->irq == irq) {
> > +               irq_status = readl(cqctx->base_va +
> > +                                  CMDQ_CURR_IRQ_STATUS_OFFSET);
> > +               irq_status &= CMDQ_IRQ_MASK;
> > +               for (i = 0;
> > +                    (irq_status != CMDQ_IRQ_MASK) && i < CMDQ_MAX_THREAD_COUNT;
> > +                    i++) {
> > +                       /* STATUS bit set to 0 means IRQ asserted */
> > +                       if (irq_status & BIT(i))
> > +                               continue;
> > +
> > +                       /*
> > +                        * We mark irq_status to 1 to denote finished
> > +                        * processing, and we can early-exit if no more
> > +                        * threads being asserted.
> > +                        */
> > +                       irq_status |= BIT(i);
> > +
> > +                       cmdq_core_handle_irq(cqctx, i);
> > +                       handled = true;
> > +               }
> > +       }
> > +
> > +       if (handled) {
> > +               queue_work(cqctx->task_consume_wq,
> > +                          &cqctx->task_consume_wait_queue_item);
> 
> Since you need to queue work anyway, why no just do this all in a threaded irq
> and use a mutex instead of spinlock for exec_lock.

I will take care of this issue with exec_lock spinlock issue together.

> > +               return IRQ_HANDLED;
> > +       }
> > +
> > +       return IRQ_NONE;
> > +}
> > +
> > +static int cmdq_core_initialize(struct platform_device *pdev,
> > +                               struct cmdq **cqctx)
> > +{
> > +       struct cmdq *lcqctx; /* local cmdq context */
> > +       int i;
> > +       int ret = 0;
> > +
> > +       lcqctx = devm_kzalloc(&pdev->dev, sizeof(*lcqctx), GFP_KERNEL);
> > +
> > +       /* save dev */
> > +       lcqctx->dev = &pdev->dev;
> > +
> > +       /* initial cmdq device related data */
> > +       ret = cmdq_dev_init(pdev, lcqctx);
> > +       if (ret) {
> > +               dev_err(&pdev->dev, "failed to init cmdq device\n");
> > +               goto fail_dev;
> > +       }
> > +
> > +       /* initial mutex, spinlock */
> > +       mutex_init(&lcqctx->task_mutex);
> > +       mutex_init(&lcqctx->clock_mutex);
> > +       spin_lock_init(&lcqctx->thread_lock);
> > +       spin_lock_init(&lcqctx->exec_lock);
> > +
> > +       /* initial wait queue for notification */
> > +       for (i = 0; i < ARRAY_SIZE(lcqctx->wait_queue); i++)
> > +               init_waitqueue_head(&lcqctx->wait_queue[i]);
> > +       init_waitqueue_head(&lcqctx->thread_dispatch_queue);
> > +
> > +       /* create task pool */
> > +       lcqctx->task_cache = kmem_cache_create(
> > +                       CMDQ_DRIVER_DEVICE_NAME "_task",
> > +                       sizeof(struct cmdq_task),
> > +                       __alignof__(struct cmdq_task),
> > +                       SLAB_POISON | SLAB_HWCACHE_ALIGN | SLAB_RED_ZONE,
> > +                       &cmdq_task_ctor);
> > +
> > +       /* initialize task lists */
> > +       INIT_LIST_HEAD(&lcqctx->task_free_list);
> > +       INIT_LIST_HEAD(&lcqctx->task_active_list);
> > +       INIT_LIST_HEAD(&lcqctx->task_wait_list);
> > +       INIT_WORK(&lcqctx->task_consume_wait_queue_item,
> > +                 cmdq_core_consume_waiting_list);
> > +
> > +       /* initialize command buffer pool */
> > +       ret = cmdq_cmd_buf_pool_init(lcqctx);
> > +       if (ret) {
> > +               dev_err(&pdev->dev, "failed to init command buffer pool\n");
> > +               goto fail_cmd_buf_pool;
> > +       }
> > +
> > +       lcqctx->task_auto_release_wq = alloc_ordered_workqueue(
> > +                       "%s", WQ_MEM_RECLAIM | WQ_HIGHPRI, "cmdq_auto_release");
> > +       lcqctx->task_consume_wq = alloc_ordered_workqueue(
> > +                       "%s", WQ_MEM_RECLAIM | WQ_HIGHPRI, "cmdq_task");
> > +
> > +       *cqctx = lcqctx;
> > +       return ret;
> > +
> > +fail_cmd_buf_pool:
> > +       destroy_workqueue(lcqctx->task_auto_release_wq);
> > +       destroy_workqueue(lcqctx->task_consume_wq);
> > +       kmem_cache_destroy(lcqctx->task_cache);
> > +
> > +fail_dev:
> > +       return ret;
> > +}
> > +
> > +static int cmdq_rec_realloc_cmd_buffer(struct cmdq_rec *handle, u32 size)
> > +{
> > +       void *new_buf;
> > +
> > +       new_buf = krealloc(handle->buf_ptr, size, GFP_KERNEL | __GFP_ZERO);
> > +       if (!new_buf)
> > +               return -ENOMEM;
> > +       handle->buf_ptr = new_buf;
> > +       handle->buf_size = size;
> > +       return 0;
> > +}
> > +
> > +static struct cmdq *cmdq_rec_get_valid_ctx(struct cmdq_rec *handle)
> > +{
> 
> Make the caller for all cmdq_rec() APIs pass in struct cmdq *, also, and get rid
> of all of this NULL checking.

We may have multiple cmdq_rec, but only one cmdq.
If we use cmdq as parameter, we still need to pass an ID or index to
identify current used cmdq_rec.
Furthermore, we need to maintain an array or list in cmdq.
So, I think use cmdq_rec is a better way for interface.

I will remove all of this NULL checking.

> > +       if (!handle) {
> > +               WARN_ON(1);
> > +               return NULL;
> > +       }
> > +
> > +       WARN_ON(!handle->cqctx);
> > +       return handle->cqctx;
> > +}
> > +
> > +static int cmdq_rec_stop_running_task(struct cmdq_rec *handle)
> > +{
> > +       int status;
> > +
> > +       status = cmdq_core_release_task(handle->running_task_ptr);
> > +       handle->running_task_ptr = NULL;
> > +       return status;
> > +}
> > +
> > +int cmdq_rec_create(struct platform_device *pdev,
> 
> Use struct device *, not struct platform_device *.

will do

> Although, I think it would be better if this API was:
> 
> struct cmdq_rec *cmdq_rec_create(struct cmdq *cmdq, enum cmdq_scenario scenario)

Please see previous explanation.

> > +                   enum cmdq_scenario scenario,
> > +                   struct cmdq_rec **handle_ptr)
> > +{
> > +       struct cmdq *cqctx;
> > +       struct device *dev = &pdev->dev;
> > +       struct cmdq_rec *handle;
> > +       int ret;
> > +
> > +       cqctx = platform_get_drvdata(pdev);
> > +       if (!cqctx) {
> > +               dev_err(dev, "cmdq context is NULL\n");
> > +               return -EINVAL;
> > +       }
> > +
> > +       if (scenario < 0 || scenario >= CMDQ_MAX_SCENARIO_COUNT) {
> > +               dev_err(dev, "unknown scenario type %d\n", scenario);
> > +               return -EINVAL;
> > +       }
> > +
> > +       handle = kzalloc(sizeof(*handle), GFP_KERNEL);
> > +       if (!handle)
> > +               return -ENOMEM;
> > +
> > +       handle->cqctx = platform_get_drvdata(pdev);
> > +       handle->scenario = scenario;
> > +       handle->engine_flag = cmdq_scenario_get_flag(cqctx, scenario);
> > +       handle->priority = CMDQ_THR_PRIO_NORMAL;
> > +
> > +       ret = cmdq_rec_realloc_cmd_buffer(handle, CMDQ_INITIAL_CMD_BLOCK_SIZE);
> > +       if (ret) {
> > +               kfree(handle);
> > +               return ret;
> > +       }
> > +
> > +       *handle_ptr = handle;
> > +
> > +       return 0;
> > +}
> > +EXPORT_SYMBOL(cmdq_rec_create);
> 
> [snip]
> 
> > +static int cmdq_suspend(struct device *dev)
> > +{
> > +       struct cmdq *cqctx;
> > +       void __iomem *gce_base_va;
> > +       unsigned long flags = 0L;
> > +       u32 exec_threads;
> > +       int ref_count;
> > +       bool kill_tasks = false;
> > +       struct cmdq_task *task;
> > +       struct list_head *p;
> > +       int i;
> > +
> > +       cqctx = dev_get_drvdata(dev);
> > +       gce_base_va = cqctx->base_va;
> > +       exec_threads = readl(gce_base_va + CMDQ_CURR_LOADED_THR_OFFSET);
> > +       ref_count = cqctx->thread_usage;
> > +
> > +       if ((ref_count > 0) || (exec_threads & CMDQ_THR_EXECUTING)) {
> > +               dev_err(dev,
> > +                       "[SUSPEND] other running, kill tasks. threads:0x%08x, ref:%d\n",
> > +                       exec_threads, ref_count);
> > +               kill_tasks = true;
> > +       }
> > +
> > +       /*
> > +        * We need to ensure the system is ready to suspend,
> > +        * so kill all running CMDQ tasks
> > +        * and release HW engines.
> > +        */
> > +       if (kill_tasks) {
> > +               /* remove all active task from thread */
> > +               dev_err(dev, "[SUSPEND] remove all active tasks\n");
> > +               list_for_each(p, &cqctx->task_active_list) {
> > +                       task = list_entry(p, struct cmdq_task, list_entry);
> > +                       if (task->thread != CMDQ_INVALID_THREAD) {
> > +                               spin_lock_irqsave(&cqctx->exec_lock, flags);
> > +                               cmdq_thread_force_remove_task(
> > +                                               task, task->thread);
> > +                               task->task_state = TASK_STATE_KILLED;
> > +                               spin_unlock_irqrestore(
> > +                                               &cqctx->exec_lock, flags);
> > +
> > +                               /*
> > +                                * release all thread and
> > +                                * mark all active tasks as "KILLED"
> > +                                * (so that thread won't release again)
> > +                                */
> > +                               dev_err(dev,
> > +                                       "[SUSPEND] release all threads and HW clocks\n");
> > +                               cmdq_task_remove_thread(task);
> > +                       }
> > +               }
> > +
> > +               /* disable all HW thread */
> > +               dev_err(dev, "[SUSPEND] disable all HW threads\n");
> > +               for (i = 0; i < CMDQ_MAX_THREAD_COUNT; i++)
> > +                       cmdq_thread_disable(cqctx, i);
> > +
> > +               /* reset all cmdq_thread */
> > +               memset(&cqctx->thread[0], 0, sizeof(cqctx->thread));
> 
> This is actually confusing, because this actually sets the tids to 0,
> which is a valid thread.  Do you really want this?:
> 
> memset(cqctx->thread, -1, sizeof(cqctx->thread));
> 
> Which would all the threads to CMDQ_INVALID_THREAD ?

This is cmdq_thread but thread ID.
I will align to use cmdq_thread to prevent confusing.

> > +       }
> > +
> > +       spin_lock_irqsave(&cqctx->thread_lock, flags);
> > +       cqctx->suspended = true;
> > +       spin_unlock_irqrestore(&cqctx->thread_lock, flags);
> > +
> > +       /* ALWAYS allow suspend */
> > +       return 0;
> > +}
> 
> [snip]
> 
> > diff --git a/include/soc/mediatek/cmdq.h b/include/soc/mediatek/cmdq.h
> > new file mode 100644
> > index 0000000..2ec349f
> > --- /dev/null
> > +++ b/include/soc/mediatek/cmdq.h
> > @@ -0,0 +1,225 @@
> > +/*
> > + * Copyright (c) 2015 MediaTek Inc.
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation.
> > + *
> > + * This program is distributed in the hope that it will be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > + * GNU General Public License for more details.
> > + */
> > +
> > +#ifndef __MTK_CMDQ_H__
> > +#define __MTK_CMDQ_H__
> > +
> > +#include <linux/platform_device.h>
> > +#include <linux/types.h>
> > +
> > +enum cmdq_scenario {
> > +       CMDQ_SCENARIO_PRIMARY_DISP,
> > +       CMDQ_SCENARIO_SUB_DISP,
> > +       CMDQ_MAX_SCENARIO_COUNT
> > +};
> > +
> > +enum cmdq_hw_thread_priority {
> > +       CMDQ_THR_PRIO_NORMAL = 0, /* nomral (low) priority */
> > +       CMDQ_THR_PRIO_DISPLAY_CONFIG = 3, /* display config (high) priority */
> > +       CMDQ_THR_PRIO_MAX = 7, /* maximum possible priority */
> > +};
> 
> We only ever use CMDQ_THR_PRIO_DISPLAY_CONFIG.
> I suggest you add support for priorities later in a follow-up patch when/if they
> are useful.

will remove

> > +
> > +/* events for CMDQ and display */
> > +enum cmdq_event {
> > +       /* Display start of frame(SOF) events */
> > +       CMDQ_EVENT_DISP_OVL0_SOF = 11,
> > +       CMDQ_EVENT_DISP_OVL1_SOF = 12,
> > +       CMDQ_EVENT_DISP_RDMA0_SOF = 13,
> > +       CMDQ_EVENT_DISP_RDMA1_SOF = 14,
> > +       CMDQ_EVENT_DISP_RDMA2_SOF = 15,
> > +       CMDQ_EVENT_DISP_WDMA0_SOF = 16,
> > +       CMDQ_EVENT_DISP_WDMA1_SOF = 17,
> > +       /* Display end of frame(EOF) events */
> > +       CMDQ_EVENT_DISP_OVL0_EOF = 39,
> > +       CMDQ_EVENT_DISP_OVL1_EOF = 40,
> > +       CMDQ_EVENT_DISP_RDMA0_EOF = 41,
> > +       CMDQ_EVENT_DISP_RDMA1_EOF = 42,
> > +       CMDQ_EVENT_DISP_RDMA2_EOF = 43,
> > +       CMDQ_EVENT_DISP_WDMA0_EOF = 44,
> > +       CMDQ_EVENT_DISP_WDMA1_EOF = 45,
> > +       /* Mutex end of frame(EOF) events */
> > +       CMDQ_EVENT_MUTEX0_STREAM_EOF = 53,
> > +       CMDQ_EVENT_MUTEX1_STREAM_EOF = 54,
> > +       CMDQ_EVENT_MUTEX2_STREAM_EOF = 55,
> > +       CMDQ_EVENT_MUTEX3_STREAM_EOF = 56,
> > +       CMDQ_EVENT_MUTEX4_STREAM_EOF = 57,
> > +       /* Display underrun events */
> > +       CMDQ_EVENT_DISP_RDMA0_UNDERRUN = 63,
> > +       CMDQ_EVENT_DISP_RDMA1_UNDERRUN = 64,
> > +       CMDQ_EVENT_DISP_RDMA2_UNDERRUN = 65,
> > +       /* Keep this at the end of HW events */
> > +       CMDQ_MAX_HW_EVENT_COUNT = 260,
> > +       /* GPR events */
> > +       CMDQ_SYNC_TOKEN_GPR_SET_0 = 400,
> > +       CMDQ_SYNC_TOKEN_GPR_SET_1 = 401,
> > +       CMDQ_SYNC_TOKEN_GPR_SET_2 = 402,
> > +       CMDQ_SYNC_TOKEN_GPR_SET_3 = 403,
> > +       CMDQ_SYNC_TOKEN_GPR_SET_4 = 404,
> > +       /* This is max event and also can be used as mask. */
> > +       CMDQ_SYNC_TOKEN_MAX = (0x1ff),
> > +       /* Invalid event */
> > +       CMDQ_SYNC_TOKEN_INVALID = (-1),
> > +};
> > +
> > +/* called after isr done or task done */
> > +typedef int (*cmdq_async_flush_cb)(void *data);
> > +
> > +struct cmdq_task;
> > +struct cmdq;
> > +
> > +struct cmdq_rec {
> > +       struct cmdq             *cqctx;
> > +       u64                     engine_flag;
> > +       int                     scenario;
> > +       u32                     block_size; /* command size */
> > +       void                    *buf_ptr;
> > +       u32                     buf_size;
> > +       /* running task after flush */
> > +       struct cmdq_task        *running_task_ptr;
> > +       /*
> > +        * HW thread priority
> > +        * high priority implies prefetch
> > +        */
> > +       enum cmdq_hw_thread_priority    priority;
> > +       bool                    finalized;
> > +       u32                     prefetch_count;
> > +};
> > +
> > +/**
> > + * cmdq_rec_create() - create command queue recorder handle
> 
> For all of these comments, I think you mean "command queue record",
> not recorder.

will rename to "record"

> Thanks,
> -Dan

Thanks,
HS Liao
Daniel Kurtz Jan. 29, 2016, 8:42 a.m. UTC | #3
On Fri, Jan 29, 2016 at 3:39 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
> Hi Dan,
>
> Many thanks for your comments and time.
> I reply my plan inline.
>
>
> On Thu, 2016-01-28 at 12:49 +0800, Daniel Kurtz wrote:
>> Hi HS,
>>
>> Sorry for the delay.  It is hard to find time to review a >3700 line
>> driver :-o in detail....
>>
>> Some review comments inline, although I still do not completely
>> understand how all that this driver does and how it works.
>> I'll try to find time to go through this driver in detail again next
>> time you post it for review.
>>
>> On Tue, Jan 19, 2016 at 9:14 PM,  <hs.liao@mediatek.com> wrote:
>> > From: HS Liao <hs.liao@mediatek.com>
>> >
>> > This patch is first version of Mediatek Command Queue(CMDQ) driver. The
>> > CMDQ is used to help read/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: HS Liao <hs.liao@mediatek.com>
>>
>> [snip]
>>
>> > diff --git a/drivers/soc/mediatek/mtk-cmdq.c b/drivers/soc/mediatek/mtk-cmdq.c
>> > new file mode 100644
>> > index 0000000..7570f00
>> > --- /dev/null
>> > +++ b/drivers/soc/mediatek/mtk-cmdq.c
>>
>> [snip]
>>
>> > +/*
>> > + * Maximum prefetch buffer size.
>> > + * Unit is instructions.
>> > + */
>> > +#define CMDQ_MAX_PREFETCH_INSTUCTION   240
>>
>> INSTRUCTION
>
> will fix
>
>> [snip]
>>
>> > +
>> > +/* get lsb for subsys encoding in arg_a (range: 0 - 31) */
>> > +
>> > +enum cmdq_eng {
>> > +       CMDQ_ENG_DISP_UFOE = 0,
>> > +       CMDQ_ENG_DISP_AAL,
>> > +       CMDQ_ENG_DISP_COLOR0,
>> > +       CMDQ_ENG_DISP_COLOR1,
>> > +       CMDQ_ENG_DISP_RDMA0,
>> > +       CMDQ_ENG_DISP_RDMA1,
>> > +       CMDQ_ENG_DISP_RDMA2,
>> > +       CMDQ_ENG_DISP_WDMA0,
>> > +       CMDQ_ENG_DISP_WDMA1,
>> > +       CMDQ_ENG_DISP_OVL0,
>> > +       CMDQ_ENG_DISP_OVL1,
>> > +       CMDQ_ENG_DISP_GAMMA,
>> > +       CMDQ_ENG_DISP_DSI0_CMD,
>> > +       CMDQ_ENG_DISP_DSI1_CMD,
>>
>> Why do these last two have "_CMD" at the end?
>
> will remove
>
>> > +       CMDQ_MAX_ENGINE_COUNT   /* ALWAYS keep at the end */
>> > +};
>> > +
>> > +struct cmdq_command {
>> > +       struct cmdq             *cqctx;
>> > +       u32                     scenario;
>> > +       /* task priority (NOT HW thread priority) */
>> > +       u32                     priority;
>> > +       /* bit flag of used engines */
>> > +       u64                     engine_flag;
>> > +       /*
>> > +        * pointer of instruction buffer
>> > +        * This must point to an 64-bit aligned u32 array
>> > +        */
>> > +       u32                     *va_base;
>>
>> All of your "va" and "va_base" should be void *, not u32 *.
>
> will do
>
>> > +       /* size of instruction buffer, in bytes. */
>> > +       u32                     block_size;
>>
>> Better to use size_t for "size in bytes".
>
> will fix
>
>> > +};
>> > +
>> > +enum cmdq_code {
>> > +       /* These are actual HW op code. */
>> > +       CMDQ_CODE_MOVE = 0x02,
>> > +       CMDQ_CODE_WRITE = 0x04,
>> > +       CMDQ_CODE_JUMP = 0x10,
>> > +       CMDQ_CODE_WFE = 0x20,   /* wait for event (and clear) */
>> > +       CMDQ_CODE_CLEAR_EVENT = 0x21,   /* clear event */
>> > +       CMDQ_CODE_EOC = 0x40,   /* end of command */
>> > +};
>> > +
>> > +enum cmdq_task_state {
>> > +       TASK_STATE_IDLE,        /* free task */
>> > +       TASK_STATE_BUSY,        /* task running on a thread */
>> > +       TASK_STATE_KILLED,      /* task process being killed */
>> > +       TASK_STATE_ERROR,       /* task execution error */
>> > +       TASK_STATE_DONE,        /* task finished */
>> > +       TASK_STATE_WAITING,     /* allocated but waiting for available thread */
>> > +};
>> > +
>> > +struct cmdq_cmd_buf {
>> > +       atomic_t                used;
>> > +       void                    *va;
>> > +       dma_addr_t              pa;
>> > +};
>> > +
>> > +struct cmdq_task_cb {
>> > +       /* called by isr */
>> > +       cmdq_async_flush_cb     isr_cb;
>> > +       void                    *isr_data;
>> > +       /* called by releasing task */
>> > +       cmdq_async_flush_cb     done_cb;
>> > +       void                    *done_data;
>> > +};
>> > +
>> > +struct cmdq_task {
>> > +       struct cmdq             *cqctx;
>> > +       struct list_head        list_entry;
>> > +
>> > +       /* state for task life cycle */
>> > +       enum cmdq_task_state    task_state;
>> > +       /* virtual address of command buffer */
>> > +       u32                     *va_base;
>> > +       /* physical address of command buffer */
>> > +       dma_addr_t              mva_base;
>> > +       /* size of allocated command buffer */
>> > +       u32                     buf_size;
>>
>> size_t
>
> will fix
>
>> > +       /* It points to a cmdq_cmd_buf if this task use command buffer pool. */
>> > +       struct cmdq_cmd_buf     *cmd_buf;
>> > +
>> > +       int                     scenario;
>> > +       int                     priority;
>> > +       u64                     engine_flag;
>> > +       u32                     command_size;
>> > +       u32                     num_cmd;
>> > +       int                     reorder;
>> > +       /* HW thread ID; CMDQ_INVALID_THREAD if not running */
>> > +       int                     thread;
>>
>> I think this driver will be a lot more clear if you do this:
>>
>> struct cmdq_thread *thread;
>>
>> And then use "NULL" for "invalid thread" instead of CMDQ_INVALID_THREAD.
>
> I will try to use cmdq_thread instead of thread id if possible.
> If CMDQ_INVALID_THREAD id is not necessary any more, I will remove it.
>
>> > +       /* flag of IRQ received */
>> > +       int                     irq_flag;
>> > +       /* callback functions */
>> > +       struct cmdq_task_cb     cb;
>> > +       /* work item when auto release is used */
>> > +       struct work_struct      auto_release_work;
>> > +
>> > +       unsigned long long      submit; /* submit time */
>> > +
>> > +       pid_t                   caller_pid;
>> > +       char                    caller_name[TASK_COMM_LEN];
>> > +};
>> > +
>> > +struct cmdq_thread {
>> > +       u32                     task_count;
>> > +       u32                     wait_cookie;
>> > +       u32                     next_cookie;
>> > +       struct cmdq_task        *cur_task[CMDQ_MAX_TASK_IN_THREAD];
>> > +};
>> > +
>> > +struct cmdq {
>> > +       struct device           *dev;
>> > +       struct notifier_block   pm_notifier;
>> > +
>> > +       void __iomem            *base_va;
>> > +       unsigned long           base_pa;
>>
>> I think we can remove base_pa (it is only used in a debug print), and just call
>> "base_va" as "base".
>
> will do
>
>> > +       u32                     irq;
>> > +
>> > +       /*
>> > +        * task information
>> > +        * task_cache: struct cmdq_task object cache
>> > +        * task_free_list: unused free tasks
>> > +        * task_active_list: active tasks
>> > +        * task_consume_wait_queue_item: task consumption work item
>> > +        * task_auto_release_wq: auto-release workqueue
>> > +        * task_consume_wq: task consumption workqueue (for queued tasks)
>> > +        */
>> > +       struct kmem_cache       *task_cache;
>> > +       struct list_head        task_free_list;
>> > +       struct list_head        task_active_list;
>> > +       struct list_head        task_wait_list;
>> > +       struct work_struct      task_consume_wait_queue_item;
>> > +       struct workqueue_struct *task_auto_release_wq;
>> > +       struct workqueue_struct *task_consume_wq;
>> > +       u16                     task_count[CMDQ_MAX_THREAD_COUNT];
>>
>> AFAICT, this task_count is not used?
>
> will remove
>
>> > +
>> > +       struct cmdq_thread      thread[CMDQ_MAX_THREAD_COUNT];
>> > +
>> > +       /* mutex, spinlock, flag */
>> > +       struct mutex            task_mutex;     /* for task list */
>> > +       struct mutex            clock_mutex;    /* for clock operation */
>> > +       spinlock_t              thread_lock;    /* for cmdq hardware thread */
>> > +       int                     thread_usage;
>> > +       spinlock_t              exec_lock;      /* for exec task */
>> > +
>> > +       /* suspend */
>> > +       bool                    suspended;
>> > +
>> > +       /* command buffer pool */
>> > +       struct cmdq_cmd_buf     cmd_buf_pool[CMDQ_CMD_BUF_POOL_BUF_NUM];
>> > +
>> > +       /*
>> > +        * notification
>> > +        * wait_queue: for task done
>> > +        * thread_dispatch_queue: for thread acquiring
>> > +        */
>> > +       wait_queue_head_t       wait_queue[CMDQ_MAX_THREAD_COUNT];
>> > +       wait_queue_head_t       thread_dispatch_queue;
>> > +
>> > +       /* ccf */
>> > +       struct clk              *clock;
>> > +};
>> > +
>> > +struct cmdq_event_name {
>> > +       enum cmdq_event event;
>> > +       char            *name;
>>
>> const char *
>
> will do
>
>> > +};
>> > +
>> > +struct cmdq_subsys {
>> > +       u32     base_addr;
>> > +       int     id;
>> > +       char    *grp_name;
>>
>> const char *
>
> will do
>
>> > +};
>> > +
>> > +static const struct cmdq_event_name g_event_name[] = {
>> > +       /* Display start of frame(SOF) events */
>> > +       {CMDQ_EVENT_DISP_OVL0_SOF, "CMDQ_EVENT_DISP_OVL0_SOF",},
>>
>> You can drop the "," inside "}".
>
> will do
>
>> > +       {CMDQ_EVENT_DISP_OVL1_SOF, "CMDQ_EVENT_DISP_OVL1_SOF",},
>> > +       {CMDQ_EVENT_DISP_RDMA0_SOF, "CMDQ_EVENT_DISP_RDMA0_SOF",},
>> > +       {CMDQ_EVENT_DISP_RDMA1_SOF, "CMDQ_EVENT_DISP_RDMA1_SOF",},
>> > +       {CMDQ_EVENT_DISP_RDMA2_SOF, "CMDQ_EVENT_DISP_RDMA2_SOF",},
>> > +       {CMDQ_EVENT_DISP_WDMA0_SOF, "CMDQ_EVENT_DISP_WDMA0_SOF",},
>> > +       {CMDQ_EVENT_DISP_WDMA1_SOF, "CMDQ_EVENT_DISP_WDMA1_SOF",},
>> > +       /* Display end of frame(EOF) events */
>> > +       {CMDQ_EVENT_DISP_OVL0_EOF, "CMDQ_EVENT_DISP_OVL0_EOF",},
>> > +       {CMDQ_EVENT_DISP_OVL1_EOF, "CMDQ_EVENT_DISP_OVL1_EOF",},
>> > +       {CMDQ_EVENT_DISP_RDMA0_EOF, "CMDQ_EVENT_DISP_RDMA0_EOF",},
>> > +       {CMDQ_EVENT_DISP_RDMA1_EOF, "CMDQ_EVENT_DISP_RDMA1_EOF",},
>> > +       {CMDQ_EVENT_DISP_RDMA2_EOF, "CMDQ_EVENT_DISP_RDMA2_EOF",},
>> > +       {CMDQ_EVENT_DISP_WDMA0_EOF, "CMDQ_EVENT_DISP_WDMA0_EOF",},
>> > +       {CMDQ_EVENT_DISP_WDMA1_EOF, "CMDQ_EVENT_DISP_WDMA1_EOF",},
>> > +       /* Mutex end of frame(EOF) events */
>> > +       {CMDQ_EVENT_MUTEX0_STREAM_EOF, "CMDQ_EVENT_MUTEX0_STREAM_EOF",},
>> > +       {CMDQ_EVENT_MUTEX1_STREAM_EOF, "CMDQ_EVENT_MUTEX1_STREAM_EOF",},
>> > +       {CMDQ_EVENT_MUTEX2_STREAM_EOF, "CMDQ_EVENT_MUTEX2_STREAM_EOF",},
>> > +       {CMDQ_EVENT_MUTEX3_STREAM_EOF, "CMDQ_EVENT_MUTEX3_STREAM_EOF",},
>> > +       {CMDQ_EVENT_MUTEX4_STREAM_EOF, "CMDQ_EVENT_MUTEX4_STREAM_EOF",},
>> > +       /* Display underrun events */
>> > +       {CMDQ_EVENT_DISP_RDMA0_UNDERRUN, "CMDQ_EVENT_DISP_RDMA0_UNDERRUN",},
>> > +       {CMDQ_EVENT_DISP_RDMA1_UNDERRUN, "CMDQ_EVENT_DISP_RDMA1_UNDERRUN",},
>> > +       {CMDQ_EVENT_DISP_RDMA2_UNDERRUN, "CMDQ_EVENT_DISP_RDMA2_UNDERRUN",},
>> > +       /* Keep this at the end of HW events */
>> > +       {CMDQ_MAX_HW_EVENT_COUNT, "CMDQ_MAX_HW_EVENT_COUNT",},
>> > +       /* GPR events */
>>
>> What are "GPR" events?
>
> They are events of General Purpose Register of GCE.
> I will remove them if driver doesn't need to take care of them.
>
>> > +       {CMDQ_SYNC_TOKEN_GPR_SET_0, "CMDQ_SYNC_TOKEN_GPR_SET_0",},
>> > +       {CMDQ_SYNC_TOKEN_GPR_SET_1, "CMDQ_SYNC_TOKEN_GPR_SET_1",},
>> > +       {CMDQ_SYNC_TOKEN_GPR_SET_2, "CMDQ_SYNC_TOKEN_GPR_SET_2",},
>> > +       {CMDQ_SYNC_TOKEN_GPR_SET_3, "CMDQ_SYNC_TOKEN_GPR_SET_3",},
>> > +       {CMDQ_SYNC_TOKEN_GPR_SET_4, "CMDQ_SYNC_TOKEN_GPR_SET_4",},
>> > +       /* This is max event and also can be used as mask. */
>> > +       {CMDQ_SYNC_TOKEN_MAX, "CMDQ_SYNC_TOKEN_MAX",},
>> > +       /* Invalid event */
>> > +       {CMDQ_SYNC_TOKEN_INVALID, "CMDQ_SYNC_TOKEN_INVALID",},
>> > +};
>> > +
>> > +static const struct cmdq_subsys g_subsys[] = {
>> > +       {0x1400, 1, "MMSYS"},
>> > +       {0x1401, 2, "DISP"},
>> > +       {0x1402, 3, "DISP"},
>>
>> This isn't going to scale.  These addresses could be different on
>> different chips.
>> Instead of a static table like this, we probably need specify to the
>> connection between gce and other devices via devicetree phandles, and
>> then use the phandles to lookup the corresponding device address
>> range.
>
> I will define them in device tree.
> E.g.
> cmdq {
>   reg_domain = 0x14000000, 0x14010000, 0x14020000
> }

The devicetree should only model hardware relationships, not software
considerations.

Is the hardware constraint here for using gce with various other
hardware blocks?  I think we already model this by only providing a
gce phandle in the device tree nodes for those devices that can use
gce.

Looking at the driver closer, as far as I can tell, the whole subsys
concept is a purely software abstraction, and only used to debug the
CMDQ_CODE_WRITE command.  In fact, AFAICT, everything would work fine
if we just completely removed the 'subsys' concept, and just passed
through the raw address provided by the driver.

So, I recommend just removing 'subsys' completely from the driver -
from this array, and in the masks.

Instead, if there is an error on the write command, just print the
address that fails.  There are other ways to deduce the subsystem from
a physical address.

Thanks,

-Dan
hs.liao@mediatek.com Jan. 29, 2016, 12:24 p.m. UTC | #4
On Fri, 2016-01-29 at 16:42 +0800, Daniel Kurtz wrote:
> On Fri, Jan 29, 2016 at 3:39 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
> > Hi Dan,
> >
> > Many thanks for your comments and time.
> > I reply my plan inline.
> >
> >
> > On Thu, 2016-01-28 at 12:49 +0800, Daniel Kurtz wrote:
> >> Hi HS,
> >>
> >> Sorry for the delay.  It is hard to find time to review a >3700 line
> >> driver :-o in detail....
> >>
> >> Some review comments inline, although I still do not completely
> >> understand how all that this driver does and how it works.
> >> I'll try to find time to go through this driver in detail again next
> >> time you post it for review.
> >>
> >> On Tue, Jan 19, 2016 at 9:14 PM,  <hs.liao@mediatek.com> wrote:
> >> > From: HS Liao <hs.liao@mediatek.com>
> >> >
> >> > This patch is first version of Mediatek Command Queue(CMDQ) driver. The
> >> > CMDQ is used to help read/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: HS Liao <hs.liao@mediatek.com>
> >>
> >> [snip]
> >>
> >> > diff --git a/drivers/soc/mediatek/mtk-cmdq.c b/drivers/soc/mediatek/mtk-cmdq.c
> >> > new file mode 100644
> >> > index 0000000..7570f00
> >> > --- /dev/null
> >> > +++ b/drivers/soc/mediatek/mtk-cmdq.c

[snip]

> >> > +static const struct cmdq_subsys g_subsys[] = {
> >> > +       {0x1400, 1, "MMSYS"},
> >> > +       {0x1401, 2, "DISP"},
> >> > +       {0x1402, 3, "DISP"},
> >>
> >> This isn't going to scale.  These addresses could be different on
> >> different chips.
> >> Instead of a static table like this, we probably need specify to the
> >> connection between gce and other devices via devicetree phandles, and
> >> then use the phandles to lookup the corresponding device address
> >> range.
> >
> > I will define them in device tree.
> > E.g.
> > cmdq {
> >   reg_domain = 0x14000000, 0x14010000, 0x14020000
> > }
> 
> The devicetree should only model hardware relationships, not software
> considerations.
> 
> Is the hardware constraint here for using gce with various other
> hardware blocks?  I think we already model this by only providing a
> gce phandle in the device tree nodes for those devices that can use
> gce.
> 
> Looking at the driver closer, as far as I can tell, the whole subsys
> concept is a purely software abstraction, and only used to debug the
> CMDQ_CODE_WRITE command.  In fact, AFAICT, everything would work fine
> if we just completely removed the 'subsys' concept, and just passed
> through the raw address provided by the driver.
> 
> So, I recommend just removing 'subsys' completely from the driver -
> from this array, and in the masks.
> 
> Instead, if there is an error on the write command, just print the
> address that fails.  There are other ways to deduce the subsystem from
> a physical address.
> 
> Thanks,
> 
> -Dan

Hi Dan,

Subsys is not just for debug.
Its main purpose is to transfer CPU address to GCE address.
Let me explain it by "write" op,
I list a code segment from cmdq_rec_append_command().

	case CMDQ_CODE_WRITE:
		subsys = cmdq_subsys_from_phys_addr(cqctx, arg_a);
		if (subsys < 0) {
			dev_err(dev,
				"unsupported memory base address 0x%08x\n",
				arg_a);
			return -EFAULT;
		}

		*cmd_ptr++ = arg_b;
		*cmd_ptr++ = (CMDQ_CODE_WRITE << CMDQ_OP_CODE_SHIFT) |
			     (arg_a & CMDQ_ARG_A_WRITE_MASK) |
			     ((subsys & CMDQ_SUBSYS_MASK) << CMDQ_SUBSYS_SHIFT);
		break;

Subsys is mapped from physical address via cmdq_subsys_from_phys_addr(),
and then it becomes part of GCE command via ((subsys & CMDQ_SUBSYS_MASK)
<< CMDQ_SUBSYS_SHIFT) .
Only low bits of physical address are the same as GCE address.
We can get it by (arg_a & CMDQ_ARG_A_WRITE_MASK).
MASK is used to define how many bits are valid for this op.
So, GCE address = subsys + valid low bits.

That's why we need to know the mapping between the range of physical
address and subsys.
Please guide us a better way to code such requirement.
Thanks for your help.

Thanks,
HS Liao
Daniel Kurtz Jan. 29, 2016, 1:15 p.m. UTC | #5
On Fri, Jan 29, 2016 at 8:24 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
> On Fri, 2016-01-29 at 16:42 +0800, Daniel Kurtz wrote:
>> On Fri, Jan 29, 2016 at 3:39 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
>> > Hi Dan,
>> >
>> > Many thanks for your comments and time.
>> > I reply my plan inline.
>> >
>> >
>> > On Thu, 2016-01-28 at 12:49 +0800, Daniel Kurtz wrote:
>> >> Hi HS,
>> >>
>> >> Sorry for the delay.  It is hard to find time to review a >3700 line
>> >> driver :-o in detail....
>> >>
>> >> Some review comments inline, although I still do not completely
>> >> understand how all that this driver does and how it works.
>> >> I'll try to find time to go through this driver in detail again next
>> >> time you post it for review.
>> >>
>> >> On Tue, Jan 19, 2016 at 9:14 PM,  <hs.liao@mediatek.com> wrote:
>> >> > From: HS Liao <hs.liao@mediatek.com>
>> >> >
>> >> > This patch is first version of Mediatek Command Queue(CMDQ) driver. The
>> >> > CMDQ is used to help read/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: HS Liao <hs.liao@mediatek.com>
>> >>
>> >> [snip]
>> >>
>> >> > diff --git a/drivers/soc/mediatek/mtk-cmdq.c b/drivers/soc/mediatek/mtk-cmdq.c
>> >> > new file mode 100644
>> >> > index 0000000..7570f00
>> >> > --- /dev/null
>> >> > +++ b/drivers/soc/mediatek/mtk-cmdq.c
>
> [snip]
>
>> >> > +static const struct cmdq_subsys g_subsys[] = {
>> >> > +       {0x1400, 1, "MMSYS"},
>> >> > +       {0x1401, 2, "DISP"},
>> >> > +       {0x1402, 3, "DISP"},
>> >>
>> >> This isn't going to scale.  These addresses could be different on
>> >> different chips.
>> >> Instead of a static table like this, we probably need specify to the
>> >> connection between gce and other devices via devicetree phandles, and
>> >> then use the phandles to lookup the corresponding device address
>> >> range.
>> >
>> > I will define them in device tree.
>> > E.g.
>> > cmdq {
>> >   reg_domain = 0x14000000, 0x14010000, 0x14020000
>> > }
>>
>> The devicetree should only model hardware relationships, not software
>> considerations.
>>
>> Is the hardware constraint here for using gce with various other
>> hardware blocks?  I think we already model this by only providing a
>> gce phandle in the device tree nodes for those devices that can use
>> gce.
>>
>> Looking at the driver closer, as far as I can tell, the whole subsys
>> concept is a purely software abstraction, and only used to debug the
>> CMDQ_CODE_WRITE command.  In fact, AFAICT, everything would work fine
>> if we just completely removed the 'subsys' concept, and just passed
>> through the raw address provided by the driver.
>>
>> So, I recommend just removing 'subsys' completely from the driver -
>> from this array, and in the masks.
>>
>> Instead, if there is an error on the write command, just print the
>> address that fails.  There are other ways to deduce the subsystem from
>> a physical address.
>>
>> Thanks,
>>
>> -Dan
>
> Hi Dan,
>
> Subsys is not just for debug.
> Its main purpose is to transfer CPU address to GCE address.
> Let me explain it by "write" op,
> I list a code segment from cmdq_rec_append_command().
>
>         case CMDQ_CODE_WRITE:
>                 subsys = cmdq_subsys_from_phys_addr(cqctx, arg_a);
>                 if (subsys < 0) {
>                         dev_err(dev,
>                                 "unsupported memory base address 0x%08x\n",
>                                 arg_a);
>                         return -EFAULT;
>                 }
>
>                 *cmd_ptr++ = arg_b;
>                 *cmd_ptr++ = (CMDQ_CODE_WRITE << CMDQ_OP_CODE_SHIFT) |
>                              (arg_a & CMDQ_ARG_A_WRITE_MASK) |
>                              ((subsys & CMDQ_SUBSYS_MASK) << CMDQ_SUBSYS_SHIFT);
>                 break;
>
> Subsys is mapped from physical address via cmdq_subsys_from_phys_addr(),
> and then it becomes part of GCE command via ((subsys & CMDQ_SUBSYS_MASK)
> << CMDQ_SUBSYS_SHIFT) .
> Only low bits of physical address are the same as GCE address.
> We can get it by (arg_a & CMDQ_ARG_A_WRITE_MASK).
> MASK is used to define how many bits are valid for this op.
> So, GCE address = subsys + valid low bits.

How are these upper bits of the "GCE address" defined?
In other words, for a given SoC, how is the mapping between physical
io addresses to GCE addresses defined?
Is this mapping fixed by hardware?
Does it vary for different SoCs?

-Dan

> That's why we need to know the mapping between the range of physical
> address and subsys.
> Please guide us a better way to code such requirement.
> Thanks for your help.
>
> Thanks,
> HS Liao
>
hs.liao@mediatek.com Feb. 1, 2016, 2:04 a.m. UTC | #6
On Fri, 2016-01-29 at 21:15 +0800, Daniel Kurtz wrote:
> On Fri, Jan 29, 2016 at 8:24 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
> > On Fri, 2016-01-29 at 16:42 +0800, Daniel Kurtz wrote:
> >> On Fri, Jan 29, 2016 at 3:39 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
> >> > Hi Dan,
> >> >
> >> > Many thanks for your comments and time.
> >> > I reply my plan inline.
> >> >
> >> >
> >> > On Thu, 2016-01-28 at 12:49 +0800, Daniel Kurtz wrote:
> >> >> Hi HS,
> >> >>
> >> >> Sorry for the delay.  It is hard to find time to review a >3700 line
> >> >> driver :-o in detail....
> >> >>
> >> >> Some review comments inline, although I still do not completely
> >> >> understand how all that this driver does and how it works.
> >> >> I'll try to find time to go through this driver in detail again next
> >> >> time you post it for review.
> >> >>
> >> >> On Tue, Jan 19, 2016 at 9:14 PM,  <hs.liao@mediatek.com> wrote:
> >> >> > From: HS Liao <hs.liao@mediatek.com>
> >> >> >
> >> >> > This patch is first version of Mediatek Command Queue(CMDQ) driver. The
> >> >> > CMDQ is used to help read/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: HS Liao <hs.liao@mediatek.com>
> >> >>
> >> >> [snip]
> >> >>
> >> >> > diff --git a/drivers/soc/mediatek/mtk-cmdq.c b/drivers/soc/mediatek/mtk-cmdq.c
> >> >> > new file mode 100644
> >> >> > index 0000000..7570f00
> >> >> > --- /dev/null
> >> >> > +++ b/drivers/soc/mediatek/mtk-cmdq.c
> >
> > [snip]
> >
> >> >> > +static const struct cmdq_subsys g_subsys[] = {
> >> >> > +       {0x1400, 1, "MMSYS"},
> >> >> > +       {0x1401, 2, "DISP"},
> >> >> > +       {0x1402, 3, "DISP"},
> >> >>
> >> >> This isn't going to scale.  These addresses could be different on
> >> >> different chips.
> >> >> Instead of a static table like this, we probably need specify to the
> >> >> connection between gce and other devices via devicetree phandles, and
> >> >> then use the phandles to lookup the corresponding device address
> >> >> range.
> >> >
> >> > I will define them in device tree.
> >> > E.g.
> >> > cmdq {
> >> >   reg_domain = 0x14000000, 0x14010000, 0x14020000
> >> > }
> >>
> >> The devicetree should only model hardware relationships, not software
> >> considerations.
> >>
> >> Is the hardware constraint here for using gce with various other
> >> hardware blocks?  I think we already model this by only providing a
> >> gce phandle in the device tree nodes for those devices that can use
> >> gce.
> >>
> >> Looking at the driver closer, as far as I can tell, the whole subsys
> >> concept is a purely software abstraction, and only used to debug the
> >> CMDQ_CODE_WRITE command.  In fact, AFAICT, everything would work fine
> >> if we just completely removed the 'subsys' concept, and just passed
> >> through the raw address provided by the driver.
> >>
> >> So, I recommend just removing 'subsys' completely from the driver -
> >> from this array, and in the masks.
> >>
> >> Instead, if there is an error on the write command, just print the
> >> address that fails.  There are other ways to deduce the subsystem from
> >> a physical address.
> >>
> >> Thanks,
> >>
> >> -Dan
> >
> > Hi Dan,
> >
> > Subsys is not just for debug.
> > Its main purpose is to transfer CPU address to GCE address.
> > Let me explain it by "write" op,
> > I list a code segment from cmdq_rec_append_command().
> >
> >         case CMDQ_CODE_WRITE:
> >                 subsys = cmdq_subsys_from_phys_addr(cqctx, arg_a);
> >                 if (subsys < 0) {
> >                         dev_err(dev,
> >                                 "unsupported memory base address 0x%08x\n",
> >                                 arg_a);
> >                         return -EFAULT;
> >                 }
> >
> >                 *cmd_ptr++ = arg_b;
> >                 *cmd_ptr++ = (CMDQ_CODE_WRITE << CMDQ_OP_CODE_SHIFT) |
> >                              (arg_a & CMDQ_ARG_A_WRITE_MASK) |
> >                              ((subsys & CMDQ_SUBSYS_MASK) << CMDQ_SUBSYS_SHIFT);
> >                 break;
> >
> > Subsys is mapped from physical address via cmdq_subsys_from_phys_addr(),
> > and then it becomes part of GCE command via ((subsys & CMDQ_SUBSYS_MASK)
> > << CMDQ_SUBSYS_SHIFT) .
> > Only low bits of physical address are the same as GCE address.
> > We can get it by (arg_a & CMDQ_ARG_A_WRITE_MASK).
> > MASK is used to define how many bits are valid for this op.
> > So, GCE address = subsys + valid low bits.
> 
> How are these upper bits of the "GCE address" defined?
> In other words, for a given SoC, how is the mapping between physical
> io addresses to GCE addresses defined?
> Is this mapping fixed by hardware?

Yes, this mapping is fixed by hardware.

> Does it vary for different SoCs?

Yes, it varies for different SoCs.

> 
> -Dan
> 
> > That's why we need to know the mapping between the range of physical
> > address and subsys.
> > Please guide us a better way to code such requirement.
> > Thanks for your help.
> >
> > Thanks,
> > HS Liao
> >

Thanks,
HS Liao
Daniel Kurtz Feb. 1, 2016, 4:15 a.m. UTC | #7
On Mon, Feb 1, 2016 at 10:04 AM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
>
> On Fri, 2016-01-29 at 21:15 +0800, Daniel Kurtz wrote:
> > On Fri, Jan 29, 2016 at 8:24 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
> > > On Fri, 2016-01-29 at 16:42 +0800, Daniel Kurtz wrote:
> > >> On Fri, Jan 29, 2016 at 3:39 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
> > >> > Hi Dan,
> > >> >
> > >> > Many thanks for your comments and time.
> > >> > I reply my plan inline.
> > >> >
> > >> >
> > >> > On Thu, 2016-01-28 at 12:49 +0800, Daniel Kurtz wrote:
> > >> >> Hi HS,
> > >> >>
> > >> >> Sorry for the delay.  It is hard to find time to review a >3700 line
> > >> >> driver :-o in detail....
> > >> >>
> > >> >> Some review comments inline, although I still do not completely
> > >> >> understand how all that this driver does and how it works.
> > >> >> I'll try to find time to go through this driver in detail again next
> > >> >> time you post it for review.
> > >> >>
> > >> >> On Tue, Jan 19, 2016 at 9:14 PM,  <hs.liao@mediatek.com> wrote:
> > >> >> > From: HS Liao <hs.liao@mediatek.com>
> > >> >> >
> > >> >> > This patch is first version of Mediatek Command Queue(CMDQ) driver. The
> > >> >> > CMDQ is used to help read/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: HS Liao <hs.liao@mediatek.com>
> > >> >>
> > >> >> [snip]
> > >> >>
> > >> >> > diff --git a/drivers/soc/mediatek/mtk-cmdq.c b/drivers/soc/mediatek/mtk-cmdq.c
> > >> >> > new file mode 100644
> > >> >> > index 0000000..7570f00
> > >> >> > --- /dev/null
> > >> >> > +++ b/drivers/soc/mediatek/mtk-cmdq.c
> > >
> > > [snip]
> > >
> > >> >> > +static const struct cmdq_subsys g_subsys[] = {
> > >> >> > +       {0x1400, 1, "MMSYS"},
> > >> >> > +       {0x1401, 2, "DISP"},
> > >> >> > +       {0x1402, 3, "DISP"},
> > >> >>
> > >> >> This isn't going to scale.  These addresses could be different on
> > >> >> different chips.
> > >> >> Instead of a static table like this, we probably need specify to the
> > >> >> connection between gce and other devices via devicetree phandles, and
> > >> >> then use the phandles to lookup the corresponding device address
> > >> >> range.
> > >> >
> > >> > I will define them in device tree.
> > >> > E.g.
> > >> > cmdq {
> > >> >   reg_domain = 0x14000000, 0x14010000, 0x14020000
> > >> > }
> > >>
> > >> The devicetree should only model hardware relationships, not software
> > >> considerations.
> > >>
> > >> Is the hardware constraint here for using gce with various other
> > >> hardware blocks?  I think we already model this by only providing a
> > >> gce phandle in the device tree nodes for those devices that can use
> > >> gce.
> > >>
> > >> Looking at the driver closer, as far as I can tell, the whole subsys
> > >> concept is a purely software abstraction, and only used to debug the
> > >> CMDQ_CODE_WRITE command.  In fact, AFAICT, everything would work fine
> > >> if we just completely removed the 'subsys' concept, and just passed
> > >> through the raw address provided by the driver.
> > >>
> > >> So, I recommend just removing 'subsys' completely from the driver -
> > >> from this array, and in the masks.
> > >>
> > >> Instead, if there is an error on the write command, just print the
> > >> address that fails.  There are other ways to deduce the subsystem from
> > >> a physical address.
> > >>
> > >> Thanks,
> > >>
> > >> -Dan
> > >
> > > Hi Dan,
> > >
> > > Subsys is not just for debug.
> > > Its main purpose is to transfer CPU address to GCE address.
> > > Let me explain it by "write" op,
> > > I list a code segment from cmdq_rec_append_command().
> > >
> > >         case CMDQ_CODE_WRITE:
> > >                 subsys = cmdq_subsys_from_phys_addr(cqctx, arg_a);
> > >                 if (subsys < 0) {
> > >                         dev_err(dev,
> > >                                 "unsupported memory base address 0x%08x\n",
> > >                                 arg_a);
> > >                         return -EFAULT;
> > >                 }
> > >
> > >                 *cmd_ptr++ = arg_b;
> > >                 *cmd_ptr++ = (CMDQ_CODE_WRITE << CMDQ_OP_CODE_SHIFT) |
> > >                              (arg_a & CMDQ_ARG_A_WRITE_MASK) |
> > >                              ((subsys & CMDQ_SUBSYS_MASK) << CMDQ_SUBSYS_SHIFT);
> > >                 break;
> > >
> > > Subsys is mapped from physical address via cmdq_subsys_from_phys_addr(),
> > > and then it becomes part of GCE command via ((subsys & CMDQ_SUBSYS_MASK)
> > > << CMDQ_SUBSYS_SHIFT) .
> > > Only low bits of physical address are the same as GCE address.
> > > We can get it by (arg_a & CMDQ_ARG_A_WRITE_MASK).
> > > MASK is used to define how many bits are valid for this op.
> > > So, GCE address = subsys + valid low bits.
> >
> > How are these upper bits of the "GCE address" defined?
> > In other words, for a given SoC, how is the mapping between physical
> > io addresses to GCE addresses defined?
> > Is this mapping fixed by hardware?

Please answer the detailed technical questions:

How are these upper bits of the "GCE address" defined?
In other words, for a given SoC, how is the mapping between physical
io addresses to GCE addresses defined?

(a) Does the GCE remap a continuous device IO address range?

AFAICT, the  defines an MT8173 specific mapping of:

For example, the g_subsys table above seems to imply that the MT8173
gce maps all of:
  0x1400ffff:0x141fffff => 0x010000:0x1fffff

(b) Or, are the upper 5 bits of the "gce address" significant, and via
hardware it can map a disjoint groups of device addresses into the
continuous GCE address space, and really there are 0x1f distinct 64k
mappings:

mmsys (1) : 0x14000000:0x1400ffff => 0x010000:0x01ffff
disp  (2) : 0x14010000:0x1401ffff => 0x020000:0x02ffff
disp  (3) : 0x14020000:0x1402ffff => 0x030000:0x03ffff
...
???? (1f) : 0x141fffff:0x141fffff => 0x1f0000:0x1fffff

If the mapping is fixed and continuous (a), then I think all we need
is a single dts entry for the gce node that describes how it performs
this mapping.  And then, the gce consumers can just pass in their
regular physical addresses, and the gce driver can remap them directly
to gce addresses.

WDYT?

-Dan

>
> Yes, this mapping is fixed by hardware.
>
> > Does it vary for different SoCs?
>
> Yes, it varies for different SoCs.
>
> >
> > -Dan
> >
> > > That's why we need to know the mapping between the range of physical
> > > address and subsys.
> > > Please guide us a better way to code such requirement.
> > > Thanks for your help.
> > >
> > > Thanks,
> > > HS Liao
> > >
>
> Thanks,
> HS Liao
>
hs.liao@mediatek.com Feb. 1, 2016, 6:20 a.m. UTC | #8
On Mon, 2016-02-01 at 12:15 +0800, Daniel Kurtz wrote:
> On Mon, Feb 1, 2016 at 10:04 AM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
> >
> > On Fri, 2016-01-29 at 21:15 +0800, Daniel Kurtz wrote:
> > > On Fri, Jan 29, 2016 at 8:24 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
> > > > On Fri, 2016-01-29 at 16:42 +0800, Daniel Kurtz wrote:
> > > >> On Fri, Jan 29, 2016 at 3:39 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
> > > >> > Hi Dan,
> > > >> >
> > > >> > Many thanks for your comments and time.
> > > >> > I reply my plan inline.
> > > >> >
> > > >> >
> > > >> > On Thu, 2016-01-28 at 12:49 +0800, Daniel Kurtz wrote:
> > > >> >> Hi HS,
> > > >> >>
> > > >> >> Sorry for the delay.  It is hard to find time to review a >3700 line
> > > >> >> driver :-o in detail....
> > > >> >>
> > > >> >> Some review comments inline, although I still do not completely
> > > >> >> understand how all that this driver does and how it works.
> > > >> >> I'll try to find time to go through this driver in detail again next
> > > >> >> time you post it for review.
> > > >> >>
> > > >> >> On Tue, Jan 19, 2016 at 9:14 PM,  <hs.liao@mediatek.com> wrote:
> > > >> >> > From: HS Liao <hs.liao@mediatek.com>
> > > >> >> >
> > > >> >> > This patch is first version of Mediatek Command Queue(CMDQ) driver. The
> > > >> >> > CMDQ is used to help read/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: HS Liao <hs.liao@mediatek.com>
> > > >> >>
> > > >> >> [snip]
> > > >> >>
> > > >> >> > diff --git a/drivers/soc/mediatek/mtk-cmdq.c b/drivers/soc/mediatek/mtk-cmdq.c
> > > >> >> > new file mode 100644
> > > >> >> > index 0000000..7570f00
> > > >> >> > --- /dev/null
> > > >> >> > +++ b/drivers/soc/mediatek/mtk-cmdq.c
> > > >
> > > > [snip]
> > > >
> > > >> >> > +static const struct cmdq_subsys g_subsys[] = {
> > > >> >> > +       {0x1400, 1, "MMSYS"},
> > > >> >> > +       {0x1401, 2, "DISP"},
> > > >> >> > +       {0x1402, 3, "DISP"},
> > > >> >>
> > > >> >> This isn't going to scale.  These addresses could be different on
> > > >> >> different chips.
> > > >> >> Instead of a static table like this, we probably need specify to the
> > > >> >> connection between gce and other devices via devicetree phandles, and
> > > >> >> then use the phandles to lookup the corresponding device address
> > > >> >> range.
> > > >> >
> > > >> > I will define them in device tree.
> > > >> > E.g.
> > > >> > cmdq {
> > > >> >   reg_domain = 0x14000000, 0x14010000, 0x14020000
> > > >> > }
> > > >>
> > > >> The devicetree should only model hardware relationships, not software
> > > >> considerations.
> > > >>
> > > >> Is the hardware constraint here for using gce with various other
> > > >> hardware blocks?  I think we already model this by only providing a
> > > >> gce phandle in the device tree nodes for those devices that can use
> > > >> gce.
> > > >>
> > > >> Looking at the driver closer, as far as I can tell, the whole subsys
> > > >> concept is a purely software abstraction, and only used to debug the
> > > >> CMDQ_CODE_WRITE command.  In fact, AFAICT, everything would work fine
> > > >> if we just completely removed the 'subsys' concept, and just passed
> > > >> through the raw address provided by the driver.
> > > >>
> > > >> So, I recommend just removing 'subsys' completely from the driver -
> > > >> from this array, and in the masks.
> > > >>
> > > >> Instead, if there is an error on the write command, just print the
> > > >> address that fails.  There are other ways to deduce the subsystem from
> > > >> a physical address.
> > > >>
> > > >> Thanks,
> > > >>
> > > >> -Dan
> > > >
> > > > Hi Dan,
> > > >
> > > > Subsys is not just for debug.
> > > > Its main purpose is to transfer CPU address to GCE address.
> > > > Let me explain it by "write" op,
> > > > I list a code segment from cmdq_rec_append_command().
> > > >
> > > >         case CMDQ_CODE_WRITE:
> > > >                 subsys = cmdq_subsys_from_phys_addr(cqctx, arg_a);
> > > >                 if (subsys < 0) {
> > > >                         dev_err(dev,
> > > >                                 "unsupported memory base address 0x%08x\n",
> > > >                                 arg_a);
> > > >                         return -EFAULT;
> > > >                 }
> > > >
> > > >                 *cmd_ptr++ = arg_b;
> > > >                 *cmd_ptr++ = (CMDQ_CODE_WRITE << CMDQ_OP_CODE_SHIFT) |
> > > >                              (arg_a & CMDQ_ARG_A_WRITE_MASK) |
> > > >                              ((subsys & CMDQ_SUBSYS_MASK) << CMDQ_SUBSYS_SHIFT);
> > > >                 break;
> > > >
> > > > Subsys is mapped from physical address via cmdq_subsys_from_phys_addr(),
> > > > and then it becomes part of GCE command via ((subsys & CMDQ_SUBSYS_MASK)
> > > > << CMDQ_SUBSYS_SHIFT) .
> > > > Only low bits of physical address are the same as GCE address.
> > > > We can get it by (arg_a & CMDQ_ARG_A_WRITE_MASK).
> > > > MASK is used to define how many bits are valid for this op.
> > > > So, GCE address = subsys + valid low bits.
> > >
> > > How are these upper bits of the "GCE address" defined?
> > > In other words, for a given SoC, how is the mapping between physical
> > > io addresses to GCE addresses defined?
> > > Is this mapping fixed by hardware?
> 
> Please answer the detailed technical questions:
> 
> How are these upper bits of the "GCE address" defined?

A GCE command is arg_a + arg_b. Both of them have 32 bits length.
arg_a is op + subsys + addr, and arg_b is value.
subsys + addr is less than 32bits, so we need to map address range to
subsys.
The mapping rule is defined by hardware.

> In other words, for a given SoC, how is the mapping between physical
> io addresses to GCE addresses defined?

It is (b).

> 
> (a) Does the GCE remap a continuous device IO address range?
> 
> AFAICT, the  defines an MT8173 specific mapping of:
> 
> For example, the g_subsys table above seems to imply that the MT8173
> gce maps all of:
>   0x1400ffff:0x141fffff => 0x010000:0x1fffff
> 
> (b) Or, are the upper 5 bits of the "gce address" significant, and via
> hardware it can map a disjoint groups of device addresses into the
> continuous GCE address space, and really there are 0x1f distinct 64k
> mappings:
> 
> mmsys (1) : 0x14000000:0x1400ffff => 0x010000:0x01ffff
> disp  (2) : 0x14010000:0x1401ffff => 0x020000:0x02ffff
> disp  (3) : 0x14020000:0x1402ffff => 0x030000:0x03ffff
> ...
> ???? (1f) : 0x141fffff:0x141fffff => 0x1f0000:0x1fffff
> 
> If the mapping is fixed and continuous (a), then I think all we need
> is a single dts entry for the gce node that describes how it performs
> this mapping.  And then, the gce consumers can just pass in their
> regular physical addresses, and the gce driver can remap them directly
> to gce addresses.
> 
> WDYT?

How about this?
hardware_module = <address_base subsys_id mask>;
So, the result is
mmsys_config_base = <0x14000000 1 0xffff0000>;
disp_rdma_config_base = <0x14010000 2 0xffff0000>;
disp_mutex_config_base = <0x14020000 3 0xffff0000>;

> -Dan
> 
> >
> > Yes, this mapping is fixed by hardware.
> >
> > > Does it vary for different SoCs?
> >
> > Yes, it varies for different SoCs.
> >
> > >
> > > -Dan
> > >
> > > > That's why we need to know the mapping between the range of physical
> > > > address and subsys.
> > > > Please guide us a better way to code such requirement.
> > > > Thanks for your help.
> > > >
> > > > Thanks,
> > > > HS Liao
> > > >
> >
> > Thanks,
> > HS Liao
> >

Thanks,
HS Liao
Daniel Kurtz Feb. 1, 2016, 10:22 a.m. UTC | #9
On Mon, Feb 1, 2016 at 2:20 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
> On Mon, 2016-02-01 at 12:15 +0800, Daniel Kurtz wrote:
>> On Mon, Feb 1, 2016 at 10:04 AM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
>> >
>> > On Fri, 2016-01-29 at 21:15 +0800, Daniel Kurtz wrote:
>> > > On Fri, Jan 29, 2016 at 8:24 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
>> > > > On Fri, 2016-01-29 at 16:42 +0800, Daniel Kurtz wrote:
>> > > >> On Fri, Jan 29, 2016 at 3:39 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
>> > > >> > Hi Dan,
>> > > >> >
>> > > >> > Many thanks for your comments and time.
>> > > >> > I reply my plan inline.
>> > > >> >
>> > > >> >
>> > > >> > On Thu, 2016-01-28 at 12:49 +0800, Daniel Kurtz wrote:
>> > > >> >> Hi HS,
>> > > >> >>
>> > > >> >> Sorry for the delay.  It is hard to find time to review a >3700 line
>> > > >> >> driver :-o in detail....
>> > > >> >>
>> > > >> >> Some review comments inline, although I still do not completely
>> > > >> >> understand how all that this driver does and how it works.
>> > > >> >> I'll try to find time to go through this driver in detail again next
>> > > >> >> time you post it for review.
>> > > >> >>
>> > > >> >> On Tue, Jan 19, 2016 at 9:14 PM,  <hs.liao@mediatek.com> wrote:
>> > > >> >> > From: HS Liao <hs.liao@mediatek.com>
>> > > >> >> >
>> > > >> >> > This patch is first version of Mediatek Command Queue(CMDQ) driver. The
>> > > >> >> > CMDQ is used to help read/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: HS Liao <hs.liao@mediatek.com>
>> > > >> >>
>> > > >> >> [snip]
>> > > >> >>
>> > > >> >> > diff --git a/drivers/soc/mediatek/mtk-cmdq.c b/drivers/soc/mediatek/mtk-cmdq.c
>> > > >> >> > new file mode 100644
>> > > >> >> > index 0000000..7570f00
>> > > >> >> > --- /dev/null
>> > > >> >> > +++ b/drivers/soc/mediatek/mtk-cmdq.c
>> > > >
>> > > > [snip]
>> > > >
>> > > >> >> > +static const struct cmdq_subsys g_subsys[] = {
>> > > >> >> > +       {0x1400, 1, "MMSYS"},
>> > > >> >> > +       {0x1401, 2, "DISP"},
>> > > >> >> > +       {0x1402, 3, "DISP"},
>> > > >> >>
>> > > >> >> This isn't going to scale.  These addresses could be different on
>> > > >> >> different chips.
>> > > >> >> Instead of a static table like this, we probably need specify to the
>> > > >> >> connection between gce and other devices via devicetree phandles, and
>> > > >> >> then use the phandles to lookup the corresponding device address
>> > > >> >> range.
>> > > >> >
>> > > >> > I will define them in device tree.
>> > > >> > E.g.
>> > > >> > cmdq {
>> > > >> >   reg_domain = 0x14000000, 0x14010000, 0x14020000
>> > > >> > }
>> > > >>
>> > > >> The devicetree should only model hardware relationships, not software
>> > > >> considerations.
>> > > >>
>> > > >> Is the hardware constraint here for using gce with various other
>> > > >> hardware blocks?  I think we already model this by only providing a
>> > > >> gce phandle in the device tree nodes for those devices that can use
>> > > >> gce.
>> > > >>
>> > > >> Looking at the driver closer, as far as I can tell, the whole subsys
>> > > >> concept is a purely software abstraction, and only used to debug the
>> > > >> CMDQ_CODE_WRITE command.  In fact, AFAICT, everything would work fine
>> > > >> if we just completely removed the 'subsys' concept, and just passed
>> > > >> through the raw address provided by the driver.
>> > > >>
>> > > >> So, I recommend just removing 'subsys' completely from the driver -
>> > > >> from this array, and in the masks.
>> > > >>
>> > > >> Instead, if there is an error on the write command, just print the
>> > > >> address that fails.  There are other ways to deduce the subsystem from
>> > > >> a physical address.
>> > > >>
>> > > >> Thanks,
>> > > >>
>> > > >> -Dan
>> > > >
>> > > > Hi Dan,
>> > > >
>> > > > Subsys is not just for debug.
>> > > > Its main purpose is to transfer CPU address to GCE address.
>> > > > Let me explain it by "write" op,
>> > > > I list a code segment from cmdq_rec_append_command().
>> > > >
>> > > >         case CMDQ_CODE_WRITE:
>> > > >                 subsys = cmdq_subsys_from_phys_addr(cqctx, arg_a);
>> > > >                 if (subsys < 0) {
>> > > >                         dev_err(dev,
>> > > >                                 "unsupported memory base address 0x%08x\n",
>> > > >                                 arg_a);
>> > > >                         return -EFAULT;
>> > > >                 }
>> > > >
>> > > >                 *cmd_ptr++ = arg_b;
>> > > >                 *cmd_ptr++ = (CMDQ_CODE_WRITE << CMDQ_OP_CODE_SHIFT) |
>> > > >                              (arg_a & CMDQ_ARG_A_WRITE_MASK) |
>> > > >                              ((subsys & CMDQ_SUBSYS_MASK) << CMDQ_SUBSYS_SHIFT);
>> > > >                 break;
>> > > >
>> > > > Subsys is mapped from physical address via cmdq_subsys_from_phys_addr(),
>> > > > and then it becomes part of GCE command via ((subsys & CMDQ_SUBSYS_MASK)
>> > > > << CMDQ_SUBSYS_SHIFT) .
>> > > > Only low bits of physical address are the same as GCE address.
>> > > > We can get it by (arg_a & CMDQ_ARG_A_WRITE_MASK).
>> > > > MASK is used to define how many bits are valid for this op.
>> > > > So, GCE address = subsys + valid low bits.
>> > >
>> > > How are these upper bits of the "GCE address" defined?
>> > > In other words, for a given SoC, how is the mapping between physical
>> > > io addresses to GCE addresses defined?
>> > > Is this mapping fixed by hardware?
>>
>> Please answer the detailed technical questions:
>>
>> How are these upper bits of the "GCE address" defined?
>
> A GCE command is arg_a + arg_b. Both of them have 32 bits length.
> arg_a is op + subsys + addr, and arg_b is value.
> subsys + addr is less than 32bits, so we need to map address range to
> subsys.
> The mapping rule is defined by hardware.
>
>> In other words, for a given SoC, how is the mapping between physical
>> io addresses to GCE addresses defined?
>
> It is (b).
>
>>
>> (a) Does the GCE remap a continuous device IO address range?
>>
>> AFAICT, the  defines an MT8173 specific mapping of:
>>
>> For example, the g_subsys table above seems to imply that the MT8173
>> gce maps all of:
>>   0x1400ffff:0x141fffff => 0x010000:0x1fffff
>>
>> (b) Or, are the upper 5 bits of the "gce address" significant, and via
>> hardware it can map a disjoint groups of device addresses into the
>> continuous GCE address space, and really there are 0x1f distinct 64k
>> mappings:
>>
>> mmsys (1) : 0x14000000:0x1400ffff => 0x010000:0x01ffff
>> disp  (2) : 0x14010000:0x1401ffff => 0x020000:0x02ffff
>> disp  (3) : 0x14020000:0x1402ffff => 0x030000:0x03ffff
>> ...
>> ???? (1f) : 0x141fffff:0x141fffff => 0x1f0000:0x1fffff
>>
>> If the mapping is fixed and continuous (a), then I think all we need
>> is a single dts entry for the gce node that describes how it performs
>> this mapping.  And then, the gce consumers can just pass in their
>> regular physical addresses, and the gce driver can remap them directly
>> to gce addresses.
>>
>> WDYT?
>
> How about this?
> hardware_module = <address_base subsys_id mask>;
> So, the result is
> mmsys_config_base = <0x14000000 1 0xffff0000>;
> disp_rdma_config_base = <0x14010000 2 0xffff0000>;
> disp_mutex_config_base = <0x14020000 3 0xffff0000>;

What uses ID 0 and 4 - 0x1f?

According to mt8173.dtsi, here are the blocks in the address ranges above:

@1400:
  mmsys: clock-controller@14000000
  ovl0: ovl@1400c000
  ovl1: ovl@1400d000
  rdma0: rdma@1400e000
  rdma1: rdma@1400f000

@1401:
  rdma2: rdma@14010000
  wdma0: wdma@14011000
  wdma1: wdma@14012000
  color0: color@14013000
  color1: color@14014000
  aal@14015000
  gamma@14016000
  merge@14017000
  split0: split@14018000
  split1: split@14019000
  ufoe@1401a000
  dsi0: dsi@1401b000
  dsi1: dsi@1401c000
  dpi0: dpi@1401d000
  pwm0: pwm@1401e000
  pwm1: pwm@1401f000

@1402:
  mutex: mutex@14020000
  od@14023000
  larb0: larb@14021000
  smi_common: smi@14022000
  hdmi0: hdmi@14025000
  larb4: larb@14027000

I assume that the gce will work with any of the devices in those
ranges, not just "mmsys", "rdma" and "mutex", right?   (Also, notice
there are two "rdma" in the @1400 range, so rdma is really not a good
name for @1401)

Further, it looks like the gce just maps a large device address range
starting at 0x14000000 to (21-bit) gce address 0x010000, rather than
31 individually addressable 64k "subsys" blocks.  Is there a counter
example that I am missing?

-Dan

>
>> -Dan
>>
>> >
>> > Yes, this mapping is fixed by hardware.
>> >
>> > > Does it vary for different SoCs?
>> >
>> > Yes, it varies for different SoCs.
>> >
>> > >
>> > > -Dan
>> > >
>> > > > That's why we need to know the mapping between the range of physical
>> > > > address and subsys.
>> > > > Please guide us a better way to code such requirement.
>> > > > Thanks for your help.
>> > > >
>> > > > Thanks,
>> > > > HS Liao
>> > > >
>> >
>> > Thanks,
>> > HS Liao
>> >
>
> Thanks,
> HS Liao
>
hs.liao@mediatek.com Feb. 2, 2016, 6:48 a.m. UTC | #10
On Mon, 2016-02-01 at 18:22 +0800, Daniel Kurtz wrote:
> On Mon, Feb 1, 2016 at 2:20 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
> > On Mon, 2016-02-01 at 12:15 +0800, Daniel Kurtz wrote:
> >> On Mon, Feb 1, 2016 at 10:04 AM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
> >> >
> >> > On Fri, 2016-01-29 at 21:15 +0800, Daniel Kurtz wrote:
> >> > > On Fri, Jan 29, 2016 at 8:24 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
> >> > > > On Fri, 2016-01-29 at 16:42 +0800, Daniel Kurtz wrote:
> >> > > >> On Fri, Jan 29, 2016 at 3:39 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
> >> > > >> > Hi Dan,
> >> > > >> >
> >> > > >> > Many thanks for your comments and time.
> >> > > >> > I reply my plan inline.
> >> > > >> >
> >> > > >> >
> >> > > >> > On Thu, 2016-01-28 at 12:49 +0800, Daniel Kurtz wrote:
> >> > > >> >> Hi HS,
> >> > > >> >>
> >> > > >> >> Sorry for the delay.  It is hard to find time to review a >3700 line
> >> > > >> >> driver :-o in detail....
> >> > > >> >>
> >> > > >> >> Some review comments inline, although I still do not completely
> >> > > >> >> understand how all that this driver does and how it works.
> >> > > >> >> I'll try to find time to go through this driver in detail again next
> >> > > >> >> time you post it for review.
> >> > > >> >>
> >> > > >> >> On Tue, Jan 19, 2016 at 9:14 PM,  <hs.liao@mediatek.com> wrote:
> >> > > >> >> > From: HS Liao <hs.liao@mediatek.com>
> >> > > >> >> >
> >> > > >> >> > This patch is first version of Mediatek Command Queue(CMDQ) driver. The
> >> > > >> >> > CMDQ is used to help read/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: HS Liao <hs.liao@mediatek.com>
> >> > > >> >>
> >> > > >> >> [snip]
> >> > > >> >>
> >> > > >> >> > diff --git a/drivers/soc/mediatek/mtk-cmdq.c b/drivers/soc/mediatek/mtk-cmdq.c
> >> > > >> >> > new file mode 100644
> >> > > >> >> > index 0000000..7570f00
> >> > > >> >> > --- /dev/null
> >> > > >> >> > +++ b/drivers/soc/mediatek/mtk-cmdq.c
> >> > > >
> >> > > > [snip]
> >> > > >
> >> > > >> >> > +static const struct cmdq_subsys g_subsys[] = {
> >> > > >> >> > +       {0x1400, 1, "MMSYS"},
> >> > > >> >> > +       {0x1401, 2, "DISP"},
> >> > > >> >> > +       {0x1402, 3, "DISP"},
> >> > > >> >>
> >> > > >> >> This isn't going to scale.  These addresses could be different on
> >> > > >> >> different chips.
> >> > > >> >> Instead of a static table like this, we probably need specify to the
> >> > > >> >> connection between gce and other devices via devicetree phandles, and
> >> > > >> >> then use the phandles to lookup the corresponding device address
> >> > > >> >> range.
> >> > > >> >
> >> > > >> > I will define them in device tree.
> >> > > >> > E.g.
> >> > > >> > cmdq {
> >> > > >> >   reg_domain = 0x14000000, 0x14010000, 0x14020000
> >> > > >> > }
> >> > > >>
> >> > > >> The devicetree should only model hardware relationships, not software
> >> > > >> considerations.
> >> > > >>
> >> > > >> Is the hardware constraint here for using gce with various other
> >> > > >> hardware blocks?  I think we already model this by only providing a
> >> > > >> gce phandle in the device tree nodes for those devices that can use
> >> > > >> gce.
> >> > > >>
> >> > > >> Looking at the driver closer, as far as I can tell, the whole subsys
> >> > > >> concept is a purely software abstraction, and only used to debug the
> >> > > >> CMDQ_CODE_WRITE command.  In fact, AFAICT, everything would work fine
> >> > > >> if we just completely removed the 'subsys' concept, and just passed
> >> > > >> through the raw address provided by the driver.
> >> > > >>
> >> > > >> So, I recommend just removing 'subsys' completely from the driver -
> >> > > >> from this array, and in the masks.
> >> > > >>
> >> > > >> Instead, if there is an error on the write command, just print the
> >> > > >> address that fails.  There are other ways to deduce the subsystem from
> >> > > >> a physical address.
> >> > > >>
> >> > > >> Thanks,
> >> > > >>
> >> > > >> -Dan
> >> > > >
> >> > > > Hi Dan,
> >> > > >
> >> > > > Subsys is not just for debug.
> >> > > > Its main purpose is to transfer CPU address to GCE address.
> >> > > > Let me explain it by "write" op,
> >> > > > I list a code segment from cmdq_rec_append_command().
> >> > > >
> >> > > >         case CMDQ_CODE_WRITE:
> >> > > >                 subsys = cmdq_subsys_from_phys_addr(cqctx, arg_a);
> >> > > >                 if (subsys < 0) {
> >> > > >                         dev_err(dev,
> >> > > >                                 "unsupported memory base address 0x%08x\n",
> >> > > >                                 arg_a);
> >> > > >                         return -EFAULT;
> >> > > >                 }
> >> > > >
> >> > > >                 *cmd_ptr++ = arg_b;
> >> > > >                 *cmd_ptr++ = (CMDQ_CODE_WRITE << CMDQ_OP_CODE_SHIFT) |
> >> > > >                              (arg_a & CMDQ_ARG_A_WRITE_MASK) |
> >> > > >                              ((subsys & CMDQ_SUBSYS_MASK) << CMDQ_SUBSYS_SHIFT);
> >> > > >                 break;
> >> > > >
> >> > > > Subsys is mapped from physical address via cmdq_subsys_from_phys_addr(),
> >> > > > and then it becomes part of GCE command via ((subsys & CMDQ_SUBSYS_MASK)
> >> > > > << CMDQ_SUBSYS_SHIFT) .
> >> > > > Only low bits of physical address are the same as GCE address.
> >> > > > We can get it by (arg_a & CMDQ_ARG_A_WRITE_MASK).
> >> > > > MASK is used to define how many bits are valid for this op.
> >> > > > So, GCE address = subsys + valid low bits.
> >> > >
> >> > > How are these upper bits of the "GCE address" defined?
> >> > > In other words, for a given SoC, how is the mapping between physical
> >> > > io addresses to GCE addresses defined?
> >> > > Is this mapping fixed by hardware?
> >>
> >> Please answer the detailed technical questions:
> >>
> >> How are these upper bits of the "GCE address" defined?
> >
> > A GCE command is arg_a + arg_b. Both of them have 32 bits length.
> > arg_a is op + subsys + addr, and arg_b is value.
> > subsys + addr is less than 32bits, so we need to map address range to
> > subsys.
> > The mapping rule is defined by hardware.
> >
> >> In other words, for a given SoC, how is the mapping between physical
> >> io addresses to GCE addresses defined?
> >
> > It is (b).
> >
> >>
> >> (a) Does the GCE remap a continuous device IO address range?
> >>
> >> AFAICT, the  defines an MT8173 specific mapping of:
> >>
> >> For example, the g_subsys table above seems to imply that the MT8173
> >> gce maps all of:
> >>   0x1400ffff:0x141fffff => 0x010000:0x1fffff
> >>
> >> (b) Or, are the upper 5 bits of the "gce address" significant, and via
> >> hardware it can map a disjoint groups of device addresses into the
> >> continuous GCE address space, and really there are 0x1f distinct 64k
> >> mappings:
> >>
> >> mmsys (1) : 0x14000000:0x1400ffff => 0x010000:0x01ffff
> >> disp  (2) : 0x14010000:0x1401ffff => 0x020000:0x02ffff
> >> disp  (3) : 0x14020000:0x1402ffff => 0x030000:0x03ffff
> >> ...
> >> ???? (1f) : 0x141fffff:0x141fffff => 0x1f0000:0x1fffff
> >>
> >> If the mapping is fixed and continuous (a), then I think all we need
> >> is a single dts entry for the gce node that describes how it performs
> >> this mapping.  And then, the gce consumers can just pass in their
> >> regular physical addresses, and the gce driver can remap them directly
> >> to gce addresses.
> >>
> >> WDYT?
> >
> > How about this?
> > hardware_module = <address_base subsys_id mask>;
> > So, the result is
> > mmsys_config_base = <0x14000000 1 0xffff0000>;
> > disp_rdma_config_base = <0x14010000 2 0xffff0000>;
> > disp_mutex_config_base = <0x14020000 3 0xffff0000>;
> 
> What uses ID 0 and 4 - 0x1f?

Subsys is defined by GCE hardware, and other IDs are reserved currently.

> According to mt8173.dtsi, here are the blocks in the address ranges above:
> 
> @1400:
>   mmsys: clock-controller@14000000
>   ovl0: ovl@1400c000
>   ovl1: ovl@1400d000
>   rdma0: rdma@1400e000
>   rdma1: rdma@1400f000
> 
> @1401:
>   rdma2: rdma@14010000
>   wdma0: wdma@14011000
>   wdma1: wdma@14012000
>   color0: color@14013000
>   color1: color@14014000
>   aal@14015000
>   gamma@14016000
>   merge@14017000
>   split0: split@14018000
>   split1: split@14019000
>   ufoe@1401a000
>   dsi0: dsi@1401b000
>   dsi1: dsi@1401c000
>   dpi0: dpi@1401d000
>   pwm0: pwm@1401e000
>   pwm1: pwm@1401f000
> 
> @1402:
>   mutex: mutex@14020000
>   od@14023000
>   larb0: larb@14021000
>   smi_common: smi@14022000
>   hdmi0: hdmi@14025000
>   larb4: larb@14027000
> 
> I assume that the gce will work with any of the devices in those
> ranges, not just "mmsys", "rdma" and "mutex", right?   (Also, notice

That's right.

> there are two "rdma" in the @1400 range, so rdma is really not a good
> name for @1401)

I think we can just use index.
disp0_config_base = <0x14000000 1 0xffff0000>;
disp1_config_base = <0x14010000 2 0xffff0000>;
disp2_config_base = <0x14020000 3 0xffff0000>;

> Further, it looks like the gce just maps a large device address range
> starting at 0x14000000 to (21-bit) gce address 0x010000, rather than
> 31 individually addressable 64k "subsys" blocks.  Is there a counter
> example that I am missing?

From GCE's point of view,
it's 32 (0x0~0x1f) individually addressable 64k "subsys" blocks.
Currently, we don't have a counter example since all display related
address are put together.

> -Dan

Thanks,
HS Liao

> >
> >> -Dan
> >>
> >> >
> >> > Yes, this mapping is fixed by hardware.
> >> >
> >> > > Does it vary for different SoCs?
> >> >
> >> > Yes, it varies for different SoCs.
> >> >
> >> > >
> >> > > -Dan
> >> > >
> >> > > > That's why we need to know the mapping between the range of physical
> >> > > > address and subsys.
> >> > > > Please guide us a better way to code such requirement.
> >> > > > Thanks for your help.
> >> > > >
> >> > > > Thanks,
> >> > > > HS Liao
> >> > > >
> >> >
> >> > Thanks,
> >> > HS Liao
> >> >
> >
> > Thanks,
> > HS Liao
> >
Daniel Kurtz Feb. 2, 2016, 4:21 p.m. UTC | #11
On Tue, Feb 2, 2016 at 2:48 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
> On Mon, 2016-02-01 at 18:22 +0800, Daniel Kurtz wrote:
>> On Mon, Feb 1, 2016 at 2:20 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
>> > On Mon, 2016-02-01 at 12:15 +0800, Daniel Kurtz wrote:
>> >> On Mon, Feb 1, 2016 at 10:04 AM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
>> >> >
>> >> > On Fri, 2016-01-29 at 21:15 +0800, Daniel Kurtz wrote:
>> >> > > On Fri, Jan 29, 2016 at 8:24 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
>> >> > > > On Fri, 2016-01-29 at 16:42 +0800, Daniel Kurtz wrote:
>> >> > > >> On Fri, Jan 29, 2016 at 3:39 PM, Horng-Shyang Liao <hs.liao@mediatek.com> wrote:
>> >> > > >> > Hi Dan,
>> >> > > >> >
>> >> > > >> > Many thanks for your comments and time.
>> >> > > >> > I reply my plan inline.
>> >> > > >> >
>> >> > > >> >
>> >> > > >> > On Thu, 2016-01-28 at 12:49 +0800, Daniel Kurtz wrote:
>> >> > > >> >> Hi HS,
>> >> > > >> >>
>> >> > > >> >> Sorry for the delay.  It is hard to find time to review a >3700 line
>> >> > > >> >> driver :-o in detail....
>> >> > > >> >>
>> >> > > >> >> Some review comments inline, although I still do not completely
>> >> > > >> >> understand how all that this driver does and how it works.
>> >> > > >> >> I'll try to find time to go through this driver in detail again next
>> >> > > >> >> time you post it for review.
>> >> > > >> >>
>> >> > > >> >> On Tue, Jan 19, 2016 at 9:14 PM,  <hs.liao@mediatek.com> wrote:
>> >> > > >> >> > From: HS Liao <hs.liao@mediatek.com>
>> >> > > >> >> >
>> >> > > >> >> > This patch is first version of Mediatek Command Queue(CMDQ) driver. The
>> >> > > >> >> > CMDQ is used to help read/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: HS Liao <hs.liao@mediatek.com>
>> >> > > >> >>
>> >> > > >> >> [snip]
>> >> > > >> >>
>> >> > > >> >> > diff --git a/drivers/soc/mediatek/mtk-cmdq.c b/drivers/soc/mediatek/mtk-cmdq.c
>> >> > > >> >> > new file mode 100644
>> >> > > >> >> > index 0000000..7570f00
>> >> > > >> >> > --- /dev/null
>> >> > > >> >> > +++ b/drivers/soc/mediatek/mtk-cmdq.c
>> >> > > >
>> >> > > > [snip]
>> >> > > >
>> >> > > >> >> > +static const struct cmdq_subsys g_subsys[] = {
>> >> > > >> >> > +       {0x1400, 1, "MMSYS"},
>> >> > > >> >> > +       {0x1401, 2, "DISP"},
>> >> > > >> >> > +       {0x1402, 3, "DISP"},
>> >> > > >> >>
>> >> > > >> >> This isn't going to scale.  These addresses could be different on
>> >> > > >> >> different chips.
>> >> > > >> >> Instead of a static table like this, we probably need specify to the
>> >> > > >> >> connection between gce and other devices via devicetree phandles, and
>> >> > > >> >> then use the phandles to lookup the corresponding device address
>> >> > > >> >> range.
>> >> > > >> >
>> >> > > >> > I will define them in device tree.
>> >> > > >> > E.g.
>> >> > > >> > cmdq {
>> >> > > >> >   reg_domain = 0x14000000, 0x14010000, 0x14020000
>> >> > > >> > }
>> >> > > >>
>> >> > > >> The devicetree should only model hardware relationships, not software
>> >> > > >> considerations.
>> >> > > >>
>> >> > > >> Is the hardware constraint here for using gce with various other
>> >> > > >> hardware blocks?  I think we already model this by only providing a
>> >> > > >> gce phandle in the device tree nodes for those devices that can use
>> >> > > >> gce.
>> >> > > >>
>> >> > > >> Looking at the driver closer, as far as I can tell, the whole subsys
>> >> > > >> concept is a purely software abstraction, and only used to debug the
>> >> > > >> CMDQ_CODE_WRITE command.  In fact, AFAICT, everything would work fine
>> >> > > >> if we just completely removed the 'subsys' concept, and just passed
>> >> > > >> through the raw address provided by the driver.
>> >> > > >>
>> >> > > >> So, I recommend just removing 'subsys' completely from the driver -
>> >> > > >> from this array, and in the masks.
>> >> > > >>
>> >> > > >> Instead, if there is an error on the write command, just print the
>> >> > > >> address that fails.  There are other ways to deduce the subsystem from
>> >> > > >> a physical address.
>> >> > > >>
>> >> > > >> Thanks,
>> >> > > >>
>> >> > > >> -Dan
>> >> > > >
>> >> > > > Hi Dan,
>> >> > > >
>> >> > > > Subsys is not just for debug.
>> >> > > > Its main purpose is to transfer CPU address to GCE address.
>> >> > > > Let me explain it by "write" op,
>> >> > > > I list a code segment from cmdq_rec_append_command().
>> >> > > >
>> >> > > >         case CMDQ_CODE_WRITE:
>> >> > > >                 subsys = cmdq_subsys_from_phys_addr(cqctx, arg_a);
>> >> > > >                 if (subsys < 0) {
>> >> > > >                         dev_err(dev,
>> >> > > >                                 "unsupported memory base address 0x%08x\n",
>> >> > > >                                 arg_a);
>> >> > > >                         return -EFAULT;
>> >> > > >                 }
>> >> > > >
>> >> > > >                 *cmd_ptr++ = arg_b;
>> >> > > >                 *cmd_ptr++ = (CMDQ_CODE_WRITE << CMDQ_OP_CODE_SHIFT) |
>> >> > > >                              (arg_a & CMDQ_ARG_A_WRITE_MASK) |
>> >> > > >                              ((subsys & CMDQ_SUBSYS_MASK) << CMDQ_SUBSYS_SHIFT);
>> >> > > >                 break;
>> >> > > >
>> >> > > > Subsys is mapped from physical address via cmdq_subsys_from_phys_addr(),
>> >> > > > and then it becomes part of GCE command via ((subsys & CMDQ_SUBSYS_MASK)
>> >> > > > << CMDQ_SUBSYS_SHIFT) .
>> >> > > > Only low bits of physical address are the same as GCE address.
>> >> > > > We can get it by (arg_a & CMDQ_ARG_A_WRITE_MASK).
>> >> > > > MASK is used to define how many bits are valid for this op.
>> >> > > > So, GCE address = subsys + valid low bits.
>> >> > >
>> >> > > How are these upper bits of the "GCE address" defined?
>> >> > > In other words, for a given SoC, how is the mapping between physical
>> >> > > io addresses to GCE addresses defined?
>> >> > > Is this mapping fixed by hardware?
>> >>
>> >> Please answer the detailed technical questions:
>> >>
>> >> How are these upper bits of the "GCE address" defined?
>> >
>> > A GCE command is arg_a + arg_b. Both of them have 32 bits length.
>> > arg_a is op + subsys + addr, and arg_b is value.
>> > subsys + addr is less than 32bits, so we need to map address range to
>> > subsys.
>> > The mapping rule is defined by hardware.
>> >
>> >> In other words, for a given SoC, how is the mapping between physical
>> >> io addresses to GCE addresses defined?
>> >
>> > It is (b).
>> >
>> >>
>> >> (a) Does the GCE remap a continuous device IO address range?
>> >>
>> >> AFAICT, the  defines an MT8173 specific mapping of:
>> >>
>> >> For example, the g_subsys table above seems to imply that the MT8173
>> >> gce maps all of:
>> >>   0x1400ffff:0x141fffff => 0x010000:0x1fffff
>> >>
>> >> (b) Or, are the upper 5 bits of the "gce address" significant, and via
>> >> hardware it can map a disjoint groups of device addresses into the
>> >> continuous GCE address space, and really there are 0x1f distinct 64k
>> >> mappings:
>> >>
>> >> mmsys (1) : 0x14000000:0x1400ffff => 0x010000:0x01ffff
>> >> disp  (2) : 0x14010000:0x1401ffff => 0x020000:0x02ffff
>> >> disp  (3) : 0x14020000:0x1402ffff => 0x030000:0x03ffff
>> >> ...
>> >> ???? (1f) : 0x141fffff:0x141fffff => 0x1f0000:0x1fffff
>> >>
>> >> If the mapping is fixed and continuous (a), then I think all we need
>> >> is a single dts entry for the gce node that describes how it performs
>> >> this mapping.  And then, the gce consumers can just pass in their
>> >> regular physical addresses, and the gce driver can remap them directly
>> >> to gce addresses.
>> >>
>> >> WDYT?
>> >
>> > How about this?
>> > hardware_module = <address_base subsys_id mask>;
>> > So, the result is
>> > mmsys_config_base = <0x14000000 1 0xffff0000>;
>> > disp_rdma_config_base = <0x14010000 2 0xffff0000>;
>> > disp_mutex_config_base = <0x14020000 3 0xffff0000>;
>>
>> What uses ID 0 and 4 - 0x1f?
>
> Subsys is defined by GCE hardware, and other IDs are reserved currently.
>
>> According to mt8173.dtsi, here are the blocks in the address ranges above:
>>
>> @1400:
>>   mmsys: clock-controller@14000000
>>   ovl0: ovl@1400c000
>>   ovl1: ovl@1400d000
>>   rdma0: rdma@1400e000
>>   rdma1: rdma@1400f000
>>
>> @1401:
>>   rdma2: rdma@14010000
>>   wdma0: wdma@14011000
>>   wdma1: wdma@14012000
>>   color0: color@14013000
>>   color1: color@14014000
>>   aal@14015000
>>   gamma@14016000
>>   merge@14017000
>>   split0: split@14018000
>>   split1: split@14019000
>>   ufoe@1401a000
>>   dsi0: dsi@1401b000
>>   dsi1: dsi@1401c000
>>   dpi0: dpi@1401d000
>>   pwm0: pwm@1401e000
>>   pwm1: pwm@1401f000
>>
>> @1402:
>>   mutex: mutex@14020000
>>   od@14023000
>>   larb0: larb@14021000
>>   smi_common: smi@14022000
>>   hdmi0: hdmi@14025000
>>   larb4: larb@14027000
>>
>> I assume that the gce will work with any of the devices in those
>> ranges, not just "mmsys", "rdma" and "mutex", right?   (Also, notice
>
> That's right.
>
>> there are two "rdma" in the @1400 range, so rdma is really not a good
>> name for @1401)
>
> I think we can just use index.
> disp0_config_base = <0x14000000 1 0xffff0000>;
> disp1_config_base = <0x14010000 2 0xffff0000>;
> disp2_config_base = <0x14020000 3 0xffff0000>;
>
>> Further, it looks like the gce just maps a large device address range
>> starting at 0x14000000 to (21-bit) gce address 0x010000, rather than
>> 31 individually addressable 64k "subsys" blocks.  Is there a counter
>> example that I am missing?
>
> From GCE's point of view,
> it's 32 (0x0~0x1f) individually addressable 64k "subsys" blocks.
> Currently, we don't have a counter example since all display related
> address are put together.

Ok, in this case, perhaps we should treat the GCE like an IOMMU, and
have its binding define 32 slots or channels.
Then, any device that wishes to send the GCE commands for its address
range should register a phandle to the gce, including the
corresponding slot.

For example:

include/.../gce.h
include/dt-bindings/../mediatek-gce.h
#define GCE_SLOT_1  1
...

arch/arm64/boot/dts/mediatek/mt8173.dtsi:

&ovl0: {
  mediatek,gce = <&gce GCE_SLOT_1>;
};

&ovl1: {
  mediatek,gce = <&gce GCE_SLOT_1>;
};

&rdma2: {
  mediatek,gce = <&gce GCE_SLOT_2>;
};

&mutex: {
  mediatek,gce = <&gce GCE_SLOT_3>;
};

&od: {
  mediatek,gce = <&gce GCE_SLOT_3>;
};

Then, as each platform driver is probed, it can use the phandle to
look up its corresponding gce slot instance, retrieving an (opaque)
pointer to a struct gce_slot.
The gce driver can have a set of constant tables matching the slots to
address ranges for particular per-soc compatibles, one of which is
loaded on probe.
Later, when the device (gce consumer) wants to send a gce write
command, it passes in the gce_slot as an argument, and the gce driver
can do the corresponding lookup of subsys value and mask out the
provided *device virtual* address.  In this way, you also no longer
need to convert the devices iomap'ed addresses into physical addresses
before passing them to the gce.

WDYT?

-Dan
diff mbox

Patch

diff --git a/drivers/soc/mediatek/Kconfig b/drivers/soc/mediatek/Kconfig
index 9d50682..8a03426 100644
--- a/drivers/soc/mediatek/Kconfig
+++ b/drivers/soc/mediatek/Kconfig
@@ -1,6 +1,16 @@ 
 #
 # MediaTek SoC drivers
 #
+config MTK_CMDQ
+	bool "MediaTek CMDQ Support"
+	depends on ARCH_MEDIATEK || COMPILE_TEST
+	select MTK_INFRACFG
+	help
+	  Say yes here to add support for the MediaTek Command Queue (CMDQ)
+	  driver. The CMDQ is used to help read/write registers with critical
+	  time limitation, such as updating display configuration during the
+	  vblank.
+
 config MTK_INFRACFG
 	bool "MediaTek INFRACFG Support"
 	depends on ARCH_MEDIATEK || COMPILE_TEST
diff --git a/drivers/soc/mediatek/Makefile b/drivers/soc/mediatek/Makefile
index 12998b0..f7397ef 100644
--- a/drivers/soc/mediatek/Makefile
+++ b/drivers/soc/mediatek/Makefile
@@ -1,3 +1,4 @@ 
+obj-$(CONFIG_MTK_CMDQ) += mtk-cmdq.o
 obj-$(CONFIG_MTK_INFRACFG) += mtk-infracfg.o
 obj-$(CONFIG_MTK_PMIC_WRAP) += mtk-pmic-wrap.o
 obj-$(CONFIG_MTK_SCPSYS) += mtk-scpsys.o
diff --git a/drivers/soc/mediatek/mtk-cmdq.c b/drivers/soc/mediatek/mtk-cmdq.c
new file mode 100644
index 0000000..7570f00
--- /dev/null
+++ b/drivers/soc/mediatek/mtk-cmdq.c
@@ -0,0 +1,3532 @@ 
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/errno.h>
+#include <linux/ftrace.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/memory.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/sched.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/suspend.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <linux/workqueue.h>
+#include <soc/mediatek/cmdq.h>
+
+#define CMDQ_MAX_THREAD_COUNT		3
+
+#define CMDQ_MAX_RETRY_COUNT		1
+#define CMDQ_MAX_TASK_IN_THREAD		2
+
+/*
+ * Maximum prefetch buffer size.
+ * Unit is instructions.
+ */
+#define CMDQ_MAX_PREFETCH_INSTUCTION	240
+
+#define CMDQ_INITIAL_CMD_BLOCK_SIZE	PAGE_SIZE
+#define CMDQ_CMD_BUF_POOL_BUF_SIZE	(128 * 1024)
+#define CMDQ_CMD_BUF_POOL_BUF_NUM	4 /* (main + sub) * 2 = 4 */
+#define CMDQ_INST_SIZE			8 /* instruction is 64-bit */
+
+#define CMDQ_MIN_AGE_VALUE		5
+
+/*
+ * cmdq_thread cookie value is from 0 to CMDQ_MAX_COOKIE_VALUE.
+ * And, this value also be used as MASK.
+ */
+#define CMDQ_MAX_COOKIE_VALUE		0xffff
+#define CMDQ_COOKIE_MASK		CMDQ_MAX_COOKIE_VALUE
+
+#define CMDQ_DEFAULT_TIMEOUT_MS		1000
+#define CMDQ_ACQUIRE_THREAD_TIMEOUT_MS	5000
+#define CMDQ_PREALARM_TIMEOUT_MS	200
+
+#define CMDQ_INVALID_THREAD		-1
+
+#define CMDQ_DRIVER_DEVICE_NAME		"mtk_cmdq"
+
+#define CMDQ_CLK_NAME			"gce"
+
+#define CMDQ_CURR_IRQ_STATUS_OFFSET	0x010
+#define CMDQ_CURR_LOADED_THR_OFFSET	0x018
+#define CMDQ_THR_SLOT_CYCLES_OFFSET	0x030
+#define CMDQ_THR_EXEC_CYCLES_OFFSET	0x034
+#define CMDQ_THR_TIMEOUT_TIMER_OFFSET	0x038
+#define CMDQ_BUS_CONTROL_TYPE_OFFSET	0x040
+
+#define CMDQ_SYNC_TOKEN_ID_OFFSET	0x060
+#define CMDQ_SYNC_TOKEN_VAL_OFFSET	0x064
+#define CMDQ_SYNC_TOKEN_UPD_OFFSET	0x068
+
+#define CMDQ_GPR_SHIFT			0x004
+#define CMDQ_GPR_OFFSET			0x080
+
+#define CMDQ_THR_SHIFT			0x080
+#define CMDQ_THR_WARM_RESET_OFFSET	0x100
+#define CMDQ_THR_ENABLE_TASK_OFFSET	0x104
+#define CMDQ_THR_SUSPEND_TASK_OFFSET	0x108
+#define CMDQ_THR_CURR_STATUS_OFFSET	0x10c
+#define CMDQ_THR_IRQ_STATUS_OFFSET	0x110
+#define CMDQ_THR_IRQ_ENABLE_OFFSET	0x114
+#define CMDQ_THR_CURR_ADDR_OFFSET	0x120
+#define CMDQ_THR_END_ADDR_OFFSET	0x124
+#define CMDQ_THR_EXEC_CNT_OFFSET	0x128
+#define CMDQ_THR_WAIT_TOKEN_OFFSET	0x130
+#define CMDQ_THR_CFG_OFFSET		0x140
+#define CMDQ_THR_PREFETCH_OFFSET	0x144
+#define CMDQ_THR_INST_CYCLES_OFFSET	0x150
+#define CMDQ_THR_INST_THRESX_OFFSET	0x154
+#define CMDQ_THR_STATUS_OFFSET		0x18c
+
+#define CMDQ_SYNC_TOKEN_SET		BIT(16)
+#define CMDQ_IRQ_MASK			0xffff
+
+#define CMDQ_THR_ENABLED		0x1
+#define CMDQ_THR_DISABLED		0x0
+#define CMDQ_THR_SUSPEND		0x1
+#define CMDQ_THR_RESUME			0x0
+#define CMDQ_THR_STATUS_SUSPENDED	BIT(1)
+#define CMDQ_THR_WARM_RESET		BIT(0)
+#define CMDQ_THR_SLOT_CYCLES		0x3200
+#define CMDQ_THR_NO_TIMEOUT		0x0
+#define CMDQ_THR_PREFETCH_EN		0x1
+#define CMDQ_THR_PRIORITY_MASK		0x7
+#define CMDQ_THR_IRQ_DONE		0x1
+#define CMDQ_THR_IRQ_ERROR		0x12
+#define CMDQ_THR_IRQ_EN			0x13 /* done + error */
+#define CMDQ_THR_IRQ_MASK		0x13
+#define CMDQ_THR_EXECUTING		BIT(31)
+
+#define CMDQ_ARG_A_MASK			0xffffff
+#define CMDQ_ARG_A_WRITE_MASK		0xffff
+#define CMDQ_ARG_A_SUBSYS_MASK		0x1f0000
+#define CMDQ_SUBSYS_MASK		0x1f
+
+#define CMDQ_OP_CODE_SHIFT		24
+#define CMDQ_SUBSYS_SHIFT		16
+
+#define CMDQ_JUMP_BY_OFFSET		0x10000000
+#define CMDQ_JUMP_BY_PA			0x10000001
+#define CMDQ_JUMP_TO_BEGIN		0x8
+
+#define CMDQ_WFE_UPDATE			BIT(31)
+#define CMDQ_WFE_WAIT			BIT(15)
+#define CMDQ_WFE_WAIT_VALUE		0x1
+
+#define CMDQ_MARK_NON_SUSPENDABLE	BIT(21) /* 53 - 32 = 21 */
+#define CMDQ_MARK_NOT_ADD_COUNTER	BIT(16) /* 48 - 32 = 16 */
+#define CMDQ_MARK_PREFETCH_MARKER	BIT(20)
+#define CMDQ_MARK_PREFETCH_MARKER_EN	BIT(17)
+#define CMDQ_MARK_PREFETCH_EN		BIT(16)
+
+#define CMDQ_EOC_IRQ_EN			BIT(0)
+
+#define CMDQ_ENABLE_MASK		BIT(0)
+
+/* get lsb for subsys encoding in arg_a (range: 0 - 31) */
+
+enum cmdq_eng {
+	CMDQ_ENG_DISP_UFOE = 0,
+	CMDQ_ENG_DISP_AAL,
+	CMDQ_ENG_DISP_COLOR0,
+	CMDQ_ENG_DISP_COLOR1,
+	CMDQ_ENG_DISP_RDMA0,
+	CMDQ_ENG_DISP_RDMA1,
+	CMDQ_ENG_DISP_RDMA2,
+	CMDQ_ENG_DISP_WDMA0,
+	CMDQ_ENG_DISP_WDMA1,
+	CMDQ_ENG_DISP_OVL0,
+	CMDQ_ENG_DISP_OVL1,
+	CMDQ_ENG_DISP_GAMMA,
+	CMDQ_ENG_DISP_DSI0_CMD,
+	CMDQ_ENG_DISP_DSI1_CMD,
+	CMDQ_MAX_ENGINE_COUNT	/* ALWAYS keep at the end */
+};
+
+struct cmdq_command {
+	struct cmdq		*cqctx;
+	u32			scenario;
+	/* task priority (NOT HW thread priority) */
+	u32			priority;
+	/* bit flag of used engines */
+	u64			engine_flag;
+	/*
+	 * pointer of instruction buffer
+	 * This must point to an 64-bit aligned u32 array
+	 */
+	u32			*va_base;
+	/* size of instruction buffer, in bytes. */
+	u32			block_size;
+};
+
+enum cmdq_code {
+	/* These are actual HW op code. */
+	CMDQ_CODE_MOVE = 0x02,
+	CMDQ_CODE_WRITE = 0x04,
+	CMDQ_CODE_JUMP = 0x10,
+	CMDQ_CODE_WFE = 0x20,	/* wait for event (and clear) */
+	CMDQ_CODE_CLEAR_EVENT = 0x21,	/* clear event */
+	CMDQ_CODE_EOC = 0x40,	/* end of command */
+};
+
+enum cmdq_task_state {
+	TASK_STATE_IDLE,	/* free task */
+	TASK_STATE_BUSY,	/* task running on a thread */
+	TASK_STATE_KILLED,	/* task process being killed */
+	TASK_STATE_ERROR,	/* task execution error */
+	TASK_STATE_DONE,	/* task finished */
+	TASK_STATE_WAITING,	/* allocated but waiting for available thread */
+};
+
+struct cmdq_cmd_buf {
+	atomic_t		used;
+	void			*va;
+	dma_addr_t		pa;
+};
+
+struct cmdq_task_cb {
+	/* called by isr */
+	cmdq_async_flush_cb	isr_cb;
+	void			*isr_data;
+	/* called by releasing task */
+	cmdq_async_flush_cb	done_cb;
+	void			*done_data;
+};
+
+struct cmdq_task {
+	struct cmdq		*cqctx;
+	struct list_head	list_entry;
+
+	/* state for task life cycle */
+	enum cmdq_task_state	task_state;
+	/* virtual address of command buffer */
+	u32			*va_base;
+	/* physical address of command buffer */
+	dma_addr_t		mva_base;
+	/* size of allocated command buffer */
+	u32			buf_size;
+	/* It points to a cmdq_cmd_buf if this task use command buffer pool. */
+	struct cmdq_cmd_buf	*cmd_buf;
+
+	int			scenario;
+	int			priority;
+	u64			engine_flag;
+	u32			command_size;
+	u32			num_cmd;
+	int			reorder;
+	/* HW thread ID; CMDQ_INVALID_THREAD if not running */
+	int			thread;
+	/* flag of IRQ received */
+	int			irq_flag;
+	/* callback functions */
+	struct cmdq_task_cb	cb;
+	/* work item when auto release is used */
+	struct work_struct	auto_release_work;
+
+	unsigned long long	submit; /* submit time */
+
+	pid_t			caller_pid;
+	char			caller_name[TASK_COMM_LEN];
+};
+
+struct cmdq_thread {
+	u32			task_count;
+	u32			wait_cookie;
+	u32			next_cookie;
+	struct cmdq_task	*cur_task[CMDQ_MAX_TASK_IN_THREAD];
+};
+
+struct cmdq {
+	struct device		*dev;
+	struct notifier_block	pm_notifier;
+
+	void __iomem		*base_va;
+	unsigned long		base_pa;
+	u32			irq;
+
+	/*
+	 * task information
+	 * task_cache: struct cmdq_task object cache
+	 * task_free_list: unused free tasks
+	 * task_active_list: active tasks
+	 * task_consume_wait_queue_item: task consumption work item
+	 * task_auto_release_wq: auto-release workqueue
+	 * task_consume_wq: task consumption workqueue (for queued tasks)
+	 */
+	struct kmem_cache	*task_cache;
+	struct list_head	task_free_list;
+	struct list_head	task_active_list;
+	struct list_head	task_wait_list;
+	struct work_struct	task_consume_wait_queue_item;
+	struct workqueue_struct	*task_auto_release_wq;
+	struct workqueue_struct	*task_consume_wq;
+	u16			task_count[CMDQ_MAX_THREAD_COUNT];
+
+	struct cmdq_thread	thread[CMDQ_MAX_THREAD_COUNT];
+
+	/* mutex, spinlock, flag */
+	struct mutex		task_mutex;	/* for task list */
+	struct mutex		clock_mutex;	/* for clock operation */
+	spinlock_t		thread_lock;	/* for cmdq hardware thread */
+	int			thread_usage;
+	spinlock_t		exec_lock;	/* for exec task */
+
+	/* suspend */
+	bool			suspended;
+
+	/* command buffer pool */
+	struct cmdq_cmd_buf	cmd_buf_pool[CMDQ_CMD_BUF_POOL_BUF_NUM];
+
+	/*
+	 * notification
+	 * wait_queue: for task done
+	 * thread_dispatch_queue: for thread acquiring
+	 */
+	wait_queue_head_t	wait_queue[CMDQ_MAX_THREAD_COUNT];
+	wait_queue_head_t	thread_dispatch_queue;
+
+	/* ccf */
+	struct clk		*clock;
+};
+
+struct cmdq_event_name {
+	enum cmdq_event	event;
+	char		*name;
+};
+
+struct cmdq_subsys {
+	u32	base_addr;
+	int	id;
+	char	*grp_name;
+};
+
+static const struct cmdq_event_name g_event_name[] = {
+	/* Display start of frame(SOF) events */
+	{CMDQ_EVENT_DISP_OVL0_SOF, "CMDQ_EVENT_DISP_OVL0_SOF",},
+	{CMDQ_EVENT_DISP_OVL1_SOF, "CMDQ_EVENT_DISP_OVL1_SOF",},
+	{CMDQ_EVENT_DISP_RDMA0_SOF, "CMDQ_EVENT_DISP_RDMA0_SOF",},
+	{CMDQ_EVENT_DISP_RDMA1_SOF, "CMDQ_EVENT_DISP_RDMA1_SOF",},
+	{CMDQ_EVENT_DISP_RDMA2_SOF, "CMDQ_EVENT_DISP_RDMA2_SOF",},
+	{CMDQ_EVENT_DISP_WDMA0_SOF, "CMDQ_EVENT_DISP_WDMA0_SOF",},
+	{CMDQ_EVENT_DISP_WDMA1_SOF, "CMDQ_EVENT_DISP_WDMA1_SOF",},
+	/* Display end of frame(EOF) events */
+	{CMDQ_EVENT_DISP_OVL0_EOF, "CMDQ_EVENT_DISP_OVL0_EOF",},
+	{CMDQ_EVENT_DISP_OVL1_EOF, "CMDQ_EVENT_DISP_OVL1_EOF",},
+	{CMDQ_EVENT_DISP_RDMA0_EOF, "CMDQ_EVENT_DISP_RDMA0_EOF",},
+	{CMDQ_EVENT_DISP_RDMA1_EOF, "CMDQ_EVENT_DISP_RDMA1_EOF",},
+	{CMDQ_EVENT_DISP_RDMA2_EOF, "CMDQ_EVENT_DISP_RDMA2_EOF",},
+	{CMDQ_EVENT_DISP_WDMA0_EOF, "CMDQ_EVENT_DISP_WDMA0_EOF",},
+	{CMDQ_EVENT_DISP_WDMA1_EOF, "CMDQ_EVENT_DISP_WDMA1_EOF",},
+	/* Mutex end of frame(EOF) events */
+	{CMDQ_EVENT_MUTEX0_STREAM_EOF, "CMDQ_EVENT_MUTEX0_STREAM_EOF",},
+	{CMDQ_EVENT_MUTEX1_STREAM_EOF, "CMDQ_EVENT_MUTEX1_STREAM_EOF",},
+	{CMDQ_EVENT_MUTEX2_STREAM_EOF, "CMDQ_EVENT_MUTEX2_STREAM_EOF",},
+	{CMDQ_EVENT_MUTEX3_STREAM_EOF, "CMDQ_EVENT_MUTEX3_STREAM_EOF",},
+	{CMDQ_EVENT_MUTEX4_STREAM_EOF, "CMDQ_EVENT_MUTEX4_STREAM_EOF",},
+	/* Display underrun events */
+	{CMDQ_EVENT_DISP_RDMA0_UNDERRUN, "CMDQ_EVENT_DISP_RDMA0_UNDERRUN",},
+	{CMDQ_EVENT_DISP_RDMA1_UNDERRUN, "CMDQ_EVENT_DISP_RDMA1_UNDERRUN",},
+	{CMDQ_EVENT_DISP_RDMA2_UNDERRUN, "CMDQ_EVENT_DISP_RDMA2_UNDERRUN",},
+	/* Keep this at the end of HW events */
+	{CMDQ_MAX_HW_EVENT_COUNT, "CMDQ_MAX_HW_EVENT_COUNT",},
+	/* GPR events */
+	{CMDQ_SYNC_TOKEN_GPR_SET_0, "CMDQ_SYNC_TOKEN_GPR_SET_0",},
+	{CMDQ_SYNC_TOKEN_GPR_SET_1, "CMDQ_SYNC_TOKEN_GPR_SET_1",},
+	{CMDQ_SYNC_TOKEN_GPR_SET_2, "CMDQ_SYNC_TOKEN_GPR_SET_2",},
+	{CMDQ_SYNC_TOKEN_GPR_SET_3, "CMDQ_SYNC_TOKEN_GPR_SET_3",},
+	{CMDQ_SYNC_TOKEN_GPR_SET_4, "CMDQ_SYNC_TOKEN_GPR_SET_4",},
+	/* This is max event and also can be used as mask. */
+	{CMDQ_SYNC_TOKEN_MAX, "CMDQ_SYNC_TOKEN_MAX",},
+	/* Invalid event */
+	{CMDQ_SYNC_TOKEN_INVALID, "CMDQ_SYNC_TOKEN_INVALID",},
+};
+
+static const struct cmdq_subsys g_subsys[] = {
+	{0x1400, 1, "MMSYS"},
+	{0x1401, 2, "DISP"},
+	{0x1402, 3, "DISP"},
+};
+
+static const char *cmdq_event_get_name(enum cmdq_event event)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(g_event_name); i++)
+		if (g_event_name[i].event == event)
+			return g_event_name[i].name;
+
+	return "CMDQ_EVENT_UNKNOWN";
+}
+
+static void cmdq_event_set(void __iomem *gce_base_va, enum cmdq_event event)
+{
+	writel(CMDQ_SYNC_TOKEN_SET | event,
+	       gce_base_va + CMDQ_SYNC_TOKEN_UPD_OFFSET);
+}
+
+static void cmdq_event_reset(struct cmdq *cqctx)
+{
+	int i;
+	u32 val;
+	void __iomem *gce_base_va = cqctx->base_va;
+
+	/* set all defined events to 0 */
+	for (i = 0; i < ARRAY_SIZE(g_event_name); i++) {
+		if (g_event_name[i].event >= CMDQ_MAX_HW_EVENT_COUNT)
+			break;
+
+		val = CMDQ_SYNC_TOKEN_MAX & g_event_name[i].event;
+		writel(val, gce_base_va + CMDQ_SYNC_TOKEN_UPD_OFFSET);
+	}
+
+	/* set GPR(resource flags) to 1 */
+	cmdq_event_set(gce_base_va, CMDQ_SYNC_TOKEN_GPR_SET_0);
+	cmdq_event_set(gce_base_va, CMDQ_SYNC_TOKEN_GPR_SET_1);
+	cmdq_event_set(gce_base_va, CMDQ_SYNC_TOKEN_GPR_SET_2);
+	cmdq_event_set(gce_base_va, CMDQ_SYNC_TOKEN_GPR_SET_3);
+	cmdq_event_set(gce_base_va, CMDQ_SYNC_TOKEN_GPR_SET_4);
+}
+
+static int cmdq_subsys_base_addr_to_id(u32 base_addr)
+{
+	int i;
+	int id = -EFAULT;
+
+	for (i = 0; i < ARRAY_SIZE(g_subsys); i++) {
+		if (g_subsys[i].base_addr == base_addr) {
+			id = g_subsys[i].id;
+			break;
+		}
+	}
+
+	return id;
+}
+
+static u32 cmdq_subsys_id_to_base_addr(int id)
+{
+	int i;
+	u32 base_addr = 0;
+
+	for (i = 0; i < ARRAY_SIZE(g_subsys); i++) {
+		if (g_subsys[i].id == id) {
+			base_addr = g_subsys[i].base_addr;
+			break;
+		}
+	}
+
+	return base_addr;
+}
+
+static const char *cmdq_subsys_base_addr_to_grp_name(u32 base_addr)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(g_subsys); i++)
+		if (g_subsys[i].base_addr == base_addr)
+			return g_subsys[i].grp_name;
+
+	return NULL;
+}
+
+static bool cmdq_scenario_is_prefetch(enum cmdq_scenario scenario)
+{
+	switch (scenario) {
+	case CMDQ_SCENARIO_PRIMARY_DISP:
+		/*
+		 * Primary DISP HW should enable prefetch.
+		 * Since thread 0/1 shares one prefetch buffer,
+		 * we allow only PRIMARY path to use prefetch.
+		 */
+		return true;
+	default:
+		return false;
+	}
+
+	return false;
+}
+
+static int cmdq_scenario_get_thread(enum cmdq_scenario scenario)
+{
+	if (cmdq_scenario_is_prefetch(scenario))
+		return 0;
+
+	switch (scenario) {
+	case CMDQ_SCENARIO_PRIMARY_DISP:
+		/* primary display: thread 0 */
+		return 0;
+	case CMDQ_SCENARIO_SUB_DISP:
+		/*
+		 * When HW thread 0 enables pre-fetch,
+		 * sub display need to use thread 2 instead of 1
+		 */
+		return 2;
+	default:
+		/* freely dispatch */
+		return CMDQ_INVALID_THREAD;
+	}
+
+	/* freely dispatch */
+	return CMDQ_INVALID_THREAD;
+}
+
+static const char *cmdq_event_get_module(enum cmdq_event event)
+{
+	const char *module;
+
+	switch (event) {
+	case CMDQ_EVENT_DISP_RDMA0_SOF:
+	case CMDQ_EVENT_DISP_RDMA1_SOF:
+	case CMDQ_EVENT_DISP_RDMA2_SOF:
+	case CMDQ_EVENT_DISP_RDMA0_EOF:
+	case CMDQ_EVENT_DISP_RDMA1_EOF:
+	case CMDQ_EVENT_DISP_RDMA2_EOF:
+	case CMDQ_EVENT_DISP_RDMA0_UNDERRUN:
+	case CMDQ_EVENT_DISP_RDMA1_UNDERRUN:
+	case CMDQ_EVENT_DISP_RDMA2_UNDERRUN:
+		module = "DISP_RDMA";
+		break;
+	case CMDQ_EVENT_DISP_WDMA0_SOF:
+	case CMDQ_EVENT_DISP_WDMA1_SOF:
+	case CMDQ_EVENT_DISP_WDMA0_EOF:
+	case CMDQ_EVENT_DISP_WDMA1_EOF:
+		module = "DISP_WDMA";
+		break;
+	case CMDQ_EVENT_DISP_OVL0_SOF:
+	case CMDQ_EVENT_DISP_OVL1_SOF:
+	case CMDQ_EVENT_DISP_OVL0_EOF:
+	case CMDQ_EVENT_DISP_OVL1_EOF:
+		module = "DISP_OVL";
+		break;
+	case CMDQ_EVENT_MUTEX0_STREAM_EOF ... CMDQ_EVENT_MUTEX4_STREAM_EOF:
+		module = "DISP";
+		break;
+	default:
+		module = "CMDQ";
+		break;
+	}
+
+	return module;
+}
+
+static u64 cmdq_scenario_get_flag(struct cmdq *cqctx,
+				  enum cmdq_scenario scenario)
+{
+	struct device *dev = cqctx->dev;
+	u64 flag;
+
+	switch (scenario) {
+	case CMDQ_SCENARIO_PRIMARY_DISP:
+		flag = ((1LL << CMDQ_ENG_DISP_OVL0) |
+			(1LL << CMDQ_ENG_DISP_COLOR0) |
+			(1LL << CMDQ_ENG_DISP_AAL) |
+			(1LL << CMDQ_ENG_DISP_RDMA0) |
+			(1LL << CMDQ_ENG_DISP_UFOE) |
+			(1LL << CMDQ_ENG_DISP_DSI0_CMD));
+		break;
+	case CMDQ_SCENARIO_SUB_DISP:
+		flag = ((1LL << CMDQ_ENG_DISP_OVL1) |
+			(1LL << CMDQ_ENG_DISP_COLOR1) |
+			(1LL << CMDQ_ENG_DISP_GAMMA) |
+			(1LL << CMDQ_ENG_DISP_RDMA1) |
+			(1LL << CMDQ_ENG_DISP_DSI1_CMD));
+		break;
+	default:
+		dev_err(dev, "unknown scenario type %d\n", scenario);
+		flag = 0LL;
+		break;
+	}
+
+	return flag;
+}
+
+static u32 cmdq_thread_get_cookie(void __iomem *gce_base_va, int tid)
+{
+	return readl(gce_base_va + CMDQ_THR_EXEC_CNT_OFFSET +
+		     CMDQ_THR_SHIFT * tid) & CMDQ_COOKIE_MASK;
+}
+
+static int cmdq_cmd_buf_pool_init(struct cmdq *cqctx)
+{
+	struct device *dev = cqctx->dev;
+	int i;
+	int ret = 0;
+	struct cmdq_cmd_buf *buf;
+
+	for (i = 0; i < ARRAY_SIZE(cqctx->cmd_buf_pool); i++) {
+		buf = &cqctx->cmd_buf_pool[i];
+		buf->va = dma_alloc_coherent(dev, CMDQ_CMD_BUF_POOL_BUF_SIZE,
+					     &buf->pa, GFP_KERNEL);
+		if (!buf->va) {
+			dev_err(dev, "failed to alloc cmdq_cmd_buf\n");
+			ret = -ENOMEM;
+			goto fail_alloc;
+		}
+	}
+
+	return 0;
+
+fail_alloc:
+	for (i -= 1; i >= 0 ; i--) {
+		buf = &cqctx->cmd_buf_pool[i];
+		dma_free_coherent(dev, CMDQ_CMD_BUF_POOL_BUF_SIZE, buf->va,
+				  buf->pa);
+	}
+
+	return ret;
+}
+
+static void cmdq_cmd_buf_pool_uninit(struct cmdq *cqctx)
+{
+	struct device *dev = cqctx->dev;
+	int i;
+	struct cmdq_cmd_buf *buf;
+
+	for (i = 0; i < ARRAY_SIZE(cqctx->cmd_buf_pool); i++) {
+		buf = &cqctx->cmd_buf_pool[i];
+		dma_free_coherent(dev, CMDQ_CMD_BUF_POOL_BUF_SIZE, buf->va,
+				  buf->pa);
+		if (atomic_read(&buf->used))
+			dev_err(dev,
+				"cmdq_cmd_buf[%d] va:0x%p still in use\n",
+				i, buf->va);
+	}
+}
+
+static struct cmdq_cmd_buf *cmdq_cmd_buf_pool_get(struct cmdq *cqctx)
+{
+	int i;
+	struct cmdq_cmd_buf *buf;
+
+	for (i = 0; i < ARRAY_SIZE(cqctx->cmd_buf_pool); i++) {
+		buf = &cqctx->cmd_buf_pool[i];
+		if (!atomic_cmpxchg(&buf->used, 0, 1))
+			break;
+	}
+
+	if (i >= ARRAY_SIZE(cqctx->cmd_buf_pool))
+		buf = NULL;
+
+	return buf;
+}
+
+static void cmdq_cmd_buf_pool_put(struct cmdq_cmd_buf *buf)
+{
+	atomic_set(&buf->used, 0);
+}
+
+static int cmdq_subsys_from_phys_addr(struct cmdq *cqctx, u32 cmdq_phys_addr)
+{
+	u32 base_addr = cmdq_phys_addr >> 16;
+	int subsys = cmdq_subsys_base_addr_to_id(base_addr);
+
+	if (subsys < 0)
+		dev_err(cqctx->dev,
+			"unknown subsys: error=%d, phys=0x%08x\n",
+			subsys, cmdq_phys_addr);
+
+	return subsys;
+}
+
+/*
+ * It's a kmemcache creator for cmdq_task to initialize variables
+ * without command buffer.
+ */
+static void cmdq_task_ctor(void *param)
+{
+	struct cmdq_task *task = param;
+
+	memset(task, 0, sizeof(*task));
+	INIT_LIST_HEAD(&task->list_entry);
+	task->task_state = TASK_STATE_IDLE;
+	task->thread = CMDQ_INVALID_THREAD;
+}
+
+static void cmdq_task_free_command_buffer(struct cmdq_task *task)
+{
+	struct cmdq *cqctx = task->cqctx;
+	struct device *dev = cqctx->dev;
+
+	if (!task->va_base)
+		return;
+
+	if (task->cmd_buf)
+		cmdq_cmd_buf_pool_put(task->cmd_buf);
+	else
+		dma_free_coherent(dev, task->buf_size, task->va_base,
+				  task->mva_base);
+
+	task->va_base = NULL;
+	task->mva_base = 0;
+	task->buf_size = 0;
+	task->command_size = 0;
+	task->num_cmd = 0;
+	task->cmd_buf = NULL;
+}
+
+/*
+ * Ensure size of command buffer in the given cmdq_task.
+ * Existing buffer data will be copied to new buffer.
+ * This buffer is guaranteed to be physically continuous.
+ * returns -ENOMEM if cannot allocate new buffer
+ */
+static int cmdq_task_realloc_command_buffer(struct cmdq_task *task, u32 size)
+{
+	struct cmdq *cqctx = task->cqctx;
+	struct device *dev = cqctx->dev;
+	void *new_buf = NULL;
+	dma_addr_t new_mva_base;
+	u32 cmd_size;
+	u32 num_cmd;
+	struct cmdq_cmd_buf *cmd_buf = NULL;
+
+	if (task->va_base && task->buf_size >= size)
+		return 0;
+
+	/* try command pool */
+	if (size <= CMDQ_CMD_BUF_POOL_BUF_SIZE)
+		cmd_buf = cmdq_cmd_buf_pool_get(cqctx);
+	if (cmd_buf) {
+		new_buf = cmd_buf->va;
+		new_mva_base = cmd_buf->pa;
+		memset(new_buf, 0, CMDQ_CMD_BUF_POOL_BUF_SIZE);
+	}
+
+	/* if failed, try reclaim */
+	if (!new_buf)
+		new_buf = dma_alloc_coherent(dev, size, &new_mva_base,
+					     GFP_KERNEL);
+
+	if (!new_buf) {
+		dev_err(dev, "realloc cmd buffer of size %d failed\n", size);
+		return -ENOMEM;
+	}
+
+	/* copy and release old buffer */
+	if (task->va_base)
+		memcpy(new_buf, task->va_base, task->buf_size);
+
+	/*
+	 * we should keep track of num_cmd and cmd_size
+	 * since they are cleared in free command buffer
+	 */
+	num_cmd = task->num_cmd;
+	cmd_size = task->command_size;
+	cmdq_task_free_command_buffer(task);
+
+	/* attach the new buffer */
+	task->va_base = new_buf;
+	task->mva_base = new_mva_base;
+	task->buf_size = cmd_buf ? CMDQ_CMD_BUF_POOL_BUF_SIZE : size;
+	task->num_cmd = num_cmd;
+	task->command_size = cmd_size;
+	task->cmd_buf = cmd_buf;
+
+	return 0;
+}
+
+/* allocate and initialize struct cmdq_task and its command buffer */
+static struct cmdq_task *cmdq_task_create(struct cmdq *cqctx)
+{
+	struct device *dev = cqctx->dev;
+	struct cmdq_task *task;
+	int status;
+
+	task = kmem_cache_alloc(cqctx->task_cache, GFP_KERNEL);
+	task->cqctx = cqctx;
+	status = cmdq_task_realloc_command_buffer(
+			task, CMDQ_INITIAL_CMD_BLOCK_SIZE);
+	if (status < 0) {
+		dev_err(dev, "allocate command buffer failed\n");
+		kmem_cache_free(cqctx->task_cache, task);
+		task = NULL;
+	}
+	return task;
+}
+
+static long cmdq_dev_get_pa(struct device_node *node, int index)
+{
+	struct resource res;
+
+	if (of_address_to_resource(node, index, &res))
+		return 0;
+
+	return res.start;
+}
+
+static int cmdq_dev_init(struct platform_device *pdev, struct cmdq *cqctx)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct resource *res;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	cqctx->base_va = devm_ioremap_resource(dev, res);
+	if (IS_ERR(cqctx->base_va)) {
+		dev_err(dev, "failed to ioremap gce\n");
+		return PTR_ERR(cqctx->base_va);
+	}
+
+	cqctx->base_pa = cmdq_dev_get_pa(node, 0);
+	if (!cqctx->base_pa) {
+		dev_err(dev, "failed to get gce pa\n");
+		return -EINVAL;
+	}
+
+	cqctx->irq = irq_of_parse_and_map(node, 0);
+	if (!cqctx->irq) {
+		dev_err(dev, "failed to get irq\n");
+		return -EINVAL;
+	}
+
+	dev_dbg(dev, "cmdq device: addr:0x%p, pa:0x%lx, va:0x%p, irq:%d\n",
+		dev, cqctx->base_pa, cqctx->base_va, cqctx->irq);
+	return 0;
+}
+
+static void cmdq_task_release_unlocked(struct cmdq_task *task)
+{
+	struct cmdq *cqctx = task->cqctx;
+
+	/* This func should be inside cqctx->task_mutex mutex */
+	lockdep_assert_held(&cqctx->task_mutex);
+
+	task->task_state = TASK_STATE_IDLE;
+	task->thread = CMDQ_INVALID_THREAD;
+
+	cmdq_task_free_command_buffer(task);
+
+	/* remove from active/waiting list */
+	list_del_init(&task->list_entry);
+	/* insert into free list. Currently we don't shrink free list. */
+	list_add_tail(&task->list_entry, &cqctx->task_free_list);
+}
+
+static void cmdq_task_release_internal(struct cmdq_task *task)
+{
+	struct cmdq *cqctx = task->cqctx;
+
+	mutex_lock(&cqctx->task_mutex);
+	cmdq_task_release_unlocked(task);
+	mutex_unlock(&cqctx->task_mutex);
+}
+
+static struct cmdq_task *cmdq_core_find_free_task(struct cmdq *cqctx)
+{
+	struct cmdq_task *task;
+
+	mutex_lock(&cqctx->task_mutex);
+
+	/*
+	 * Pick from free list first;
+	 * create one if there is no free entry.
+	 */
+	if (list_empty(&cqctx->task_free_list)) {
+		task = cmdq_task_create(cqctx);
+	} else {
+		task = list_first_entry(&cqctx->task_free_list,
+					struct cmdq_task, list_entry);
+		/* remove from free list */
+		list_del_init(&task->list_entry);
+	}
+
+	mutex_unlock(&cqctx->task_mutex);
+
+	return task;
+}
+
+static void cmdq_thread_reorder_task_array(struct cmdq_thread *thread,
+					   int prev_id)
+{
+	int i, j;
+	int next_id, search_id;
+	int reorder_count = 0;
+	struct cmdq_task *task;
+
+	next_id = prev_id + 1;
+	for (i = 1; i < (CMDQ_MAX_TASK_IN_THREAD - 1); i++, next_id++) {
+		if (next_id >= CMDQ_MAX_TASK_IN_THREAD)
+			next_id = 0;
+
+		if (thread->cur_task[next_id])
+			break;
+
+		search_id = next_id + 1;
+		for (j = (i + 1); j < CMDQ_MAX_TASK_IN_THREAD;
+		     j++, search_id++) {
+			if (search_id >= CMDQ_MAX_TASK_IN_THREAD)
+				search_id = 0;
+
+			if (thread->cur_task[search_id]) {
+				thread->cur_task[next_id] =
+					thread->cur_task[search_id];
+				thread->cur_task[search_id] = NULL;
+				if ((j - i) > reorder_count)
+					reorder_count = j - i;
+
+				break;
+			}
+		}
+
+		task = thread->cur_task[next_id];
+		if ((task->va_base[task->num_cmd - 1] == CMDQ_JUMP_BY_OFFSET) &&
+		    (task->va_base[task->num_cmd - 2] == CMDQ_JUMP_TO_BEGIN)) {
+			/* We reached the last task */
+			break;
+		}
+	}
+
+	thread->next_cookie -= reorder_count;
+}
+
+static int cmdq_core_sync_command(struct cmdq_task *task,
+				  struct cmdq_command *cmd_desc)
+{
+	struct cmdq *cqctx = task->cqctx;
+	struct device *dev = cqctx->dev;
+	int status = 0;
+	u32 size;
+
+	size = task->command_size + CMDQ_INST_SIZE;
+	status = cmdq_task_realloc_command_buffer(task, size);
+	if (status < 0) {
+		dev_err(dev, "failed to realloc command buffer\n");
+		dev_err(dev, "task=0x%p, request size=%d\n", task, size);
+		return status;
+	}
+
+	/* copy the commands to our DMA buffer */
+	memcpy(task->va_base, cmd_desc->va_base, cmd_desc->block_size);
+
+	/* re-adjust num_cmd according to command_size */
+	task->num_cmd = task->command_size / sizeof(task->va_base[0]);
+
+	return status;
+}
+
+static struct cmdq_task *cmdq_core_acquire_task(struct cmdq_command *cmd_desc,
+						struct cmdq_task_cb *cb)
+{
+	struct cmdq *cqctx = cmd_desc->cqctx;
+	struct device *dev = cqctx->dev;
+	int status;
+	struct cmdq_task *task;
+
+	task = cmdq_core_find_free_task(cqctx);
+	if (!task) {
+		dev_err(dev, "can't acquire task info\n");
+		return NULL;
+	}
+
+	/* initialize field values */
+	task->scenario = cmd_desc->scenario;
+	task->priority = cmd_desc->priority;
+	task->engine_flag = cmd_desc->engine_flag;
+	task->task_state = TASK_STATE_WAITING;
+	task->reorder = 0;
+	task->thread = CMDQ_INVALID_THREAD;
+	task->irq_flag = 0x0;
+	memcpy(&task->cb, cb, sizeof(*cb));
+	task->command_size = cmd_desc->block_size;
+
+	/* store caller info for debug */
+	if (current) {
+		task->caller_pid = current->pid;
+		memcpy(task->caller_name, current->comm, sizeof(current->comm));
+	}
+
+	status = cmdq_core_sync_command(task, cmd_desc);
+	if (status < 0) {
+		dev_err(dev, "fail to sync command\n");
+		cmdq_task_release_internal(task);
+		return NULL;
+	}
+
+	/* insert into waiting list to process */
+	mutex_lock(&cqctx->task_mutex);
+	if (task) {
+		struct list_head *in_item = &cqctx->task_wait_list;
+		struct cmdq_task *wait_task = NULL;
+		struct list_head *p = NULL;
+
+		task->submit = sched_clock();
+
+		/*
+		 * add to waiting list, keep it sorted by priority
+		 * so that we add high-priority tasks first.
+		 */
+		list_for_each(p, &cqctx->task_wait_list) {
+			wait_task = list_entry(p, struct cmdq_task, list_entry);
+			/*
+			 * keep the list sorted.
+			 * higher priority tasks are inserted
+			 * in front of the queue
+			 */
+			if (wait_task->priority < task->priority)
+				break;
+
+			in_item = p;
+		}
+
+		list_add(&task->list_entry, in_item);
+	}
+	mutex_unlock(&cqctx->task_mutex);
+
+	return task;
+}
+
+static int cmdq_clk_enable(struct cmdq *cqctx)
+{
+	struct device *dev = cqctx->dev;
+	int ret = 0;
+
+	if (!cqctx->thread_usage) {
+		ret = clk_prepare_enable(cqctx->clock);
+		if (ret) {
+			dev_err(dev, "prepare and enable clk:%s fail\n",
+				CMDQ_CLK_NAME);
+			return ret;
+		}
+		cmdq_event_reset(cqctx);
+	}
+	cqctx->thread_usage++;
+
+	return ret;
+}
+
+static void cmdq_clk_disable(struct cmdq *cqctx)
+{
+	cqctx->thread_usage--;
+	if (cqctx->thread_usage <= 0)
+		clk_disable_unprepare(cqctx->clock);
+}
+
+static int cmdq_core_find_free_thread(struct cmdq *cqctx, u32 scenario)
+{
+	struct device *dev = cqctx->dev;
+	struct cmdq_thread *thread;
+	int tid;
+	u32 next_cookie;
+
+	thread = cqctx->thread;
+	tid = cmdq_scenario_get_thread(scenario);
+
+	/*
+	 * Currently, we only support disp,
+	 * so it's error if tid is CMDQ_INVALID_THREAD.
+	 */
+	if (tid == CMDQ_INVALID_THREAD) {
+		dev_err(dev, "got CMDQ_INVALID_THREAD!!!\n");
+		return tid;
+	}
+
+	/*
+	 * make sure the found thread has enough space for the task;
+	 * cmdq_thread->cur_task has size limitation.
+	 */
+	if (thread[tid].task_count >= CMDQ_MAX_TASK_IN_THREAD)
+		tid = CMDQ_INVALID_THREAD;
+
+	next_cookie = thread[tid].next_cookie % CMDQ_MAX_TASK_IN_THREAD;
+	if (thread[tid].cur_task[next_cookie])
+		tid = CMDQ_INVALID_THREAD;
+
+	return tid;
+}
+
+static int cmdq_core_acquire_thread(struct cmdq *cqctx, u32 scenario)
+{
+	int tid;
+
+	mutex_lock(&cqctx->clock_mutex);
+	tid = cmdq_core_find_free_thread(cqctx, scenario);
+	if (tid != CMDQ_INVALID_THREAD)
+		cmdq_clk_enable(cqctx);
+	mutex_unlock(&cqctx->clock_mutex);
+
+	return tid;
+}
+
+static void cmdq_core_release_thread(struct cmdq *cqctx, int tid)
+{
+	if (unlikely(tid == CMDQ_INVALID_THREAD)) {
+		WARN_ON(1);
+		return;
+	}
+
+	mutex_lock(&cqctx->clock_mutex);
+	cmdq_clk_disable(cqctx);
+	mutex_unlock(&cqctx->clock_mutex);
+}
+
+static void cmdq_task_remove_thread(struct cmdq_task *task)
+{
+	int tid = task->thread;
+
+	task->thread = CMDQ_INVALID_THREAD;
+	cmdq_core_release_thread(task->cqctx, tid);
+}
+
+static int cmdq_thread_suspend(struct cmdq *cqctx, int tid)
+{
+	struct device *dev = cqctx->dev;
+	void __iomem *gce_base_va = cqctx->base_va;
+	u32 enabled;
+	u32 status;
+
+	/* write suspend bit */
+	writel(CMDQ_THR_SUSPEND,
+	       gce_base_va + CMDQ_THR_SUSPEND_TASK_OFFSET +
+	       CMDQ_THR_SHIFT * tid);
+
+	/* If already disabled, treat as suspended successful. */
+	enabled = readl(gce_base_va + CMDQ_THR_ENABLE_TASK_OFFSET +
+			CMDQ_THR_SHIFT * tid);
+	if (!(enabled & CMDQ_THR_ENABLED))
+		return 0;
+
+	/* poll suspended status */
+	if (readl_poll_timeout_atomic(gce_base_va +
+				      CMDQ_THR_CURR_STATUS_OFFSET +
+				      CMDQ_THR_SHIFT * tid,
+				      status,
+				      status & CMDQ_THR_STATUS_SUSPENDED,
+				      0, 10)) {
+		dev_err(dev, "Suspend HW thread %d failed\n", tid);
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+static void cmdq_thread_resume(struct cmdq *cqctx, int tid)
+{
+	void __iomem *gce_base_va = cqctx->base_va;
+
+	writel(CMDQ_THR_RESUME,
+	       gce_base_va + CMDQ_THR_SUSPEND_TASK_OFFSET +
+	       CMDQ_THR_SHIFT * tid);
+}
+
+static int cmdq_thread_reset(struct cmdq *cqctx, int tid)
+{
+	struct device *dev = cqctx->dev;
+	void __iomem *gce_base_va = cqctx->base_va;
+	u32 warm_reset;
+
+	writel(CMDQ_THR_WARM_RESET,
+	       gce_base_va + CMDQ_THR_WARM_RESET_OFFSET +
+	       CMDQ_THR_SHIFT * tid);
+
+	if (readl_poll_timeout_atomic(gce_base_va + CMDQ_THR_WARM_RESET_OFFSET +
+				      CMDQ_THR_SHIFT * tid,
+				      warm_reset,
+				      !(warm_reset & CMDQ_THR_WARM_RESET),
+				      0, 10)) {
+		dev_err(dev, "Reset HW thread %d failed\n", tid);
+		return -EFAULT;
+	}
+
+	writel(CMDQ_THR_SLOT_CYCLES, gce_base_va + CMDQ_THR_SLOT_CYCLES_OFFSET);
+	return 0;
+}
+
+static int cmdq_thread_disable(struct cmdq *cqctx, int tid)
+{
+	void __iomem *gce_base_va = cqctx->base_va;
+
+	cmdq_thread_reset(cqctx, tid);
+	writel(CMDQ_THR_DISABLED,
+	       gce_base_va + CMDQ_THR_ENABLE_TASK_OFFSET +
+	       CMDQ_THR_SHIFT * tid);
+	return 0;
+}
+
+static u32 *cmdq_task_get_pc_and_inst(const struct cmdq_task *task, int tid,
+				      u32 insts[2])
+{
+	struct cmdq *cqctx;
+	void __iomem *gce_base_va;
+	unsigned long pc_pa;
+	u8 *pc_va;
+	u8 *cmd_end;
+
+	memset(insts, 0, sizeof(u32) * 2);
+
+	if (!task ||
+	    !task->va_base ||
+	    tid == CMDQ_INVALID_THREAD) {
+		pr_err("cmdq get pc failed since invalid param, task 0x%p, task->va_base:0x%p, thread:%d\n",
+		       task, task->va_base, tid);
+		return NULL;
+	}
+
+	cqctx = task->cqctx;
+	gce_base_va = cqctx->base_va;
+
+	pc_pa = (unsigned long)readl(gce_base_va + CMDQ_THR_CURR_ADDR_OFFSET +
+				     CMDQ_THR_SHIFT * tid);
+	pc_va = (u8 *)task->va_base + (pc_pa - task->mva_base);
+	cmd_end = (u8 *)(task->va_base + task->num_cmd - 1);
+
+	if (((u8 *)task->va_base <= pc_va) && (pc_va <= cmd_end)) {
+		if (pc_va < cmd_end) {
+			/*
+			 * If PC points to start of CMD,
+			 * - 8 causes access violation
+			 */
+			insts[0] = readl(pc_va);
+			insts[1] = readl(pc_va + 4);
+		} else {
+			insts[0] = readl(pc_va - 8);
+			insts[1] = readl(pc_va - 4);
+		}
+	} else {
+		/* invalid PC address */
+		return NULL;
+	}
+
+	return (u32 *)pc_va;
+}
+
+static const char *cmdq_core_parse_module_from_subsys(u32 arg_a)
+{
+	int id = (arg_a & CMDQ_ARG_A_SUBSYS_MASK) >> CMDQ_SUBSYS_SHIFT;
+	u32 base_addr = cmdq_subsys_id_to_base_addr(id);
+	const char *module = cmdq_subsys_base_addr_to_grp_name(base_addr);
+
+	return module ? module : "CMDQ";
+}
+
+static const char *cmdq_core_parse_op(u32 op_code)
+{
+	switch (op_code) {
+	case CMDQ_CODE_WRITE:
+		return "WRIT";
+	case CMDQ_CODE_WFE:
+		return "SYNC";
+	case CMDQ_CODE_MOVE:
+		return "MASK";
+	case CMDQ_CODE_JUMP:
+		return "JUMP";
+	case CMDQ_CODE_EOC:
+		return "MARK";
+	}
+	return NULL;
+}
+
+static bool cmdq_core_verfiy_command_end(struct device *dev, u32 *va_base,
+					 u32 cmd_size, u32 num_cmd,
+					 const char *tag)
+{
+	/* make sure we have sufficient command to parse */
+	if (!va_base || cmd_size < (2 * CMDQ_INST_SIZE)) {
+		dev_err(dev, "[%s][CMD] doesn't have sufficient commands\n",
+			tag);
+		return false;
+	}
+
+	/* make sure EOC enable IRQ */
+	if ((va_base[num_cmd - 4] & 0x1) != 1) {
+		dev_err(dev, "[%s][CMD] doesn't enable IRQ (%08x:%08x)\n",
+			tag, va_base[num_cmd - 4], va_base[num_cmd - 3]);
+		return false;
+	}
+
+	/* make sure the command is ended by EOC + JUMP */
+	if ((va_base[num_cmd - 3] >> CMDQ_OP_CODE_SHIFT) != CMDQ_CODE_EOC ||
+	    (va_base[num_cmd - 1] >> CMDQ_OP_CODE_SHIFT) != CMDQ_CODE_JUMP) {
+		dev_err(dev,
+			"[%s][CMD] doesn't end in EOC+JUMP (%08x:%08x, %08x:%08x)\n",
+			tag, va_base[num_cmd - 4], va_base[num_cmd - 3],
+			va_base[num_cmd - 2], va_base[num_cmd - 1]);
+		return false;
+	}
+
+	return true;
+}
+
+static bool cmdq_core_verfiy_desc_command_end(struct cmdq_command *cmd_desc)
+{
+	struct device *dev;
+	u32 num_cmd;
+	bool valid;
+
+	dev = cmd_desc->cqctx->dev;
+	num_cmd = cmd_desc->block_size / sizeof(u32);
+	valid = cmdq_core_verfiy_command_end(dev, cmd_desc->va_base,
+					     cmd_desc->block_size, num_cmd,
+					     "DESC");
+
+	if (!valid)
+		dev_err(dev, "invalid command desc 0x%p\n", cmd_desc);
+
+	return valid;
+}
+
+static bool cmdq_core_verfiy_task_command_end(const struct cmdq_task *task)
+{
+	struct device *dev;
+	bool valid;
+
+	dev = task->cqctx->dev;
+	valid = cmdq_core_verfiy_command_end(dev, task->va_base,
+					     task->command_size, task->num_cmd,
+					     "TASK");
+
+	if (!valid)
+		dev_err(dev, "invalid task 0x%p\n", task);
+
+	return valid;
+}
+
+static void cmdq_core_parse_error(struct cmdq_task *task, int tid,
+				  const char **module_name, int *flag,
+				  u32 *inst_a, u32 *inst_b)
+{
+	int irq_flag = task->irq_flag;
+	u32 insts[2] = { 0 };
+	const char *module;
+
+	/*
+	 * other cases, use instruction to judge
+	 * because scenario / HW flag are not sufficient
+	 */
+	if (cmdq_task_get_pc_and_inst(task, tid, insts)) {
+		u32 op, arg_a, arg_b;
+
+		op = insts[1] >> CMDQ_OP_CODE_SHIFT;
+		arg_a = insts[1] & CMDQ_ARG_A_MASK;
+		arg_b = insts[0];
+
+		switch (op) {
+		case CMDQ_CODE_WRITE:
+			module = cmdq_core_parse_module_from_subsys(arg_a);
+			break;
+		case CMDQ_CODE_WFE:
+			/* arg_a is the event id */
+			module = cmdq_event_get_module((enum cmdq_event)arg_a);
+			break;
+		case CMDQ_CODE_MOVE:
+		case CMDQ_CODE_JUMP:
+		case CMDQ_CODE_EOC:
+		default:
+			module = "CMDQ";
+			break;
+		}
+	} else {
+		module = "CMDQ";
+	}
+
+	/* fill output parameter */
+	*module_name = module;
+	*flag = irq_flag;
+	*inst_a = insts[1];
+	*inst_b = insts[0];
+}
+
+static int cmdq_thread_insert_task_by_cookie(struct cmdq_thread *thread,
+					     struct cmdq_task *task,
+					     int cookie, bool reset)
+{
+	if (!task || !thread) {
+		pr_err("%s: invalid param, task:0x%p, thread:0x%p, cookie:%d, reset:%d\n",
+		       __func__, task, thread, cookie, reset);
+		return -EFAULT;
+	}
+
+	if (reset) {
+		thread->wait_cookie = cookie;
+		thread->next_cookie = cookie + 1;
+		if (thread->next_cookie > CMDQ_MAX_COOKIE_VALUE)
+			thread->next_cookie = 0;
+
+		/*
+		 * task_count must start from 0.
+		 * if we are the first task, set to 1.
+		 */
+		thread->task_count = 1;
+	} else {
+		thread->next_cookie++;
+		if (thread->next_cookie > CMDQ_MAX_COOKIE_VALUE)
+			thread->next_cookie = 0;
+
+		thread->task_count++;
+	}
+
+	/* genernal part */
+	thread->cur_task[cookie % CMDQ_MAX_TASK_IN_THREAD] = task;
+
+	return 0;
+}
+
+static int cmdq_thread_remove_task_by_index(struct cmdq_thread *thread,
+					    int index,
+					    enum cmdq_task_state new_state)
+{
+	struct cmdq_task *task;
+	int tid;
+	struct device *dev;
+
+	if ((!thread) || (index >= CMDQ_MAX_TASK_IN_THREAD) ||
+	    (index < 0)) {
+		pr_err("%s: invalid param. thread:0x%p, task:%d, new_state:%d\n",
+		       __func__, thread, index, new_state);
+		return -EINVAL;
+	}
+
+	task = thread->cur_task[index];
+	if (!task) {
+		pr_err("%s: remove fail, task:%d on thread:0x%p is NULL\n",
+		       __func__, index, thread);
+		return -EINVAL;
+	}
+	dev = task->cqctx->dev;
+
+	/*
+	 * note timing to switch a task to done_status(_ERROR, _KILLED, _DONE)
+	 * is aligned with thread's taskcount change
+	 * check task status to prevent double clean-up thread's taskcount
+	 */
+	if (task->task_state != TASK_STATE_BUSY) {
+		dev_err(dev, "remove task failed\n");
+		dev_err(dev, "state:%d. thread:0x%p, task:%d, new_state:%d\n",
+			task->task_state, thread, index, new_state);
+		return -EINVAL;
+	}
+
+	tid = task->thread;
+	task->task_state = new_state;
+	thread->cur_task[index] = NULL;
+	thread->task_count--;
+
+	if ((int)thread->task_count < 0) {
+		dev_err(dev, "task count < 0 after %s\n", __func__);
+		dev_err(dev, "thread:%d, index:%d\n", tid, index);
+		/* for error handle */
+		thread->task_count = 0;
+	}
+
+	return 0;
+}
+
+static int cmdq_thread_force_remove_task(struct cmdq_task *task, int tid)
+{
+	struct cmdq *cqctx = task->cqctx;
+	struct cmdq_thread *thread = &cqctx->thread[tid];
+	void __iomem *gce_base_va = cqctx->base_va;
+	int status;
+	int cookie;
+	struct cmdq_task *exec_task;
+
+	status = cmdq_thread_suspend(cqctx, tid);
+
+	writel(CMDQ_THR_NO_TIMEOUT,
+	       gce_base_va + CMDQ_THR_INST_CYCLES_OFFSET +
+	       CMDQ_THR_SHIFT * tid);
+
+	/* The cookie of the task currently being processed */
+	cookie = cmdq_thread_get_cookie(gce_base_va, tid) + 1;
+
+	exec_task = thread->cur_task[cookie % CMDQ_MAX_TASK_IN_THREAD];
+	if (exec_task && exec_task == task) {
+		dma_addr_t eoc_pa = task->mva_base + task->command_size - 16;
+
+		/* The task is executed now, set the PC to EOC for bypass */
+		writel(eoc_pa,
+		       gce_base_va + CMDQ_THR_CURR_ADDR_OFFSET +
+		       CMDQ_THR_SHIFT * tid);
+
+		thread->cur_task[cookie % CMDQ_MAX_TASK_IN_THREAD] = NULL;
+		task->task_state = TASK_STATE_KILLED;
+	} else {
+		int i, j;
+
+		j = thread->task_count;
+		for (i = (cookie % CMDQ_MAX_TASK_IN_THREAD); j > 0; j--, i++) {
+			i %= CMDQ_MAX_TASK_IN_THREAD;
+
+			exec_task = thread->cur_task[i];
+			if (!exec_task)
+				continue;
+
+			if ((exec_task->va_base[exec_task->num_cmd - 1] ==
+			     CMDQ_JUMP_BY_OFFSET) &&
+			    (exec_task->va_base[exec_task->num_cmd - 2] ==
+			     CMDQ_JUMP_TO_BEGIN)) {
+				/* reached the last task */
+				break;
+			}
+
+			if (exec_task->va_base[exec_task->num_cmd - 2] ==
+			    task->mva_base) {
+				/* fake EOC command */
+				exec_task->va_base[exec_task->num_cmd - 2] =
+					CMDQ_EOC_IRQ_EN;
+				exec_task->va_base[exec_task->num_cmd - 1] =
+					CMDQ_CODE_EOC << CMDQ_OP_CODE_SHIFT;
+
+				/* bypass the task */
+				exec_task->va_base[exec_task->num_cmd] =
+					task->va_base[task->num_cmd - 2];
+				exec_task->va_base[exec_task->num_cmd + 1] =
+					task->va_base[task->num_cmd - 1];
+
+				i = (i + 1) % CMDQ_MAX_TASK_IN_THREAD;
+
+				thread->cur_task[i] = NULL;
+				task->task_state = TASK_STATE_KILLED;
+				status = 0;
+				break;
+			}
+		}
+	}
+
+	return status;
+}
+
+static struct cmdq_task *cmdq_thread_search_task_by_pc(
+		const struct cmdq_thread *thread, u32 pc)
+{
+	struct cmdq_task *task;
+	int i;
+
+	for (i = 0; i < CMDQ_MAX_TASK_IN_THREAD; i++) {
+		task = thread->cur_task[i];
+		if (task &&
+		    pc >= task->mva_base &&
+		    pc <= task->mva_base + task->command_size)
+			break;
+	}
+
+	return task;
+}
+
+/*
+ * Re-fetch thread's command buffer
+ * Use Case:
+ *     If SW modifies command buffer content after SW configed commands to GCE,
+ *     SW should notify GCE to re-fetch commands in order to
+ *     prevent inconsistent command buffer content between DRAM and GCE's SRAM.
+ */
+static void cmdq_core_invalidate_hw_fetched_buffer(void __iomem *gce_base_va,
+						   int tid)
+{
+	void __iomem *pc_va;
+	u32 pc;
+
+	/*
+	 * Setting HW thread PC will invoke that
+	 * GCE (CMDQ HW) gives up fetched command buffer,
+	 * and fetch command from DRAM to GCE's SRAM again.
+	 */
+	pc_va = gce_base_va + CMDQ_THR_CURR_ADDR_OFFSET + CMDQ_THR_SHIFT * tid;
+	pc = readl(pc_va);
+	writel(pc, pc_va);
+}
+
+static int cmdq_task_insert_into_thread(struct cmdq_task *last,
+					struct cmdq_task *task,
+					int tid, int loop)
+{
+	struct cmdq *cqctx = task->cqctx;
+	struct device *dev = cqctx->dev;
+	void __iomem *gce_base_va = cqctx->base_va;
+	int status = 0;
+	struct cmdq_thread *thread;
+	struct cmdq_task *prev_task;
+	int index;
+	int prev;
+	int cookie;
+
+	thread = &cqctx->thread[tid];
+	cookie = thread->next_cookie;
+
+	/*
+	 * Traverse forward to adjust tasks' order
+	 * according to their priorities.
+	 */
+	for (prev = (cookie % CMDQ_MAX_TASK_IN_THREAD); loop > 0; loop--) {
+		index = prev;
+		if (index < 0)
+			index = CMDQ_MAX_TASK_IN_THREAD - 1;
+
+		prev = index - 1;
+		if (prev < 0)
+			prev = CMDQ_MAX_TASK_IN_THREAD - 1;
+
+		prev_task = thread->cur_task[prev];
+
+		/* maybe the job is killed, search a new one */
+		for (; !prev_task && loop > 1; loop--) {
+			dev_err(dev,
+				"prev_task is NULL, prev:%d, loop:%d, index:%d\n",
+				prev, loop, index);
+
+			prev--;
+			if (prev < 0)
+				prev = CMDQ_MAX_TASK_IN_THREAD - 1;
+
+			prev_task = thread->cur_task[prev];
+		}
+
+		if (!prev_task) {
+			dev_err(dev,
+				"invalid task state for reorder %d %d\n",
+				index, loop);
+			status = -EFAULT;
+			break;
+		}
+
+		/* insert this task */
+		if (loop <= 1) {
+			thread->cur_task[index] = task;
+			/* Jump: Absolute */
+			prev_task->va_base[prev_task->num_cmd - 1] =
+					CMDQ_JUMP_BY_PA;
+			/* Jump to here */
+			prev_task->va_base[prev_task->num_cmd - 2] =
+					task->mva_base;
+
+			/* re-fetch command buffer again. */
+			cmdq_core_invalidate_hw_fetched_buffer(
+					gce_base_va, tid);
+
+			break;
+		}
+
+		if (prev_task->priority < task->priority) {
+			/* new task has higher priority */
+
+			thread->cur_task[index] = prev_task;
+			prev_task->va_base[prev_task->num_cmd - 1] =
+					task->va_base[task->num_cmd - 1];
+			prev_task->va_base[prev_task->num_cmd - 2] =
+					task->va_base[task->num_cmd - 2];
+
+			/* Boot priority for the task */
+			prev_task->priority += CMDQ_MIN_AGE_VALUE;
+			prev_task->reorder++;
+
+			thread->cur_task[prev] = task;
+			/* Jump: Absolute */
+			task->va_base[task->num_cmd - 1] = CMDQ_JUMP_BY_PA;
+			/* Jump to here */
+			task->va_base[task->num_cmd - 2] = prev_task->mva_base;
+
+			/* re-fetch command buffer again. */
+			cmdq_core_invalidate_hw_fetched_buffer(
+					gce_base_va, tid);
+
+			if (last == task)
+				last = prev_task;
+		} else {
+			/* previous task has higher priority */
+
+			thread->cur_task[index] = task;
+			/* Jump: Absolute */
+			prev_task->va_base[prev_task->num_cmd - 1] =
+					CMDQ_JUMP_BY_PA;
+			/* Jump to here */
+			prev_task->va_base[prev_task->num_cmd - 2] =
+					task->mva_base;
+
+			/* re-fetch command buffer again. */
+			cmdq_core_invalidate_hw_fetched_buffer(
+					gce_base_va, tid);
+
+			break;
+		}
+	}
+
+	return status;
+}
+
+static int cmdq_task_exec_async_impl(struct cmdq_task *task, int tid)
+{
+	struct cmdq *cqctx = task->cqctx;
+	struct device *dev = cqctx->dev;
+	void __iomem *gce_base_va = cqctx->base_va;
+	int status;
+	struct cmdq_thread *thread;
+	struct cmdq_task *last_task;
+	unsigned long flags;
+	int loop;
+	int minimum;
+	int cookie;
+	int thread_prio;
+
+	status = 0;
+	thread = &cqctx->thread[tid];
+
+	spin_lock_irqsave(&cqctx->exec_lock, flags);
+
+	/* update task's thread info */
+	task->thread = tid;
+	task->irq_flag = 0;
+	task->task_state = TASK_STATE_BUSY;
+
+	if (thread->task_count <= 0) {
+		bool is_prefetch;
+
+		if (cmdq_thread_reset(cqctx, tid) < 0) {
+			spin_unlock_irqrestore(&cqctx->exec_lock, flags);
+			return -EFAULT;
+		}
+
+		writel(CMDQ_THR_NO_TIMEOUT,
+		       gce_base_va + CMDQ_THR_INST_CYCLES_OFFSET +
+		       CMDQ_THR_SHIFT * tid);
+
+		is_prefetch = cmdq_scenario_is_prefetch(task->scenario);
+		if (is_prefetch) {
+			writel(CMDQ_THR_PREFETCH_EN,
+			       gce_base_va + CMDQ_THR_PREFETCH_OFFSET +
+			       CMDQ_THR_SHIFT * tid);
+		}
+
+		thread_prio = CMDQ_THR_PRIO_DISPLAY_CONFIG;
+		writel(task->mva_base,
+		       gce_base_va + CMDQ_THR_CURR_ADDR_OFFSET +
+		       CMDQ_THR_SHIFT * tid);
+		writel(task->mva_base + task->command_size,
+		       gce_base_va + CMDQ_THR_END_ADDR_OFFSET +
+		       CMDQ_THR_SHIFT * tid);
+		writel(thread_prio & CMDQ_THR_PRIORITY_MASK,
+		       gce_base_va + CMDQ_THR_CFG_OFFSET +
+		       CMDQ_THR_SHIFT * tid);
+
+		writel(CMDQ_THR_IRQ_EN,
+		       gce_base_va + CMDQ_THR_IRQ_ENABLE_OFFSET +
+		       CMDQ_THR_SHIFT * tid);
+
+		minimum = cmdq_thread_get_cookie(gce_base_va, tid);
+		cmdq_thread_insert_task_by_cookie(
+				thread, task, (minimum + 1), true);
+
+		/* verify that we don't corrupt EOC + JUMP pattern */
+		cmdq_core_verfiy_task_command_end(task);
+
+		/* enable HW thread */
+		writel(CMDQ_THR_ENABLED,
+		       gce_base_va + CMDQ_THR_ENABLE_TASK_OFFSET +
+		       CMDQ_THR_SHIFT * tid);
+	} else {
+		unsigned long curr_pa, end_pa;
+
+		status = cmdq_thread_suspend(cqctx, tid);
+		if (status < 0) {
+			spin_unlock_irqrestore(&cqctx->exec_lock, flags);
+			return status;
+		}
+
+		writel(CMDQ_THR_NO_TIMEOUT,
+		       gce_base_va + CMDQ_THR_INST_CYCLES_OFFSET +
+		       CMDQ_THR_SHIFT * tid);
+
+		cookie = thread->next_cookie;
+
+		/*
+		 * Boundary case tested: EOC have been executed,
+		 *                       but JUMP is not executed
+		 * Thread PC: 0x9edc0dd8, End: 0x9edc0de0,
+		 * Curr Cookie: 1, Next Cookie: 2
+		 * PC = END - 8, EOC is executed
+		 * PC = END - 0, All CMDs are executed
+		 */
+
+		curr_pa = (unsigned long)readl(gce_base_va +
+					       CMDQ_THR_CURR_ADDR_OFFSET +
+					       CMDQ_THR_SHIFT * tid);
+		end_pa = (unsigned long)readl(gce_base_va +
+					      CMDQ_THR_END_ADDR_OFFSET +
+					      CMDQ_THR_SHIFT * tid);
+		if ((curr_pa == end_pa - 8) || (curr_pa == end_pa - 0)) {
+			/* set to task directly */
+			writel(task->mva_base,
+			       gce_base_va + CMDQ_THR_CURR_ADDR_OFFSET +
+			       CMDQ_THR_SHIFT * tid);
+			writel(task->mva_base + task->command_size,
+			       gce_base_va + CMDQ_THR_END_ADDR_OFFSET +
+			       CMDQ_THR_SHIFT * tid);
+			thread->cur_task[cookie % CMDQ_MAX_TASK_IN_THREAD] = task;
+			thread->task_count++;
+		} else {
+			/* Current task that shuld be processed */
+			minimum = cmdq_thread_get_cookie(gce_base_va, tid) + 1;
+			if (minimum > CMDQ_MAX_COOKIE_VALUE)
+				minimum = 0;
+
+			/* Calculate loop count to adjust the tasks' order */
+			if (minimum <= cookie)
+				loop = cookie - minimum;
+			else
+				/* Counter wrapped */
+				loop = (CMDQ_MAX_COOKIE_VALUE - minimum + 1) +
+				       cookie;
+
+			if (loop < 0) {
+				dev_err(dev, "reorder fail:\n");
+				dev_err(dev, "  task count=%d\n", loop);
+				dev_err(dev, "  thread=%d\n", tid);
+				dev_err(dev, "  next cookie=%d\n",
+					thread->next_cookie);
+				dev_err(dev, "  (HW) next cookie=%d\n",
+					minimum);
+				dev_err(dev, "  task=0x%p\n", task);
+
+				spin_unlock_irqrestore(&cqctx->exec_lock,
+						       flags);
+				return -EFAULT;
+			}
+
+			if (loop > CMDQ_MAX_TASK_IN_THREAD)
+				loop %= CMDQ_MAX_TASK_IN_THREAD;
+
+			/*
+			 * By default, task is the last task,
+			 * and insert [cookie % CMDQ_MAX_TASK_IN_THREAD]
+			 */
+			last_task = task;	/* Default last task */
+
+			status = cmdq_task_insert_into_thread(
+					last_task, task, tid, loop);
+			if (status < 0) {
+				spin_unlock_irqrestore(
+						&cqctx->exec_lock, flags);
+				dev_err(dev,
+					"invalid task state for reorder.\n");
+				return status;
+			}
+
+			smp_mb(); /* modify jump before enable thread */
+
+			writel(task->mva_base + task->command_size,
+			       gce_base_va + CMDQ_THR_END_ADDR_OFFSET +
+			       CMDQ_THR_SHIFT * tid);
+			thread->task_count++;
+		}
+
+		thread->next_cookie += 1;
+		if (thread->next_cookie > CMDQ_MAX_COOKIE_VALUE)
+			thread->next_cookie = 0;
+
+		/* verify that we don't corrupt EOC + JUMP pattern */
+		cmdq_core_verfiy_task_command_end(task);
+
+		/* resume HW thread */
+		cmdq_thread_resume(cqctx, tid);
+	}
+
+	spin_unlock_irqrestore(&cqctx->exec_lock, flags);
+
+	return status;
+}
+
+static void cmdq_core_handle_error(struct cmdq *cqctx, int tid, int value)
+{
+	struct device *dev = cqctx->dev;
+	void __iomem *gce_base_va = cqctx->base_va;
+	struct cmdq_thread *thread;
+	struct cmdq_task *task;
+	int cookie;
+	int count;
+	int inner;
+	int status;
+	u32 curr_pa, end_pa;
+
+	curr_pa = readl(gce_base_va + CMDQ_THR_CURR_ADDR_OFFSET +
+			CMDQ_THR_SHIFT * tid);
+	end_pa = readl(gce_base_va + CMDQ_THR_END_ADDR_OFFSET +
+		       CMDQ_THR_SHIFT * tid);
+
+	dev_err(dev, "IRQ: error thread=%d, irq_flag=0x%x\n", tid, value);
+	dev_err(dev, "IRQ: Thread PC: 0x%08x, End PC:0x%08x\n",
+		curr_pa, end_pa);
+
+	thread = &cqctx->thread[tid];
+
+	cookie = cmdq_thread_get_cookie(gce_base_va, tid);
+
+	/*
+	 * we assume error happens BEFORE EOC
+	 * because it wouldn't be error if this interrupt is issue by EOC.
+	 * so we should inc by 1 to locate "current" task
+	 */
+	cookie++;
+
+	/* set the issued task to error state */
+	if (thread->cur_task[cookie % CMDQ_MAX_TASK_IN_THREAD]) {
+		task = thread->cur_task[cookie % CMDQ_MAX_TASK_IN_THREAD];
+		task->irq_flag = value;
+		cmdq_thread_remove_task_by_index(
+				thread, cookie % CMDQ_MAX_TASK_IN_THREAD,
+				TASK_STATE_ERROR);
+	} else {
+		dev_err(dev,
+			"IRQ: can not find task in %s, pc:0x%08x, end_pc:0x%08x\n",
+			__func__, curr_pa, end_pa);
+		if (thread->task_count <= 0) {
+			/*
+			 * suspend HW thread first,
+			 * so that we work in a consistent state
+			 * outer function should acquire spinlock:
+			 *   cqctx->exec_lock
+			 */
+			status = cmdq_thread_suspend(cqctx, tid);
+			if (status < 0)
+				dev_err(dev, "IRQ: suspend HW thread failed!");
+
+			cmdq_thread_disable(cqctx, tid);
+			dev_err(dev,
+				"IRQ: there is no task for thread (%d) %s\n",
+				tid, __func__);
+		}
+	}
+
+	/* set the remain tasks to done state */
+	if (thread->wait_cookie <= cookie) {
+		count = cookie - thread->wait_cookie + 1;
+	} else if ((cookie + 1) % CMDQ_MAX_COOKIE_VALUE ==
+			thread->wait_cookie) {
+		count = 0;
+	} else {
+		/* counter wrapped */
+		count = (CMDQ_MAX_COOKIE_VALUE - thread->wait_cookie + 1) +
+			(cookie + 1);
+		dev_err(dev,
+			"IRQ: counter wrapped: wait cookie:%d, hw cookie:%d, count=%d",
+			thread->wait_cookie, cookie, count);
+	}
+
+	for (inner = (thread->wait_cookie % CMDQ_MAX_TASK_IN_THREAD); count > 0;
+	     count--, inner++) {
+		if (inner >= CMDQ_MAX_TASK_IN_THREAD)
+			inner = 0;
+
+		if (thread->cur_task[inner]) {
+			task = thread->cur_task[inner];
+			task->irq_flag = 0;	/* don't know irq flag */
+			/* still call isr_cb to prevent lock */
+			if (task->cb.isr_cb)
+				task->cb.isr_cb(task->cb.isr_data);
+			cmdq_thread_remove_task_by_index(
+					thread, inner, TASK_STATE_DONE);
+		}
+	}
+
+	thread->wait_cookie = cookie + 1;
+	if (thread->wait_cookie > CMDQ_MAX_COOKIE_VALUE)
+		thread->wait_cookie -= (CMDQ_MAX_COOKIE_VALUE + 1);
+			/* min cookie value is 0 */
+
+	wake_up(&cqctx->wait_queue[tid]);
+}
+
+static void cmdq_core_handle_done(struct cmdq *cqctx, int tid, int value)
+{
+	struct device *dev = cqctx->dev;
+	void __iomem *gce_base_va = cqctx->base_va;
+	struct cmdq_thread *thread = &cqctx->thread[tid];
+	int cookie = cmdq_thread_get_cookie(gce_base_va, tid);
+	int count;
+	int i;
+	struct cmdq_task *task;
+
+	if (thread->wait_cookie <= cookie) {
+		count = cookie - thread->wait_cookie + 1;
+	} else if ((cookie + 1) % CMDQ_MAX_COOKIE_VALUE ==
+			thread->wait_cookie) {
+		count = 0;
+	} else {
+		/* counter wrapped */
+		count = (CMDQ_MAX_COOKIE_VALUE - thread->wait_cookie + 1) +
+			(cookie + 1);
+		dev_err(dev,
+			"IRQ: counter wrapped: wait cookie:%d, hw cookie:%d, count=%d",
+			thread->wait_cookie, cookie, count);
+	}
+
+	for (i = (thread->wait_cookie % CMDQ_MAX_TASK_IN_THREAD); count > 0;
+	     count--, i++) {
+		if (i >= CMDQ_MAX_TASK_IN_THREAD)
+			i = 0;
+
+		if (thread->cur_task[i]) {
+			task = thread->cur_task[i];
+			task->irq_flag = value;
+			if (task->cb.isr_cb)
+				task->cb.isr_cb(task->cb.isr_data);
+			cmdq_thread_remove_task_by_index(
+					thread, i, TASK_STATE_DONE);
+		}
+	}
+
+	thread->wait_cookie = cookie + 1;
+	if (thread->wait_cookie > CMDQ_MAX_COOKIE_VALUE)
+		thread->wait_cookie -= (CMDQ_MAX_COOKIE_VALUE + 1);
+			/* min cookie value is 0 */
+
+	wake_up(&cqctx->wait_queue[tid]);
+}
+
+static void cmdq_core_handle_irq(struct cmdq *cqctx, int tid)
+{
+	struct device *dev = cqctx->dev;
+	void __iomem *gce_base_va = cqctx->base_va;
+	unsigned long flags = 0L;
+	int value;
+	int enabled;
+	int cookie;
+
+	/*
+	 * normal execution, marks tasks done and remove from thread
+	 * also, handle "loop CB fail" case
+	 */
+	spin_lock_irqsave(&cqctx->exec_lock, flags);
+
+	/*
+	 * it is possible for another CPU core
+	 * to run "release task" right before we acquire the spin lock
+	 * and thus reset / disable this HW thread
+	 * so we check both the IRQ flag and the enable bit of this thread
+	 */
+	value = readl(gce_base_va + CMDQ_THR_IRQ_STATUS_OFFSET +
+		      CMDQ_THR_SHIFT * tid);
+	if (!(value & CMDQ_THR_IRQ_MASK)) {
+		dev_err(dev,
+			"IRQ: thread %d got interrupt but IRQ flag is 0x%08x\n",
+			tid, value);
+		spin_unlock_irqrestore(&cqctx->exec_lock, flags);
+		return;
+	}
+
+	enabled = readl(gce_base_va + CMDQ_THR_ENABLE_TASK_OFFSET +
+			CMDQ_THR_SHIFT * tid);
+	if (!(enabled & CMDQ_THR_ENABLED)) {
+		dev_err(dev,
+			"IRQ: thread %d got interrupt already disabled 0x%08x\n",
+			tid, enabled);
+		spin_unlock_irqrestore(&cqctx->exec_lock, flags);
+		return;
+	}
+
+	/* read HW cookie here for printing message */
+	cookie = cmdq_thread_get_cookie(gce_base_va, tid);
+
+	/*
+	 * Move the reset IRQ before read HW cookie
+	 * to prevent race condition and save the cost of suspend
+	 */
+	writel(~value,
+	       gce_base_va + CMDQ_THR_IRQ_STATUS_OFFSET +
+	       CMDQ_THR_SHIFT * tid);
+
+	if (value & CMDQ_THR_IRQ_ERROR)
+		cmdq_core_handle_error(cqctx, tid, value);
+	else if (value & CMDQ_THR_IRQ_DONE)
+		cmdq_core_handle_done(cqctx, tid, value);
+
+	spin_unlock_irqrestore(&cqctx->exec_lock, flags);
+}
+
+static int cmdq_core_resumed_notifier(struct cmdq *cqctx)
+{
+	/*
+	 * Note:
+	 * delay resume timing until process-unfreeze done in order to
+	 * ensure M4U driver had restore M4U port setting
+	 */
+
+	unsigned long flags = 0L;
+
+	spin_lock_irqsave(&cqctx->thread_lock, flags);
+	cqctx->suspended = false;
+
+	/*
+	 * during suspending, there may be queued tasks.
+	 * we should process them if any.
+	 */
+	if (!work_pending(&cqctx->task_consume_wait_queue_item))
+		/* we use system global work queue (kernel thread kworker/n) */
+		queue_work(cqctx->task_consume_wq,
+			   &cqctx->task_consume_wait_queue_item);
+
+	spin_unlock_irqrestore(&cqctx->thread_lock, flags);
+
+	return 0;
+}
+
+static int cmdq_task_exec_async_with_retry(struct cmdq_task *task, int tid)
+{
+	struct device *dev = task->cqctx->dev;
+	int retry;
+	int status;
+
+	retry = 0;
+	status = -EFAULT;
+	do {
+		cmdq_core_verfiy_task_command_end(task);
+
+		status = cmdq_task_exec_async_impl(task, tid);
+
+		if (status >= 0)
+			break;
+
+		if ((task->task_state == TASK_STATE_KILLED) ||
+		    (task->task_state == TASK_STATE_ERROR)) {
+			dev_err(dev, "cmdq_task_exec_async_impl fail\n");
+			status = -EFAULT;
+			break;
+		}
+
+		retry++;
+	} while (retry < CMDQ_MAX_RETRY_COUNT);
+
+	return status;
+}
+
+static int cmdq_core_get_time_in_ms(unsigned long long start,
+				    unsigned long long end)
+{
+	unsigned long long _duration = end - start;
+
+	do_div(_duration, 1000000);
+	return (int)_duration;
+}
+
+static void cmdq_core_consume_waiting_list(struct work_struct *work)
+{
+	struct list_head *p, *n = NULL;
+	bool thread_acquired;
+	unsigned long long consume_time;
+	int waiting_time_ms;
+	bool need_log;
+	struct cmdq *cqctx;
+	struct device *dev;
+
+	cqctx = container_of(work, struct cmdq,
+			     task_consume_wait_queue_item);
+	dev = cqctx->dev;
+
+	/*
+	 * when we're suspending,
+	 * do not execute any tasks. delay & hold them.
+	 */
+	if (cqctx->suspended)
+		return;
+
+	consume_time = sched_clock();
+
+	mutex_lock(&cqctx->task_mutex);
+
+	thread_acquired = false;
+
+	/* scan and remove (if executed) waiting tasks */
+	list_for_each_safe(p, n, &cqctx->task_wait_list) {
+		struct cmdq_task *task;
+		struct cmdq_thread *thread = NULL;
+		int tid;
+		int status;
+		enum cmdq_hw_thread_priority thread_prio;
+
+		task = list_entry(p, struct cmdq_task, list_entry);
+
+		thread_prio = CMDQ_THR_PRIO_DISPLAY_CONFIG;
+
+		waiting_time_ms = cmdq_core_get_time_in_ms(
+				task->submit, consume_time);
+		need_log = waiting_time_ms >= CMDQ_PREALARM_TIMEOUT_MS;
+		/* allocate hw thread */
+		tid = cmdq_core_acquire_thread(cqctx, task->scenario);
+		if (tid != CMDQ_INVALID_THREAD)
+			thread = &cqctx->thread[tid];
+
+		if (tid == CMDQ_INVALID_THREAD || !thread) {
+			/* have to wait, remain in wait list */
+			dev_warn(dev, "acquire thread fail, need to wait\n");
+			if (need_log) /* task wait too long */
+				dev_warn(dev, "waiting:%dms, task:0x%p\n",
+					 waiting_time_ms, task);
+			continue;
+		}
+
+		/* some task is ready to run */
+		thread_acquired = true;
+
+		/*
+		 * start execution
+		 * remove from wait list and put into active list
+		 */
+		list_del_init(&task->list_entry);
+		list_add_tail(&task->list_entry,
+			      &cqctx->task_active_list);
+
+		/* run task on thread */
+		status = cmdq_task_exec_async_with_retry(task, tid);
+		if (status < 0) {
+			dev_err(dev, "%s fail, release task 0x%p\n",
+				__func__, task);
+			cmdq_task_remove_thread(task);
+			cmdq_task_release_unlocked(task);
+			task = NULL;
+		}
+	}
+
+	if (thread_acquired) {
+		/*
+		 * notify some task's sw thread to change their waiting state.
+		 * (if they have already called cmdq_task_wait_and_release())
+		 */
+		wake_up_all(&cqctx->thread_dispatch_queue);
+	}
+
+	mutex_unlock(&cqctx->task_mutex);
+}
+
+static int cmdq_core_submit_task_async(struct cmdq_command *cmd_desc,
+				       struct cmdq_task **task_out,
+				       struct cmdq_task_cb *cb)
+{
+	struct cmdq *cqctx = cmd_desc->cqctx;
+	struct cmdq_task *task;
+
+	cmdq_core_verfiy_desc_command_end(cmd_desc);
+
+	/* creates a new task and put into tail of waiting list */
+	task = cmdq_core_acquire_task(cmd_desc, cb);
+
+	if (!task)
+		return -EFAULT;
+
+	if (task_out)
+		*task_out = task;
+
+	/*
+	 * Consume the waiting list.
+	 * This may or may not execute the task, depending on available threads.
+	 */
+	cmdq_core_consume_waiting_list(&cqctx->task_consume_wait_queue_item);
+
+	return 0;
+}
+
+static int cmdq_core_release_task(struct cmdq_task *task)
+{
+	struct cmdq *cqctx = task->cqctx;
+	int tid = task->thread;
+	struct cmdq_thread *thread = &cqctx->thread[tid];
+	unsigned long flags;
+	int status;
+
+	if (tid != CMDQ_INVALID_THREAD && thread) {
+		/* this task is being executed (or queueed) on a hw thread */
+
+		/* get sw lock first to ensure atomic access hw */
+		spin_lock_irqsave(&cqctx->exec_lock, flags);
+		smp_mb();	/* make sure atomic access hw */
+
+		status = cmdq_thread_force_remove_task(task, tid);
+		if (thread->task_count > 0)
+			cmdq_thread_resume(cqctx, tid);
+
+		spin_unlock_irqrestore(&cqctx->exec_lock, flags);
+		wake_up(&cqctx->wait_queue[tid]);
+	}
+
+	cmdq_task_remove_thread(task);
+	cmdq_task_release_internal(task);
+	return 0;
+}
+
+struct cmdq_task_error_report {
+	bool throw_err;
+	const char *module;
+	u32 inst_a;
+	u32 inst_b;
+	u32 irq_flag;
+};
+
+static int cmdq_task_handle_error_result(
+		struct cmdq_task *task, int tid, int wait_q,
+		struct cmdq_task_error_report *error_report)
+{
+	struct cmdq *cqctx = task->cqctx;
+	struct device *dev = cqctx->dev;
+	void __iomem *gce_base_va = cqctx->base_va;
+	struct cmdq_thread *thread = &cqctx->thread[tid];
+	int status = 0;
+	int i;
+	bool is_err = false;
+	struct cmdq_task *next_task;
+	struct cmdq_task *prev_task;
+	int cookie;
+	unsigned long thread_pc;
+
+	dev_err(dev,
+		"task(0x%p) state is not TASK_STATE_DONE, but %d.\n",
+		task, task->task_state);
+
+	/*
+	 * Oops, tha tasks is not done.
+	 * We have several possible error scenario:
+	 * 1. task still running (hang / timeout)
+	 * 2. IRQ pending (done or error/timeout IRQ)
+	 * 3. task's SW thread has been signaled (e.g. SIGKILL)
+	 */
+
+	/*
+	 * suspend HW thread first,
+	 * so that we work in a consistent state
+	 */
+	status = cmdq_thread_suspend(cqctx, tid);
+	if (status < 0)
+		error_report->throw_err = true;
+
+	/* The cookie of the task currently being processed */
+	cookie = cmdq_thread_get_cookie(gce_base_va, tid) + 1;
+	thread_pc = (unsigned long)readl(gce_base_va +
+					 CMDQ_THR_CURR_ADDR_OFFSET +
+					 CMDQ_THR_SHIFT * tid);
+
+	/* process any pending IRQ */
+	error_report->irq_flag = readl(
+			gce_base_va + CMDQ_THR_IRQ_STATUS_OFFSET +
+			CMDQ_THR_SHIFT * tid);
+	if (error_report->irq_flag & CMDQ_THR_IRQ_ERROR)
+		cmdq_core_handle_error(cqctx, tid, error_report->irq_flag);
+	else if (error_report->irq_flag & CMDQ_THR_IRQ_DONE)
+		cmdq_core_handle_done(cqctx, tid, error_report->irq_flag);
+
+	writel(~error_report->irq_flag,
+	       gce_base_va + CMDQ_THR_IRQ_STATUS_OFFSET +
+	       CMDQ_THR_SHIFT * tid);
+
+	/* check if this task has finished after handling pending IRQ */
+	if (task->task_state == TASK_STATE_DONE)
+		return 0;
+
+	/* Then decide we are SW timeout or SIGNALed (not an error) */
+	if (!wait_q) {
+		/* SW timeout and no IRQ received */
+		is_err = true;
+		dev_err(dev, "SW timeout of task 0x%p on tid %d\n",
+			task, tid);
+		error_report->throw_err = true;
+		cmdq_core_parse_error(task, tid,
+				      &error_report->module,
+				      &error_report->irq_flag,
+				      &error_report->inst_a,
+				      &error_report->inst_b);
+		status = -ETIMEDOUT;
+	} else if (wait_q < 0) {
+		/*
+		 * Task is killed.
+		 * Not an error, but still need to remove.
+		 */
+		is_err = false;
+
+		if (wait_q == -ERESTARTSYS)
+			dev_err(dev,
+				"Task 0x%p KILLED by wait_q = -ERESTARTSYS\n",
+				task);
+		else if (wait_q == -EINTR)
+			dev_err(dev,
+				"Task 0x%p KILLED by wait_q = -EINTR\n",
+				task);
+		else
+			dev_err(dev,
+				"Task 0x%p KILLED by wait_q = %d\n",
+				task, wait_q);
+
+		status = wait_q;
+	}
+
+	if (task->task_state == TASK_STATE_ERROR) {
+		/* do nothing */
+	} else if (task->task_state == TASK_STATE_BUSY) {
+		/*
+		 * if task_state is BUSY,
+		 * this means we did not reach EOC,
+		 * did not have error IRQ.
+		 * - remove the task from thread.cur_task[]
+		 * - and decrease thread.task_count
+		 * NOTE: after this,
+		 * the cur_task will not contain link to task anymore.
+		 * and task should become TASK_STATE_ERROR
+		 */
+
+		/* we find our place in thread->cur_task[]. */
+		for (i = 0; i < CMDQ_MAX_TASK_IN_THREAD; i++) {
+			if (thread->cur_task[i] == task) {
+				/* update task_count and cur_task[] */
+				cmdq_thread_remove_task_by_index(
+						thread, i, is_err ?
+						TASK_STATE_ERROR :
+						TASK_STATE_KILLED);
+				break;
+			}
+		}
+	}
+
+	next_task = NULL;
+
+	/* find task's jump destination or no next task*/
+	if (task->va_base[task->num_cmd - 1] == CMDQ_JUMP_BY_PA)
+		next_task = cmdq_thread_search_task_by_pc(
+				thread,
+				task->va_base[task->num_cmd - 2]);
+
+	/*
+	 * Then, we try remove task from the chain of thread->cur_task.
+	 * . if HW PC falls in task range
+	 * . HW EXEC_CNT += 1
+	 * . thread.wait_cookie += 1
+	 * . set HW PC to next task head
+	 * . if not, find previous task
+	 *                (whose jump address is task->mva_base)
+	 * . check if HW PC points is not at the EOC/JUMP end
+	 * . change jump to fake EOC(no IRQ)
+	 * . insert jump to next task head and increase cmd buffer size
+	 * . if there is no next task, set HW End Address
+	 */
+	if (task->num_cmd && thread_pc >= task->mva_base &&
+	    thread_pc <= (task->mva_base + task->command_size)) {
+		if (next_task) {
+			/* cookie already +1 */
+			writel(cookie,
+			       gce_base_va + CMDQ_THR_EXEC_CNT_OFFSET +
+			       CMDQ_THR_SHIFT * tid);
+			thread->wait_cookie = cookie + 1;
+			writel(next_task->mva_base,
+			       gce_base_va + CMDQ_THR_CURR_ADDR_OFFSET +
+			       CMDQ_THR_SHIFT * tid);
+		}
+	} else {
+		prev_task = NULL;
+		for (i = 0; i < CMDQ_MAX_TASK_IN_THREAD; i++) {
+			u32 *prev_va, *curr_va;
+			u32 prev_num, curr_num;
+
+			prev_task = thread->cur_task[i];
+			if (!prev_task)
+				continue;
+
+			prev_va = prev_task->va_base;
+			prev_num = prev_task->num_cmd;
+			if (!prev_num)
+				continue;
+
+			curr_va = task->va_base;
+			curr_num = task->num_cmd;
+
+			/* find which task JUMP into task */
+			if (prev_va[prev_num - 2] == task->mva_base &&
+			    prev_va[prev_num - 1] == CMDQ_JUMP_BY_PA) {
+				/* Copy Jump instruction */
+				prev_va[prev_num - 2] =
+					curr_va[curr_num - 2];
+				prev_va[prev_num - 1] =
+					curr_va[curr_num - 1];
+
+				if (next_task)
+					cmdq_thread_reorder_task_array(
+							thread, i);
+
+				/*
+				 * Give up fetched command,
+				 * invoke CMDQ HW to re-fetch command.
+				 */
+				cmdq_core_invalidate_hw_fetched_buffer(
+						gce_base_va, tid);
+
+				break;
+			}
+		}
+	}
+
+	return status;
+}
+
+static int cmdq_task_wait_result(struct cmdq_task *task, int tid, int wait_q)
+{
+	struct cmdq *cqctx = task->cqctx;
+	struct cmdq_thread *thread = &cqctx->thread[tid];
+	int status = 0;
+	unsigned long flags;
+	struct cmdq_task_error_report error_report = {
+		.throw_err = false,
+		.module = NULL,
+		.inst_a = 0,
+		.inst_b = 0,
+		.irq_flag = 0,
+	};
+
+	/*
+	 * Note that although we disable IRQ, HW continues to execute
+	 * so it's possible to have pending IRQ
+	 */
+	spin_lock_irqsave(&cqctx->exec_lock, flags);
+
+	if (task->task_state != TASK_STATE_DONE)
+		status = cmdq_task_handle_error_result(
+				task, tid, wait_q, &error_report);
+
+	if (thread->task_count <= 0)
+		cmdq_thread_disable(cqctx, tid);
+	else
+		cmdq_thread_resume(cqctx, tid);
+
+	spin_unlock_irqrestore(&cqctx->exec_lock, flags);
+
+	if (error_report.throw_err) {
+		u32 op = error_report.inst_a >> CMDQ_OP_CODE_SHIFT;
+
+		switch (op) {
+		case CMDQ_CODE_WFE:
+			dev_err(cqctx->dev,
+				"%s in CMDQ IRQ:0x%02x, INST:(0x%08x, 0x%08x), OP:WAIT EVENT:%s\n",
+				error_report.module, error_report.irq_flag,
+				error_report.inst_a, error_report.inst_b,
+				cmdq_event_get_name(error_report.inst_a &
+						    CMDQ_ARG_A_MASK));
+			break;
+		default:
+			dev_err(cqctx->dev,
+				"%s in CMDQ IRQ:0x%02x, INST:(0x%08x, 0x%08x), OP:%s\n",
+				error_report.module, error_report.irq_flag,
+				error_report.inst_a, error_report.inst_b,
+				cmdq_core_parse_op(op));
+			break;
+		}
+	}
+
+	return status;
+}
+
+static int cmdq_task_wait_done(struct cmdq_task *task)
+{
+	struct cmdq *cqctx = task->cqctx;
+	struct device *dev = cqctx->dev;
+	int wait_q;
+	int tid;
+	unsigned long timeout = msecs_to_jiffies(
+			CMDQ_ACQUIRE_THREAD_TIMEOUT_MS);
+
+	tid = task->thread;
+	if (tid == CMDQ_INVALID_THREAD) {
+		/*
+		 * wait for acquire thread
+		 * (this is done by cmdq_core_consume_waiting_list);
+		 */
+		wait_q = wait_event_timeout(
+				cqctx->thread_dispatch_queue,
+				(task->thread != CMDQ_INVALID_THREAD), timeout);
+
+		if (!wait_q || task->thread == CMDQ_INVALID_THREAD) {
+			mutex_lock(&cqctx->task_mutex);
+
+			/*
+			 * it's possible that the task was just consumed now.
+			 * so check again.
+			 */
+			if (task->thread == CMDQ_INVALID_THREAD) {
+				/*
+				 * Task may have released,
+				 * or starved to death.
+				 */
+				dev_err(dev,
+					"task(0x%p) timeout with invalid thread\n",
+					task);
+
+				/*
+				 * remove from waiting list,
+				 * so that it won't be consumed in the future
+				 */
+				list_del_init(&task->list_entry);
+
+				mutex_unlock(&cqctx->task_mutex);
+				return -EINVAL;
+			}
+
+			/* valid thread, so we keep going */
+			mutex_unlock(&cqctx->task_mutex);
+		}
+	}
+
+	tid = task->thread;
+	if (tid < 0 || tid >= CMDQ_MAX_THREAD_COUNT) {
+		dev_err(dev, "invalid thread %d in %s\n", tid, __func__);
+		return -EINVAL;
+	}
+
+	/* start to wait */
+	wait_q = wait_event_timeout(task->cqctx->wait_queue[tid],
+				    (task->task_state != TASK_STATE_BUSY &&
+				     task->task_state != TASK_STATE_WAITING),
+				    msecs_to_jiffies(CMDQ_DEFAULT_TIMEOUT_MS));
+	if (!wait_q)
+		dev_dbg(dev, "timeout!\n");
+
+	/* wake up and continue */
+	return cmdq_task_wait_result(task, tid, wait_q);
+}
+
+static int cmdq_task_wait_and_release(struct cmdq_task *task)
+{
+	struct cmdq *cqctx;
+	int status;
+	int tid;
+
+	if (!task) {
+		pr_err("%s err ptr=0x%p\n", __func__, task);
+		return -EFAULT;
+	}
+
+	if (task->task_state == TASK_STATE_IDLE) {
+		pr_err("%s task=0x%p is IDLE\n", __func__, task);
+		return -EFAULT;
+	}
+
+	cqctx = task->cqctx;
+
+	/* wait for task finish */
+	tid = task->thread;
+	status = cmdq_task_wait_done(task);
+
+	/* release */
+	cmdq_task_remove_thread(task);
+	cmdq_task_release_internal(task);
+
+	return status;
+}
+
+static void cmdq_core_auto_release_work(struct work_struct *work_item)
+{
+	struct cmdq_task *task;
+	int status;
+	struct cmdq_task_cb cb;
+
+	task = container_of(work_item, struct cmdq_task, auto_release_work);
+	memcpy(&cb, &task->cb, sizeof(cb));
+	status = cmdq_task_wait_and_release(task);
+	task = NULL;
+
+	/* isr fail, so call isr_cb here to prevent lock */
+	if (status && cb.isr_cb)
+		cb.isr_cb(cb.isr_data);
+
+	if (cb.done_cb)
+		cb.done_cb(cb.done_data);
+}
+
+static int cmdq_core_auto_release_task(struct cmdq_task *task)
+{
+	struct cmdq *cqctx = task->cqctx;
+
+	/*
+	 * the work item is embeded in task already
+	 * but we need to initialized it
+	 */
+	INIT_WORK(&task->auto_release_work, cmdq_core_auto_release_work);
+	queue_work(cqctx->task_auto_release_wq, &task->auto_release_work);
+	return 0;
+}
+
+static int cmdq_core_submit_task(struct cmdq_command *cmd_desc)
+{
+	struct device *dev = cmd_desc->cqctx->dev;
+	int status;
+	struct cmdq_task *task;
+
+	status = cmdq_core_submit_task_async(cmd_desc, &task, NULL);
+
+	if (status >= 0) {
+		status = cmdq_task_wait_and_release(task);
+		if (status < 0)
+			dev_err(dev, "task(0x%p) wait fail\n", task);
+	} else {
+		dev_err(dev, "cmdq_core_submit_task_async failed=%d", status);
+	}
+
+	return status;
+}
+
+static void cmdq_core_deinitialize(struct platform_device *pdev)
+{
+	struct cmdq *cqctx = platform_get_drvdata(pdev);
+	struct list_head *p;
+	int i;
+	struct list_head *lists[] = {
+		&cqctx->task_free_list,
+		&cqctx->task_active_list,
+		&cqctx->task_wait_list
+	};
+
+	/*
+	 * Directly destroy the auto release WQ
+	 * since we're going to release tasks anyway.
+	 */
+	destroy_workqueue(cqctx->task_auto_release_wq);
+	cqctx->task_auto_release_wq = NULL;
+
+	destroy_workqueue(cqctx->task_consume_wq);
+	cqctx->task_consume_wq = NULL;
+
+	/* release all tasks in both list */
+	for (i = 0; i < ARRAY_SIZE(lists); i++) {
+		list_for_each(p, lists[i]) {
+			struct cmdq_task *task;
+
+			mutex_lock(&cqctx->task_mutex);
+
+			task = list_entry(p, struct cmdq_task, list_entry);
+
+			/* free allocated DMA buffer */
+			cmdq_task_free_command_buffer(task);
+			kmem_cache_free(cqctx->task_cache, task);
+			list_del(p);
+
+			mutex_unlock(&cqctx->task_mutex);
+		}
+	}
+
+	kmem_cache_destroy(cqctx->task_cache);
+	cqctx->task_cache = NULL;
+
+	/* release command buffer pool */
+	cmdq_cmd_buf_pool_uninit(cqctx);
+}
+
+static irqreturn_t cmdq_irq_handler(int irq, void *dev)
+{
+	struct cmdq *cqctx = dev;
+	int i;
+	u32 irq_status;
+	bool handled = false;
+
+	if (cqctx->irq == irq) {
+		irq_status = readl(cqctx->base_va +
+				   CMDQ_CURR_IRQ_STATUS_OFFSET);
+		irq_status &= CMDQ_IRQ_MASK;
+		for (i = 0;
+		     (irq_status != CMDQ_IRQ_MASK) && i < CMDQ_MAX_THREAD_COUNT;
+		     i++) {
+			/* STATUS bit set to 0 means IRQ asserted */
+			if (irq_status & BIT(i))
+				continue;
+
+			/*
+			 * We mark irq_status to 1 to denote finished
+			 * processing, and we can early-exit if no more
+			 * threads being asserted.
+			 */
+			irq_status |= BIT(i);
+
+			cmdq_core_handle_irq(cqctx, i);
+			handled = true;
+		}
+	}
+
+	if (handled) {
+		queue_work(cqctx->task_consume_wq,
+			   &cqctx->task_consume_wait_queue_item);
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+static int cmdq_core_initialize(struct platform_device *pdev,
+				struct cmdq **cqctx)
+{
+	struct cmdq *lcqctx; /* local cmdq context */
+	int i;
+	int ret = 0;
+
+	lcqctx = devm_kzalloc(&pdev->dev, sizeof(*lcqctx), GFP_KERNEL);
+
+	/* save dev */
+	lcqctx->dev = &pdev->dev;
+
+	/* initial cmdq device related data */
+	ret = cmdq_dev_init(pdev, lcqctx);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to init cmdq device\n");
+		goto fail_dev;
+	}
+
+	/* initial mutex, spinlock */
+	mutex_init(&lcqctx->task_mutex);
+	mutex_init(&lcqctx->clock_mutex);
+	spin_lock_init(&lcqctx->thread_lock);
+	spin_lock_init(&lcqctx->exec_lock);
+
+	/* initial wait queue for notification */
+	for (i = 0; i < ARRAY_SIZE(lcqctx->wait_queue); i++)
+		init_waitqueue_head(&lcqctx->wait_queue[i]);
+	init_waitqueue_head(&lcqctx->thread_dispatch_queue);
+
+	/* create task pool */
+	lcqctx->task_cache = kmem_cache_create(
+			CMDQ_DRIVER_DEVICE_NAME "_task",
+			sizeof(struct cmdq_task),
+			__alignof__(struct cmdq_task),
+			SLAB_POISON | SLAB_HWCACHE_ALIGN | SLAB_RED_ZONE,
+			&cmdq_task_ctor);
+
+	/* initialize task lists */
+	INIT_LIST_HEAD(&lcqctx->task_free_list);
+	INIT_LIST_HEAD(&lcqctx->task_active_list);
+	INIT_LIST_HEAD(&lcqctx->task_wait_list);
+	INIT_WORK(&lcqctx->task_consume_wait_queue_item,
+		  cmdq_core_consume_waiting_list);
+
+	/* initialize command buffer pool */
+	ret = cmdq_cmd_buf_pool_init(lcqctx);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to init command buffer pool\n");
+		goto fail_cmd_buf_pool;
+	}
+
+	lcqctx->task_auto_release_wq = alloc_ordered_workqueue(
+			"%s", WQ_MEM_RECLAIM | WQ_HIGHPRI, "cmdq_auto_release");
+	lcqctx->task_consume_wq = alloc_ordered_workqueue(
+			"%s", WQ_MEM_RECLAIM | WQ_HIGHPRI, "cmdq_task");
+
+	*cqctx = lcqctx;
+	return ret;
+
+fail_cmd_buf_pool:
+	destroy_workqueue(lcqctx->task_auto_release_wq);
+	destroy_workqueue(lcqctx->task_consume_wq);
+	kmem_cache_destroy(lcqctx->task_cache);
+
+fail_dev:
+	return ret;
+}
+
+static int cmdq_rec_realloc_cmd_buffer(struct cmdq_rec *handle, u32 size)
+{
+	void *new_buf;
+
+	new_buf = krealloc(handle->buf_ptr, size, GFP_KERNEL | __GFP_ZERO);
+	if (!new_buf)
+		return -ENOMEM;
+	handle->buf_ptr = new_buf;
+	handle->buf_size = size;
+	return 0;
+}
+
+static struct cmdq *cmdq_rec_get_valid_ctx(struct cmdq_rec *handle)
+{
+	if (!handle) {
+		WARN_ON(1);
+		return NULL;
+	}
+
+	WARN_ON(!handle->cqctx);
+	return handle->cqctx;
+}
+
+static int cmdq_rec_stop_running_task(struct cmdq_rec *handle)
+{
+	int status;
+
+	status = cmdq_core_release_task(handle->running_task_ptr);
+	handle->running_task_ptr = NULL;
+	return status;
+}
+
+int cmdq_rec_create(struct platform_device *pdev,
+		    enum cmdq_scenario scenario,
+		    struct cmdq_rec **handle_ptr)
+{
+	struct cmdq *cqctx;
+	struct device *dev = &pdev->dev;
+	struct cmdq_rec *handle;
+	int ret;
+
+	cqctx = platform_get_drvdata(pdev);
+	if (!cqctx) {
+		dev_err(dev, "cmdq context is NULL\n");
+		return -EINVAL;
+	}
+
+	if (scenario < 0 || scenario >= CMDQ_MAX_SCENARIO_COUNT) {
+		dev_err(dev, "unknown scenario type %d\n", scenario);
+		return -EINVAL;
+	}
+
+	handle = kzalloc(sizeof(*handle), GFP_KERNEL);
+	if (!handle)
+		return -ENOMEM;
+
+	handle->cqctx = platform_get_drvdata(pdev);
+	handle->scenario = scenario;
+	handle->engine_flag = cmdq_scenario_get_flag(cqctx, scenario);
+	handle->priority = CMDQ_THR_PRIO_NORMAL;
+
+	ret = cmdq_rec_realloc_cmd_buffer(handle, CMDQ_INITIAL_CMD_BLOCK_SIZE);
+	if (ret) {
+		kfree(handle);
+		return ret;
+	}
+
+	*handle_ptr = handle;
+
+	return 0;
+}
+EXPORT_SYMBOL(cmdq_rec_create);
+
+static int cmdq_rec_mark(struct cmdq_rec *handle);
+
+static int cmdq_rec_append_command(struct cmdq_rec *handle,
+				   enum cmdq_code code,
+				   u32 arg_a, u32 arg_b)
+{
+	struct cmdq *cqctx;
+	struct device *dev;
+	int subsys;
+	u32 *cmd_ptr;
+	int ret;
+
+	if (!cmdq_rec_get_valid_ctx(handle))
+		return -EFAULT;
+
+	cqctx = handle->cqctx;
+	dev = cqctx->dev;
+	cmd_ptr = (u32 *)((u8 *)handle->buf_ptr + handle->block_size);
+
+	if (handle->finalized) {
+		dev_err(dev,
+			"already finalized record(cannot add more command)");
+		dev_err(dev, "handle=0x%p, tid=%d\n", handle, current->pid);
+		return -EBUSY;
+	}
+
+	/* check if we have sufficient buffer size */
+	if (unlikely(handle->block_size + CMDQ_INST_SIZE > handle->buf_size)) {
+		ret = cmdq_rec_realloc_cmd_buffer(handle, handle->buf_size * 2);
+		if (ret)
+			return ret;
+	}
+
+	/*
+	 * force insert MARKER if prefetch memory is full
+	 * GCE deadlocks if we don't do so
+	 */
+	if (code != CMDQ_CODE_EOC &&
+	    cmdq_scenario_is_prefetch(handle->scenario)) {
+		if (handle->prefetch_count >= CMDQ_MAX_PREFETCH_INSTUCTION) {
+			/* Mark END of prefetch section */
+			cmdq_rec_disable_prefetch(handle);
+			/* BEGINNING of next prefetch section */
+			cmdq_rec_mark(handle);
+		} else {
+			/* prefetch enabled marker exist */
+			if (handle->prefetch_count >= 1)
+				++handle->prefetch_count;
+		}
+	}
+
+	/*
+	 * we must re-calculate current PC
+	 * because we may already insert MARKER inst.
+	 */
+	cmd_ptr = (u32 *)((u8 *)handle->buf_ptr + handle->block_size);
+
+	switch (code) {
+	case CMDQ_CODE_MOVE:
+		*cmd_ptr++ = arg_b;
+		*cmd_ptr++ = (CMDQ_CODE_MOVE << CMDQ_OP_CODE_SHIFT) |
+			     (arg_a & CMDQ_ARG_A_MASK);
+		break;
+	case CMDQ_CODE_WRITE:
+		subsys = cmdq_subsys_from_phys_addr(cqctx, arg_a);
+		if (subsys < 0) {
+			dev_err(dev,
+				"unsupported memory base address 0x%08x\n",
+				arg_a);
+			return -EFAULT;
+		}
+
+		*cmd_ptr++ = arg_b;
+		*cmd_ptr++ = (CMDQ_CODE_WRITE << CMDQ_OP_CODE_SHIFT) |
+			     (arg_a & CMDQ_ARG_A_WRITE_MASK) |
+			     ((subsys & CMDQ_SUBSYS_MASK) << CMDQ_SUBSYS_SHIFT);
+		break;
+	case CMDQ_CODE_JUMP:
+		*cmd_ptr++ = arg_b;
+		*cmd_ptr++ = (CMDQ_CODE_JUMP << CMDQ_OP_CODE_SHIFT) |
+			     (arg_a & CMDQ_ARG_A_MASK);
+		break;
+	case CMDQ_CODE_WFE:
+		/*
+		 * bit 0-11: wait_value, 1
+		 * bit 15: to_wait, true
+		 * bit 16-27: update_value, 0
+		 * bit 31: to_update, true
+		 */
+		*cmd_ptr++ = CMDQ_WFE_UPDATE | CMDQ_WFE_WAIT |
+			     CMDQ_WFE_WAIT_VALUE;
+		*cmd_ptr++ = (CMDQ_CODE_WFE << CMDQ_OP_CODE_SHIFT) | arg_a;
+		break;
+	case CMDQ_CODE_CLEAR_EVENT:
+		/*
+		 * bit 0-11: wait_value, 0
+		 * bit 15: to_wait, false
+		 * bit 16-27: update_value, 0
+		 * bit 31: to_update, true
+		 */
+		*cmd_ptr++ = CMDQ_WFE_UPDATE;
+		*cmd_ptr++ = (CMDQ_CODE_WFE << CMDQ_OP_CODE_SHIFT) | arg_a;
+		break;
+	case CMDQ_CODE_EOC:
+		*cmd_ptr++ = arg_b;
+		*cmd_ptr++ = (CMDQ_CODE_EOC << CMDQ_OP_CODE_SHIFT) |
+			     (arg_a & CMDQ_ARG_A_MASK);
+		break;
+	default:
+		return -EFAULT;
+	}
+
+	handle->block_size += CMDQ_INST_SIZE;
+
+	return 0;
+}
+
+int cmdq_rec_reset(struct cmdq_rec *handle)
+{
+	struct cmdq *cqctx = cmdq_rec_get_valid_ctx(handle);
+
+	if (!cqctx)
+		return -EFAULT;
+
+	if (handle->running_task_ptr)
+		cmdq_rec_stop_running_task(handle);
+
+	handle->block_size = 0;
+	handle->prefetch_count = 0;
+	handle->finalized = false;
+
+	return 0;
+}
+EXPORT_SYMBOL(cmdq_rec_reset);
+
+static int cmdq_rec_mark(struct cmdq_rec *handle)
+{
+	int status;
+
+	/*
+	 * bit 53: non-suspendable. set to 1 because we don't want
+	 *         CPU suspend this thread during pre-fetching.
+	 *         If CPU change PC, then there will be a mess,
+	 *         because prefetch buffer is not properly cleared.
+	 * bit 48: do not increase CMD_COUNTER
+	 *         (because this is not the end of the task)
+	 * bit 20: prefetch_marker
+	 * bit 17: prefetch_marker_en
+	 * bit 16: prefetch_en
+	 * bit 0:  irq_en (set to 0 since we don't want EOC interrupt)
+	 */
+	status = cmdq_rec_append_command(handle, CMDQ_CODE_EOC,
+					 CMDQ_MARK_NON_SUSPENDABLE |
+					 CMDQ_MARK_NOT_ADD_COUNTER,
+					 CMDQ_MARK_PREFETCH_MARKER |
+					 CMDQ_MARK_PREFETCH_MARKER_EN |
+					 CMDQ_MARK_PREFETCH_EN);
+	if (status)
+		return status;
+
+	/*
+	 * if we're in a prefetch region,
+	 * this ends the region so set count to 0.
+	 * otherwise we start the region by setting count to 1.
+	 */
+	handle->prefetch_count = 1;
+
+	return 0;
+}
+
+int cmdq_rec_write(struct cmdq_rec *handle, u32 value, u32 addr)
+{
+	struct cmdq *cqctx = cmdq_rec_get_valid_ctx(handle);
+
+	if (!cqctx)
+		return -EFAULT;
+
+	return cmdq_rec_append_command(handle, CMDQ_CODE_WRITE, addr, value);
+}
+EXPORT_SYMBOL(cmdq_rec_write);
+
+int cmdq_rec_write_mask(struct cmdq_rec *handle, u32 value,
+			u32 addr, u32 mask)
+{
+	struct cmdq *cqctx = cmdq_rec_get_valid_ctx(handle);
+	int ret;
+
+	if (!cqctx)
+		return -EFAULT;
+
+	if (mask != 0xffffffff) {
+		ret = cmdq_rec_append_command(handle, CMDQ_CODE_MOVE, 0, ~mask);
+		if (ret)
+			return ret;
+
+		addr = addr | CMDQ_ENABLE_MASK;
+	}
+
+	return cmdq_rec_append_command(handle, CMDQ_CODE_WRITE, addr, value);
+}
+EXPORT_SYMBOL(cmdq_rec_write_mask);
+
+int cmdq_rec_wait(struct cmdq_rec *handle, enum cmdq_event event)
+{
+	struct cmdq *cqctx = cmdq_rec_get_valid_ctx(handle);
+
+	if (!cqctx)
+		return -EFAULT;
+
+	if (event == CMDQ_SYNC_TOKEN_INVALID || event >= CMDQ_SYNC_TOKEN_MAX ||
+	    event < 0)
+		return -EINVAL;
+
+	return cmdq_rec_append_command(handle, CMDQ_CODE_WFE, event, 0);
+}
+EXPORT_SYMBOL(cmdq_rec_wait);
+
+int cmdq_rec_clear_event(struct cmdq_rec *handle, enum cmdq_event event)
+{
+	struct cmdq *cqctx = cmdq_rec_get_valid_ctx(handle);
+
+	if (!cqctx)
+		return -EFAULT;
+
+	if (event == CMDQ_SYNC_TOKEN_INVALID || event >= CMDQ_SYNC_TOKEN_MAX ||
+	    event < 0)
+		return -EINVAL;
+
+	return cmdq_rec_append_command(handle, CMDQ_CODE_CLEAR_EVENT, event, 0);
+}
+EXPORT_SYMBOL(cmdq_rec_clear_event);
+
+int cmdq_rec_disable_prefetch(struct cmdq_rec *handle)
+{
+	struct cmdq *cqctx = cmdq_rec_get_valid_ctx(handle);
+	int status;
+	u32 arg_a, arg_b;
+
+	if (!cqctx)
+		return -EFAULT;
+
+	if (!handle->finalized) {
+		if (handle->prefetch_count > 0) {
+			/*
+			 * with prefetch threads we should end with
+			 * bit 48: no_inc_exec_cmds_cnt = 1
+			 * bit 20: prefetch_mark = 1
+			 * bit 17: prefetch_mark_en = 0
+			 * bit 16: prefetch_en = 0
+			 */
+			arg_b = CMDQ_MARK_PREFETCH_MARKER;
+			arg_a = CMDQ_MARK_NOT_ADD_COUNTER;
+			/* since we're finalized, no more prefetch */
+			handle->prefetch_count = 0;
+			status = cmdq_rec_append_command(handle, CMDQ_CODE_EOC,
+							 arg_a, arg_b);
+		}
+
+		if (status)
+			return status;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(cmdq_rec_disable_prefetch);
+
+static int cmdq_rec_finalize_command(struct cmdq_rec *handle)
+{
+	int status;
+	struct device *dev;
+	u32 arg_b;
+
+	if (!cmdq_rec_get_valid_ctx(handle))
+		return -EFAULT;
+
+	dev = handle->cqctx->dev;
+
+	if (!handle->finalized) {
+		if ((handle->prefetch_count > 0) &&
+		    cmdq_scenario_is_prefetch(handle->scenario)) {
+			dev_err(dev, "not insert prefetch disble marker ");
+			dev_err(dev, "if prefetch enabled, prefetch_count:%d\n",
+				handle->prefetch_count);
+			return -EFAULT;
+		}
+
+		/* insert EOC and generate IRQ for each command iteration */
+		arg_b = CMDQ_EOC_IRQ_EN;
+		status = cmdq_rec_append_command(handle, CMDQ_CODE_EOC,
+						 0, arg_b);
+		if (status)
+			return status;
+
+		/* JUMP to begin */
+		status = cmdq_rec_append_command(handle, CMDQ_CODE_JUMP, 0, 8);
+		if (status)
+			return status;
+
+		handle->finalized = true;
+	}
+
+	return 0;
+}
+
+static int cmdq_rec_fill_cmd_desc(struct cmdq_rec *handle,
+				  struct cmdq_command *desc)
+{
+	int ret;
+
+	ret = cmdq_rec_finalize_command(handle);
+	if (ret)
+		return ret;
+
+	desc->cqctx = handle->cqctx;
+	desc->scenario = handle->scenario;
+	desc->priority = handle->priority;
+	desc->engine_flag = handle->engine_flag;
+	desc->va_base = handle->buf_ptr;
+	desc->block_size = handle->block_size;
+
+	return ret;
+}
+
+int cmdq_rec_flush(struct cmdq_rec *handle)
+{
+	struct cmdq *cqctx = cmdq_rec_get_valid_ctx(handle);
+	int ret;
+	struct cmdq_command desc = { 0 };
+
+	if (!cqctx)
+		return -EFAULT;
+
+	ret = cmdq_rec_fill_cmd_desc(handle, &desc);
+	if (ret)
+		return ret;
+
+	return cmdq_core_submit_task(&desc);
+}
+EXPORT_SYMBOL(cmdq_rec_flush);
+
+static int cmdq_rec_flush_async_cb(struct cmdq_rec *handle,
+				   cmdq_async_flush_cb isr_cb,
+				   void *isr_data,
+				   cmdq_async_flush_cb done_cb,
+				   void *done_data)
+{
+	int ret;
+	struct cmdq_command desc = { 0 };
+	struct cmdq_task *task;
+	struct cmdq_task_cb cb;
+
+	ret = cmdq_rec_fill_cmd_desc(handle, &desc);
+	if (ret)
+		return ret;
+
+	cb.isr_cb = isr_cb;
+	cb.isr_data = isr_data;
+	cb.done_cb = done_cb;
+	cb.done_data = done_data;
+
+	ret = cmdq_core_submit_task_async(&desc, &task, &cb);
+	if (ret)
+		return ret;
+
+	if (task)
+		ret = cmdq_core_auto_release_task(task);
+	else
+		ret = -ENOMEM;
+
+	return ret;
+}
+
+int cmdq_rec_flush_async(struct cmdq_rec *handle)
+{
+	struct cmdq *cqctx = cmdq_rec_get_valid_ctx(handle);
+
+	if (!cqctx)
+		return -EFAULT;
+
+	return cmdq_rec_flush_async_cb(handle, NULL, NULL, NULL, NULL);
+}
+EXPORT_SYMBOL(cmdq_rec_flush_async);
+
+int cmdq_rec_flush_async_callback(struct cmdq_rec *handle,
+				  cmdq_async_flush_cb isr_cb,
+				  void *isr_data,
+				  cmdq_async_flush_cb done_cb,
+				  void *done_data)
+{
+	struct cmdq *cqctx = cmdq_rec_get_valid_ctx(handle);
+
+	if (!cqctx)
+		return -EFAULT;
+
+	return cmdq_rec_flush_async_cb(handle, isr_cb, isr_data,
+				       done_cb, done_data);
+}
+EXPORT_SYMBOL(cmdq_rec_flush_async_callback);
+
+void cmdq_rec_destroy(struct cmdq_rec *handle)
+{
+	struct cmdq *cqctx = cmdq_rec_get_valid_ctx(handle);
+
+	if (!cqctx)
+		return;
+
+	if (handle->running_task_ptr)
+		cmdq_rec_stop_running_task(handle);
+
+	/* free command buffer */
+	kfree(handle->buf_ptr);
+	handle->buf_ptr = NULL;
+
+	/* free command handle */
+	kfree(handle);
+}
+EXPORT_SYMBOL(cmdq_rec_destroy);
+
+static int cmdq_pm_notifier_cb(struct notifier_block *nb, unsigned long event,
+			       void *ptr)
+{
+	struct cmdq *cqctx = container_of(nb, struct cmdq, pm_notifier);
+
+	switch (event) {
+	case PM_SUSPEND_PREPARE:
+		/*
+		 * Going to suspend the system
+		 * The next stage is freeze process.
+		 * We will queue all request in suspend callback,
+		 * so don't care this stage
+		 */
+		return NOTIFY_DONE;
+	case PM_POST_SUSPEND:
+		/*
+		 * processes had resumed in previous stage
+		 * (system resume callback)
+		 * resume CMDQ driver to execute.
+		 */
+		cmdq_core_resumed_notifier(cqctx);
+		return NOTIFY_OK;
+	default:
+		return NOTIFY_DONE;
+	}
+	return NOTIFY_DONE;
+}
+
+static int cmdq_suspend(struct device *dev)
+{
+	struct cmdq *cqctx;
+	void __iomem *gce_base_va;
+	unsigned long flags = 0L;
+	u32 exec_threads;
+	int ref_count;
+	bool kill_tasks = false;
+	struct cmdq_task *task;
+	struct list_head *p;
+	int i;
+
+	cqctx = dev_get_drvdata(dev);
+	gce_base_va = cqctx->base_va;
+	exec_threads = readl(gce_base_va + CMDQ_CURR_LOADED_THR_OFFSET);
+	ref_count = cqctx->thread_usage;
+
+	if ((ref_count > 0) || (exec_threads & CMDQ_THR_EXECUTING)) {
+		dev_err(dev,
+			"[SUSPEND] other running, kill tasks. threads:0x%08x, ref:%d\n",
+			exec_threads, ref_count);
+		kill_tasks = true;
+	}
+
+	/*
+	 * We need to ensure the system is ready to suspend,
+	 * so kill all running CMDQ tasks
+	 * and release HW engines.
+	 */
+	if (kill_tasks) {
+		/* remove all active task from thread */
+		dev_err(dev, "[SUSPEND] remove all active tasks\n");
+		list_for_each(p, &cqctx->task_active_list) {
+			task = list_entry(p, struct cmdq_task, list_entry);
+			if (task->thread != CMDQ_INVALID_THREAD) {
+				spin_lock_irqsave(&cqctx->exec_lock, flags);
+				cmdq_thread_force_remove_task(
+						task, task->thread);
+				task->task_state = TASK_STATE_KILLED;
+				spin_unlock_irqrestore(
+						&cqctx->exec_lock, flags);
+
+				/*
+				 * release all thread and
+				 * mark all active tasks as "KILLED"
+				 * (so that thread won't release again)
+				 */
+				dev_err(dev,
+					"[SUSPEND] release all threads and HW clocks\n");
+				cmdq_task_remove_thread(task);
+			}
+		}
+
+		/* disable all HW thread */
+		dev_err(dev, "[SUSPEND] disable all HW threads\n");
+		for (i = 0; i < CMDQ_MAX_THREAD_COUNT; i++)
+			cmdq_thread_disable(cqctx, i);
+
+		/* reset all cmdq_thread */
+		memset(&cqctx->thread[0], 0, sizeof(cqctx->thread));
+	}
+
+	spin_lock_irqsave(&cqctx->thread_lock, flags);
+	cqctx->suspended = true;
+	spin_unlock_irqrestore(&cqctx->thread_lock, flags);
+
+	/* ALWAYS allow suspend */
+	return 0;
+}
+
+static int cmdq_resume(struct device *dev)
+{
+	return 0;
+}
+
+static int cmdq_probe(struct platform_device *pdev)
+{
+	struct cmdq *cqctx;
+	int ret = 0;
+
+	/* init cmdq context, and save it */
+	ret = cmdq_core_initialize(pdev, &cqctx);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to init cmdq context\n");
+		return ret;
+	}
+	platform_set_drvdata(pdev, cqctx);
+
+	ret = devm_request_irq(&pdev->dev, cqctx->irq, cmdq_irq_handler,
+			       IRQF_TRIGGER_LOW | IRQF_SHARED,
+			       CMDQ_DRIVER_DEVICE_NAME, cqctx);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register ISR (%d)\n", ret);
+		goto fail;
+	}
+
+	cqctx->clock = devm_clk_get(&pdev->dev, CMDQ_CLK_NAME);
+	if (IS_ERR(cqctx->clock)) {
+		dev_err(&pdev->dev, "failed to get clk:%s\n", CMDQ_CLK_NAME);
+		ret = PTR_ERR(cqctx->clock);
+		goto fail;
+	}
+
+	/* hibernation and suspend events */
+	cqctx->pm_notifier.notifier_call = cmdq_pm_notifier_cb;
+	cqctx->pm_notifier.priority = 5;
+	ret = register_pm_notifier(&cqctx->pm_notifier);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register cmdq pm notifier\n");
+		goto fail;
+	}
+
+	return ret;
+
+fail:
+	cmdq_core_deinitialize(pdev);
+	return ret;
+}
+
+static int cmdq_remove(struct platform_device *pdev)
+{
+	struct cmdq *cqctx = platform_get_drvdata(pdev);
+	int status;
+
+	status = unregister_pm_notifier(&cqctx->pm_notifier);
+	if (status)
+		dev_err(&pdev->dev, "unregister pm notifier failed\n");
+
+	cmdq_core_deinitialize(pdev);
+	return 0;
+}
+
+static const struct dev_pm_ops cmdq_pm_ops = {
+	.suspend = cmdq_suspend,
+	.resume = cmdq_resume,
+};
+
+static const struct of_device_id cmdq_of_ids[] = {
+	{.compatible = "mediatek,mt8173-gce",},
+	{}
+};
+
+static struct platform_driver cmdq_driver = {
+	.probe = cmdq_probe,
+	.remove = cmdq_remove,
+	.driver = {
+		.name = CMDQ_DRIVER_DEVICE_NAME,
+		.owner = THIS_MODULE,
+		.pm = &cmdq_pm_ops,
+		.of_match_table = cmdq_of_ids,
+	}
+};
+
+static int __init cmdq_init(void)
+{
+	int status;
+
+	status = platform_driver_register(&cmdq_driver);
+	if (status) {
+		pr_err("register cmdq driver failed(%d)\n", status);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void __exit cmdq_exit(void)
+{
+	platform_driver_unregister(&cmdq_driver);
+}
+
+subsys_initcall(cmdq_init);
+module_exit(cmdq_exit);
+
+MODULE_DESCRIPTION("Mediatek CMDQ driver");
+MODULE_AUTHOR("HS Liao <hs.liao@mediatek.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/soc/mediatek/cmdq.h b/include/soc/mediatek/cmdq.h
new file mode 100644
index 0000000..2ec349f
--- /dev/null
+++ b/include/soc/mediatek/cmdq.h
@@ -0,0 +1,225 @@ 
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __MTK_CMDQ_H__
+#define __MTK_CMDQ_H__
+
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+enum cmdq_scenario {
+	CMDQ_SCENARIO_PRIMARY_DISP,
+	CMDQ_SCENARIO_SUB_DISP,
+	CMDQ_MAX_SCENARIO_COUNT
+};
+
+enum cmdq_hw_thread_priority {
+	CMDQ_THR_PRIO_NORMAL = 0, /* nomral (low) priority */
+	CMDQ_THR_PRIO_DISPLAY_CONFIG = 3, /* display config (high) priority */
+	CMDQ_THR_PRIO_MAX = 7, /* maximum possible priority */
+};
+
+/* events for CMDQ and display */
+enum cmdq_event {
+	/* Display start of frame(SOF) events */
+	CMDQ_EVENT_DISP_OVL0_SOF = 11,
+	CMDQ_EVENT_DISP_OVL1_SOF = 12,
+	CMDQ_EVENT_DISP_RDMA0_SOF = 13,
+	CMDQ_EVENT_DISP_RDMA1_SOF = 14,
+	CMDQ_EVENT_DISP_RDMA2_SOF = 15,
+	CMDQ_EVENT_DISP_WDMA0_SOF = 16,
+	CMDQ_EVENT_DISP_WDMA1_SOF = 17,
+	/* Display end of frame(EOF) events */
+	CMDQ_EVENT_DISP_OVL0_EOF = 39,
+	CMDQ_EVENT_DISP_OVL1_EOF = 40,
+	CMDQ_EVENT_DISP_RDMA0_EOF = 41,
+	CMDQ_EVENT_DISP_RDMA1_EOF = 42,
+	CMDQ_EVENT_DISP_RDMA2_EOF = 43,
+	CMDQ_EVENT_DISP_WDMA0_EOF = 44,
+	CMDQ_EVENT_DISP_WDMA1_EOF = 45,
+	/* Mutex end of frame(EOF) events */
+	CMDQ_EVENT_MUTEX0_STREAM_EOF = 53,
+	CMDQ_EVENT_MUTEX1_STREAM_EOF = 54,
+	CMDQ_EVENT_MUTEX2_STREAM_EOF = 55,
+	CMDQ_EVENT_MUTEX3_STREAM_EOF = 56,
+	CMDQ_EVENT_MUTEX4_STREAM_EOF = 57,
+	/* Display underrun events */
+	CMDQ_EVENT_DISP_RDMA0_UNDERRUN = 63,
+	CMDQ_EVENT_DISP_RDMA1_UNDERRUN = 64,
+	CMDQ_EVENT_DISP_RDMA2_UNDERRUN = 65,
+	/* Keep this at the end of HW events */
+	CMDQ_MAX_HW_EVENT_COUNT = 260,
+	/* GPR events */
+	CMDQ_SYNC_TOKEN_GPR_SET_0 = 400,
+	CMDQ_SYNC_TOKEN_GPR_SET_1 = 401,
+	CMDQ_SYNC_TOKEN_GPR_SET_2 = 402,
+	CMDQ_SYNC_TOKEN_GPR_SET_3 = 403,
+	CMDQ_SYNC_TOKEN_GPR_SET_4 = 404,
+	/* This is max event and also can be used as mask. */
+	CMDQ_SYNC_TOKEN_MAX = (0x1ff),
+	/* Invalid event */
+	CMDQ_SYNC_TOKEN_INVALID = (-1),
+};
+
+/* called after isr done or task done */
+typedef int (*cmdq_async_flush_cb)(void *data);
+
+struct cmdq_task;
+struct cmdq;
+
+struct cmdq_rec {
+	struct cmdq		*cqctx;
+	u64			engine_flag;
+	int			scenario;
+	u32			block_size; /* command size */
+	void			*buf_ptr;
+	u32			buf_size;
+	/* running task after flush */
+	struct cmdq_task	*running_task_ptr;
+	/*
+	 * HW thread priority
+	 * high priority implies prefetch
+	 */
+	enum cmdq_hw_thread_priority	priority;
+	bool			finalized;
+	u32			prefetch_count;
+};
+
+/**
+ * cmdq_rec_create() - create command queue recorder handle
+ * @pdev:	platform device
+ * @scenario:	command queue scenario
+ * @handle_ptr:	command queue recorder handle pointer to retrieve cmdq_rec
+ *
+ * Return: 0 for success; else the error code is returned
+ */
+int cmdq_rec_create(struct platform_device *pdev,
+		    enum cmdq_scenario scenario,
+		    struct cmdq_rec **handle_ptr);
+
+/**
+ * cmdq_rec_reset() - reset command queue recorder commands
+ * @handle:	the command queue recorder handle
+ *
+ * Return: 0 for success; else the error code is returned
+ */
+int cmdq_rec_reset(struct cmdq_rec *handle);
+
+/**
+ * cmdq_rec_disable_prefetch() - Append mark command to disable prefetch on
+ *				 purpose. We don't have enable function since
+ *				 prefetch is decided automatically.
+ * @handle:	the command queue recorder handle
+ *
+ * Return: 0 for success; else the error code is returned
+ */
+int cmdq_rec_disable_prefetch(struct cmdq_rec *handle);
+
+/**
+ * cmdq_rec_write() - append write command to the command queue recorder
+ * @handle:	the command queue recorder handle
+ * @value:	the specified target register value
+ * @addr:	the specified target register physical address
+ *
+ * Return: 0 for success; else the error code is returned
+ */
+int cmdq_rec_write(struct cmdq_rec *handle, u32 value, u32 addr);
+
+/**
+ * cmdq_rec_write_mask() - append write command with mask to the command queue
+ *			   recorder
+ * @handle:	the command queue recorder handle
+ * @value:	the specified target register value
+ * @addr:	the specified target register physical address
+ * @mask:	the specified target register mask
+ *
+ * Return: 0 for success; else the error code is returned
+ */
+int cmdq_rec_write_mask(struct cmdq_rec *handle, u32 value,
+			u32 addr, u32 mask);
+
+/**
+ * cmdq_rec_wait() - append wait command to the command queue recorder
+ * @handle:	the command queue recorder handle
+ * @event:	the desired event type to "wait and CLEAR"
+ *
+ * Return: 0 for success; else the error code is returned
+ */
+int cmdq_rec_wait(struct cmdq_rec *handle, enum cmdq_event event);
+
+/**
+ * cmdq_rec_clear_event() - append clear event command to the command queue
+ *			    recorder
+ * @handle:	the command queue recorder handle
+ * @event:	the desired event to be cleared
+ *
+ * Return: 0 for success; else the error code is returned
+ */
+int cmdq_rec_clear_event(struct cmdq_rec *handle, enum cmdq_event event);
+
+/**
+ * cmdq_rec_flush() - trigger CMDQ to execute the recorded commands
+ * @handle:	the command queue recorder handle
+ *
+ * Return: 0 for success; else the error code is returned
+ *
+ * Trigger CMDQ to execute the recorded commands. Note that this is a
+ * synchronous flush function. When the function returned, the recorded
+ * commands have been done.
+ */
+int cmdq_rec_flush(struct cmdq_rec *handle);
+
+/**
+ * cmdq_rec_flush_async() - trigger CMDQ to asynchronously execute the
+ *			    recorded commands
+ * @handle:	the command queue recorder handle
+ *
+ * Return: 0 for successfully start execution; else the error code is returned
+ *
+ * Trigger CMDQ to asynchronously execute the recorded commands. Note that this
+ * is an ASYNC function. When the function returned, it may or may not be
+ * finished. There is no way to retrieve the result.
+ */
+int cmdq_rec_flush_async(struct cmdq_rec *handle);
+
+/**
+ * cmdq_rec_flush_async_callback() - trigger CMDQ to asynchronously execute
+ *				     the recorded commands and call back after
+ *				     ISR is finished and this flush is finished
+ * @handle:	the command queue recorder handle
+ * @isr_cb:	called by ISR in the end of CMDQ ISR
+ * @isr_data:	this data will pass back to isr_cb
+ * @done_cb:	called after flush is done
+ * @done_data:	this data will pass back to done_cb
+ *
+ * Return: 0 for success; else the error code is returned
+ *
+ * Trigger CMDQ to asynchronously execute the recorded commands and call back
+ * after ISR is finished and this flush is finished. Note that this is an ASYNC
+ * function. When the function returned, it may or may not be finished. The ISR
+ * callback function is called in the end of ISR, and  the done callback
+ * function is called after all commands are done.
+ */
+int cmdq_rec_flush_async_callback(struct cmdq_rec *handle,
+				  cmdq_async_flush_cb isr_cb,
+				  void *isr_data,
+				  cmdq_async_flush_cb done_cb,
+				  void *done_data);
+
+/**
+ * cmdq_rec_destroy() - destroy command queue recorder handle
+ * @handle:	the command queue recorder handle
+ */
+void cmdq_rec_destroy(struct cmdq_rec *handle);
+
+#endif	/* __MTK_CMDQ_H__ */