// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2021 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cmdq-bdg.h" #if IS_ENABLED(CONFIG_MTK_CMDQ_V3) #include "cmdq_helper_ext.h" #endif #define SYSREG_IRQ_CTRL2 0x1c #define SYSREG_IRQ_MSK_CLR 0x74 #define SYSBUF_BASE 0xA000 #define SYSBUF_SIZE 0x2000 #define CMDQ_SYSBUF_COPY_SIZE 0x80 /* 16 inst */ #define CMDQ_SYSBUF_SIZE (CMDQ_SYSBUF_COPY_SIZE + CMDQ_INST_SIZE) #define GCE_BASE 0x10000 #define GCE_DBG_CTL 0x3000 #define GCE_DBG0 0x3004 #define GCE_DBG2 0x300c #define CMDQ_THR_SLOT_CYCLES 0x30 #define CMDQ_THR_BASE 0x100 #define CMDQ_THR_SIZE 0x80 #define CMDQ_THR_WARM_RESET 0x0 #define CMDQ_THR_ENABLE 0x4 #define CMDQ_THR_SUSPEND 0x8 #define CMDQ_THR_STATUS 0xc #define CMDQ_THR_IRQ_FLAG 0x10 #define CMDQ_THR_IRQ_FLAG_EN 0x14 #define CMDQ_THR_PC 0x20 #define CMDQ_THR_END_ADDR 0x24 #define CMDQ_THR_QOS 0x40 #define CMDQ_THR_SPR0 0x60 #define CMDQ_THR_SLOT_CYCLES_UNIT 64 #define CMDQ_THR_SLOT_CYCLES_COUNT 128 #define CMDQ_THR_IRQ_DONE 0x1 #define CMDQ_THR_IRQ_ERROR 0x12 #define CMDQ_PKT_PRI_AGE 5 #define CMDQ_OP_JUMP_OFFSET 0x10000000 #define CMDQ_OP_JUMP_PA 0x10000001 #define ceil(x, y) (((x) / (y)) + ((x) % (y) ? 1 : 0)) #define CMDQ_SET_ADDR(addr) (((addr) >> 3) | BIT(24)) #define CMDQ_GET_ADDR(addr) (((addr) & ~BIT(24)) << 3) struct cmdq_mmp { mmp_event cmdq; mmp_event thread_enable; mmp_event thread_suspend; mmp_event task_send; mmp_event task_wait; mmp_event task_done; }; struct cmdq_sysbuf { struct list_head list_entry; u32 index; phys_addr_t base; }; struct cmdq { void __iomem *base; phys_addr_t base_pa; u32 irq; struct clk *clock; struct clk *clock_timer; atomic_t usage; struct mbox_controller mbox; struct cmdq_thread thread[CMDQ_THR_MAX_COUNT]; struct workqueue_struct *timeout_wq; struct list_head sysbuf; atomic_t buf_count; struct cmdq_mmp mmp; }; struct cmdq *g_cmdq; struct cmdq_task { struct list_head list_entry; struct cmdq *cmdq; struct cmdq_thread *thread; struct cmdq_pkt *pkt; struct list_head sysbuf; phys_addr_t cmd_base; size_t cmd_size; }; inline u32 spi_read_reg(const u32 addr) { u32 val = UINT_MAX; spislv_read_register(addr, &val); return val; } inline s32 spi_write_reg(const u32 addr, const u32 val) { return spislv_write_register(addr, val); } inline s32 spi_read_mem(const u32 addr, void *val, const s32 len) { return spislv_read(addr, val, len); } inline s32 spi_write_mem(const u32 addr, void *val, const s32 len) { return spislv_write(addr, val, len); } static inline u32 cmdq_bdg_thread_get_reg(struct cmdq_thread *thread, const u32 addr) { return spi_read_reg((u32)thread->base + addr); } static inline void cmdq_bdg_thread_set_reg(struct cmdq_thread *thread, const u32 addr, const u32 val) { spi_write_reg((u32)thread->base + addr, val); } static s32 cmdq_bdg_thread_warm_reset(struct cmdq_thread *thread) { u32 val; cmdq_bdg_thread_set_reg(thread, CMDQ_THR_WARM_RESET, BIT(0)); // readl_poll_timeout_atomic val = cmdq_bdg_thread_get_reg(thread, CMDQ_THR_WARM_RESET); if (!(val & BIT(0))) return 0; cmdq_err("thread:%u warm reset failed:%#x:%#x", thread->idx, CMDQ_THR_WARM_RESET, val); return -EFAULT; } static inline void cmdq_bdg_thread_enable(struct cmdq_thread *thread) { struct cmdq *cmdq = container_of(thread->chan->mbox, typeof(*cmdq), mbox); cmdq_bdg_thread_set_reg(thread, CMDQ_THR_ENABLE, BIT(0)); mmprofile_log_ex(cmdq->mmp.thread_enable, MMPROFILE_FLAG_PULSE, (BIT(0) << 16) | thread->idx, cmdq_bdg_thread_get_reg(thread, CMDQ_THR_STATUS)); } static inline void cmdq_bdg_thread_disable(struct cmdq_thread *thread) { struct cmdq *cmdq = container_of(thread->chan->mbox, typeof(*cmdq), mbox); WARN_ON(cmdq_bdg_thread_warm_reset(thread)); cmdq_bdg_thread_set_reg(thread, CMDQ_THR_ENABLE, 0x0); mmprofile_log_ex(cmdq->mmp.thread_enable, MMPROFILE_FLAG_PULSE, thread->idx, cmdq_bdg_thread_get_reg(thread, CMDQ_THR_STATUS)); } static s32 cmdq_bdg_thread_suspend(struct cmdq_thread *thread) { struct cmdq *cmdq = container_of(thread->chan->mbox, typeof(*cmdq), mbox); u32 val; cmdq_bdg_thread_set_reg(thread, CMDQ_THR_SUSPEND, BIT(0)); mmprofile_log_ex(cmdq->mmp.thread_suspend, MMPROFILE_FLAG_PULSE, (BIT(0) << 16) | thread->idx, cmdq_bdg_thread_get_reg(thread, CMDQ_THR_STATUS)); val = cmdq_bdg_thread_get_reg(thread, CMDQ_THR_ENABLE); if (!(val & BIT(0))) return 0; // readl_poll_timeout_atomic val = cmdq_bdg_thread_get_reg(thread, CMDQ_THR_STATUS); if (val & BIT(1)) return 0; cmdq_err("thread:%u suspend failed:%#x:%#x", thread->idx, CMDQ_THR_STATUS, val); return -EFAULT; } static inline void cmdq_bdg_thread_resume(struct cmdq_thread *thread) { struct cmdq *cmdq = container_of(thread->chan->mbox, typeof(*cmdq), mbox); cmdq_bdg_thread_set_reg(thread, CMDQ_THR_SUSPEND, 0x0); mmprofile_log_ex(cmdq->mmp.thread_suspend, MMPROFILE_FLAG_PULSE, thread->idx, cmdq_bdg_thread_get_reg(thread, CMDQ_THR_STATUS)); } static inline phys_addr_t cmdq_bdg_thread_get_pc(struct cmdq_thread *thread) { return CMDQ_GET_ADDR(cmdq_bdg_thread_get_reg(thread, CMDQ_THR_PC)); } static inline void cmdq_bdg_thread_set_pc(struct cmdq_thread *thread, const phys_addr_t val) { cmdq_bdg_thread_set_reg(thread, CMDQ_THR_PC, CMDQ_SET_ADDR(val)); } static inline phys_addr_t cmdq_bdg_thread_get_end(struct cmdq_thread *thread) { return CMDQ_GET_ADDR( cmdq_bdg_thread_get_reg(thread, CMDQ_THR_END_ADDR)); } static inline void cmdq_bdg_thread_set_end(struct cmdq_thread *thread, const phys_addr_t val) { cmdq_bdg_thread_set_reg(thread, CMDQ_THR_END_ADDR, CMDQ_SET_ADDR(val)); } static s32 cmdq_bdg_clk_enable(struct cmdq_thread *thread) { struct cmdq *cmdq = container_of(thread->chan->mbox, typeof(*cmdq), mbox); s32 ret = atomic_read(&cmdq->usage); if (ret & BIT(thread->idx)) { cmdq_err("cmdq:%pa thread:%u usage:%#x have enabled", &cmdq->base_pa, thread->idx, ret); return ret; } atomic_add_return(BIT(thread->idx), &cmdq->usage); cmdq_msg("%s: cmdq:%pa thread:%u usage:%#x", __func__, &cmdq->base_pa, thread->idx, atomic_read(&cmdq->usage)); return 0; } static void cmdq_bdg_clk_disable(struct cmdq_thread *thread) { struct cmdq *cmdq = container_of(thread->chan->mbox, typeof(*cmdq), mbox); s32 ret = atomic_read(&cmdq->usage); if (!(ret | BIT(thread->idx))) { cmdq_err("cmdq:%pa thread:%u usage:%#x have disabled", &cmdq->base_pa, thread->idx, ret); return; } atomic_sub_return(BIT(thread->idx), &cmdq->usage); cmdq_msg("%s: cmdq:%pa thread:%u usage:%#x", __func__, &cmdq->base_pa, thread->idx, atomic_read(&cmdq->usage)); } static void cmdq_bdg_dump_sysbuf(const phys_addr_t base, const size_t size) { u64 *command; s32 i; command = kzalloc(size, GFP_KERNEL); if (!command) return; cmdq_msg("%s: base:%pa size:%ld", __func__, &base, size); spi_read_mem(base, command, size); for (i = 0; i < size / CMDQ_INST_SIZE; i++) cmdq_msg("%s: inst[%d]:%#llx", __func__, i, *(command + i)); kfree(command); } static inline phys_addr_t cmdq_bdg_task_get_end(struct cmdq_task *task) { struct cmdq_sysbuf *buf = list_last_entry(&task->sysbuf, typeof(*buf), list_entry); return buf->base + (task->cmd_size % CMDQ_SYSBUF_SIZE); } static bool cmdq_bdg_task_running(struct cmdq_task *task, const phys_addr_t pa) { struct cmdq_sysbuf *buf, *temp; phys_addr_t end; list_for_each_entry_safe(buf, temp, &task->sysbuf, list_entry) { if (list_is_last(&buf->list_entry, &task->sysbuf)) end = buf->base + (task->cmd_size % CMDQ_SYSBUF_SIZE); else end = buf->base + CMDQ_SYSBUF_SIZE; if (pa >= buf->base && pa < end) return true; } return false; } void cmdq_bdg_client_get_irq(struct cmdq_client *cl, u32 *irq) { *irq = cmdq_bdg_thread_get_reg(cl->chan->con_priv, CMDQ_THR_IRQ_FLAG); } EXPORT_SYMBOL(cmdq_bdg_client_get_irq); static inline void cmdq_bdg_dump_gce(void) { const u32 irq = spi_read_reg(GCE_BASE + CMDQ_THR_IRQ_FLAG); #if 0 u32 dbg0[3], dbg2[6], i; for (i = 0; i < 6; i++) { if (i < 3) { spi_write_reg(GCE_BASE + GCE_DBG_CTL, (i << 8) | i); dbg0[i] = spi_read_reg(GCE_BASE + GCE_DBG0); } else spi_write_reg(GCE_BASE + GCE_DBG_CTL, (i << 8)); dbg2[i] = spi_read_reg(GCE_BASE + GCE_DBG2); } cmdq_msg( "%s: gce:%#x irq:%#x dbg0:%#x %#x %#x dbg2:%#x %#x %#x %#x %#x %#x", __func__, GCE_BASE, irq, dbg0[0], dbg0[1], dbg0[2], dbg2[0], dbg2[1], dbg2[2], dbg2[3], dbg2[4], dbg2[5]); #endif cmdq_msg("%s: gce:%#x irq:%#x", __func__, GCE_BASE, irq); } static inline u32 cmdq_bdg_dump_thread(struct cmdq_thread *thread) { u32 val[10], pc, end, spr[4]; u64 inst; spi_read_mem( (u32)thread->base + CMDQ_THR_STATUS, val, sizeof(u32) * 10); pc = cmdq_bdg_thread_get_pc(thread); end = cmdq_bdg_thread_get_end(thread); spi_read_mem((u32)thread->base + CMDQ_THR_SPR0, spr, sizeof(u32) * 4); spi_read_mem(pc, &inst, CMDQ_INST_SIZE); cmdq_msg( "%s:%u priority:%d status:%#x irq:%#x pc:%#x inst:%#llx end:%#x cookie:%#x wait:%#x spr:%#x %#x %#x %#x", __func__, thread->idx, thread->priority, val[0], val[1], pc, inst, end, val[7], val[9], spr[0], spr[1], spr[2], spr[3]); return pc; } #if IS_ENABLED(CONFIG_MTK_CMDQ_V3) void cmdq_bdg_dump_handle(struct cmdqRecStruct *rec, const char *tag) { #if IS_ENABLED(CONFIG_MTK_CMDQ_V3) struct cmdq_client *client = (struct cmdq_client *)rec->pkt->cl; u32 pc, base; cmdq_msg("%s: rec:%p pkt:%p thread:%d scenario:%d engine:%#llx", __func__, rec, rec->pkt, rec->thread, rec->scenario, rec->engineFlag); cmdq_bdg_dump_gce(); pc = cmdq_bdg_dump_thread(client->chan->con_priv); if (!strncmp(tag, "ERR", 3)) { base = SYSBUF_BASE + CMDQ_SYSBUF_SIZE * ((pc - SYSBUF_BASE) / CMDQ_SYSBUF_SIZE); cmdq_bdg_dump_sysbuf(base, pc - base + CMDQ_INST_SIZE); } #else cmdq_msg("%s: rec:%p CMDQ_V3 not support", __func__, ptr); #endif } EXPORT_SYMBOL(cmdq_bdg_dump_handle); #endif static void cmdq_bdg_task_callback(struct cmdq_pkt *pkt, const s32 err) { struct cmdq_cb_data data; if (pkt && pkt->cb.cb) { data.err = err; data.data = pkt->cb.data; pkt->cb.cb(data); } } static void cmdq_bdg_task_error_callback(struct cmdq_pkt *pkt, const s32 err) { struct cmdq_cb_data data; if (pkt && pkt->err_cb.cb) { data.err = err; data.data = pkt->err_cb.data; pkt->err_cb.cb(data); } } static void cmdq_bdg_task_done(struct cmdq_task *task, const s32 err) { struct cmdq_sysbuf *buf, *temp; s32 count = 0; cmdq_bdg_task_callback(task->pkt, err); list_del_init(&task->list_entry); mmprofile_log_ex(task->cmdq->mmp.task_done, MMPROFILE_FLAG_PULSE, ((s16)err << 16) | task->thread->idx, (unsigned long)task->pkt); if (task->pkt->reuse) { cmdq_msg("%s: thread:%u pkt:%p task:%p err:%d reuse", __func__, task->thread->idx, task->pkt, task, err); return; } list_for_each_entry_safe(buf, temp, &task->sysbuf, list_entry) { list_move_tail(&buf->list_entry, &task->cmdq->sysbuf); count += 1; } atomic_add_return(count, &task->cmdq->buf_count); kfree(task); } static void cmdq_bdg_thread_irq_handler(struct cmdq_thread *thread) { struct cmdq_task *task, *temp, *curr = NULL, *next = NULL; phys_addr_t pc, end; u32 irq; if (!(cmdq_bdg_thread_get_reg(thread, CMDQ_THR_ENABLE) & BIT(0))) { cmdq_msg("%s: thread:%u disable", __func__, thread->idx); return; } pc = cmdq_bdg_thread_get_pc(thread); end = cmdq_bdg_thread_get_end(thread); irq = cmdq_bdg_thread_get_reg(thread, CMDQ_THR_IRQ_FLAG); cmdq_bdg_thread_set_reg(thread, CMDQ_THR_IRQ_FLAG, ~irq); cmdq_msg("%s: thread:%u irq:%#x pc:%pa end:%pa", __func__, thread->idx, irq, &pc, &end); irq &= (CMDQ_THR_IRQ_DONE | CMDQ_THR_IRQ_ERROR); if (!irq) return; task = list_first_entry_or_null( &thread->task_busy_list, typeof(*task), list_entry); if (task && task->pkt->loop) { cmdq_bdg_task_callback(task->pkt, irq); cmdq_msg("%s: thread:%u loop:%d", __func__, thread->idx, task->pkt->loop); return; } list_for_each_entry_safe( task, temp, &thread->task_busy_list, list_entry) { if (cmdq_bdg_task_running(task, pc)) curr = task; if (!curr || pc == cmdq_bdg_task_get_end(task) - CMDQ_INST_SIZE) { cmdq_bdg_task_done(task, 0); } else if (irq & CMDQ_THR_IRQ_ERROR) { u64 inst; spi_read_mem(pc, &inst, CMDQ_INST_SIZE); cmdq_err("thread:%u irq:%#x pkt:%p pc:%pa inst:%#llx", thread->idx, irq, task->pkt, &pc, inst); cmdq_bdg_task_done(task, -EINTR); next = list_first_entry_or_null(&thread->task_busy_list, typeof(*next), list_entry); if (next) cmdq_bdg_thread_set_pc(thread, next->cmd_base); } if (curr) break; } if (!list_empty(&thread->task_busy_list)) { mod_timer(&thread->timeout, jiffies + msecs_to_jiffies(thread->timeout_ms)); thread->timer_mod = sched_clock(); cmdq_msg("%s: thread:%u timeout_ms:%u reset timer", __func__, thread->idx, thread->timeout_ms); } else cmdq_msg("%s: thread:%u without task", __func__, thread->idx); } s32 cmdq_bdg_irq_handler(void) { struct cmdq *cmdq = g_cmdq; struct cmdq_thread *thread; s32 bit, ret = atomic_read(&cmdq->usage); unsigned long irq; if (!ret) cmdq_err("cmdq:%pa usage:%#x have disabled", &cmdq->base_pa, ret); irq = spi_read_reg(cmdq->base_pa + CMDQ_THR_IRQ_FLAG); cmdq_msg("%s: cmdq:%pa usage:%#x irq:%#x:%#x", __func__, &cmdq->base_pa, ret, CMDQ_THR_IRQ_FLAG, irq); if (irq == UINT_MAX) return IRQ_NONE; ret = IRQ_HANDLED; for_each_clear_bit(bit, &irq, 32) { thread = &cmdq->thread[bit]; cmdq_msg("%s: bit:%d thread:%u occupied:%d", __func__, bit, thread->idx, thread->occupied); if (!thread->occupied) { ret = IRQ_NONE; continue; } cmdq_bdg_thread_irq_handler(thread); } spi_write_reg(cmdq->base_pa + CMDQ_THR_IRQ_FLAG, UINT_MAX); cmdq_msg("%s: cmdq:%pa usage:%#x irq:%#x:%#x", __func__, &cmdq->base_pa, ret, CMDQ_THR_IRQ_FLAG, spi_read_reg(cmdq->base_pa + CMDQ_THR_IRQ_FLAG)); return ret; } EXPORT_SYMBOL(cmdq_bdg_irq_handler); static void cmdq_bdg_thread_timeout_work(struct work_struct *work) { struct cmdq_thread *thread = container_of(work, typeof(*thread), timeout_work); struct cmdq *cmdq = container_of(thread->chan->mbox, typeof(*cmdq), mbox); struct cmdq_task *task, *temp, *next; s32 ret = atomic_read(&cmdq->usage); u64 duration; phys_addr_t pc; if (!(ret | BIT(thread->idx))) cmdq_err("cmdq:%pa thread:%u usage:%#x have disabled", &cmdq->base_pa, thread->idx, ret); if (list_empty(&thread->task_busy_list)) { cmdq_msg("%s: thread:%u without task", __func__, thread->idx); cmdq_bdg_thread_disable(thread); cmdq_bdg_clk_disable(thread); return; } WARN_ON(cmdq_bdg_thread_suspend(thread)); cmdq_bdg_thread_irq_handler(thread); pc = cmdq_bdg_thread_get_pc(thread); list_for_each_entry_safe( task, temp, &thread->task_busy_list, list_entry) { if (cmdq_bdg_task_running(task, pc)) { u64 inst; spi_read_mem(pc, &inst, CMDQ_INST_SIZE); cmdq_err("thread:%u pkt:%p pc:%pa inst:%#llx", thread->idx, task->pkt, &pc, inst); cmdq_bdg_task_error_callback(task->pkt, -ETIMEDOUT); cmdq_bdg_task_done(task, -ETIMEDOUT); next = list_first_entry_or_null(&thread->task_busy_list, typeof(*next), list_entry); if (next) cmdq_bdg_thread_set_pc(thread, next->cmd_base); break; } cmdq_bdg_task_done(task, 0); } if (list_empty(&thread->task_busy_list)) { cmdq_msg("%s: thread:%u without task after irq handler", __func__, thread->idx); cmdq_bdg_thread_disable(thread); cmdq_bdg_clk_disable(thread); return; } // cmdq_bdg_thread_timeout_excceed duration = div_s64(sched_clock() - thread->timer_mod, 1000000); if (duration < thread->timeout_ms) mod_timer(&thread->timeout, jiffies + msecs_to_jiffies(thread->timeout_ms - duration)); else mod_timer(&thread->timeout, jiffies + msecs_to_jiffies(thread->timeout_ms)); thread->timer_mod = sched_clock(); cmdq_msg("%s: thread:%u timeout_ms:%u duration:%llu", __func__, thread->idx, thread->timeout_ms, duration); cmdq_bdg_thread_resume(thread); } static void cmdq_bdg_thread_timeout(struct timer_list *timer) { struct cmdq_thread *thread = from_timer(thread, timer, timeout); struct cmdq *cmdq = container_of(thread->chan->mbox, typeof(*cmdq), mbox); if (list_empty(&thread->task_busy_list)) { cmdq_msg("%s: thread:%u without task", __func__, thread->idx); return; } if (!work_pending(&thread->timeout_work)) { cmdq_msg("%s: thread:%u queue work", __func__, thread->idx); queue_work(cmdq->timeout_wq, &thread->timeout_work); } else cmdq_msg("%s: thread:%u ignore timeout", __func__, thread->idx); } static void cmdq_bdg_thread_shutdown(struct cmdq_thread *thread) { struct cmdq *cmdq = container_of(thread->chan->mbox, typeof(*cmdq), mbox); struct cmdq_task *task, *temp; if (list_empty(&thread->task_busy_list)) { cmdq_msg("%s: thread:%u without task", __func__, thread->idx); return; } WARN_ON(cmdq_bdg_thread_suspend(thread)); cmdq_bdg_thread_irq_handler(thread); list_for_each_entry_safe( task, temp, &thread->task_busy_list, list_entry) { cmdq_bdg_task_done(task, -ECONNABORTED); } cmdq_bdg_thread_disable(thread); cmdq_bdg_clk_disable(thread); } void cmdq_bdg_client_shutdown(struct cmdq_client *cl) { cmdq_bdg_thread_shutdown(cl->chan->con_priv); } EXPORT_SYMBOL(cmdq_bdg_client_shutdown); static inline void cmdq_bdg_mbox_write_sysbuf(struct cmdq_task *task) { struct cmdq_pkt *pkt = task->pkt; struct cmdq_pkt_buffer *pkt_buf, *pkt_temp; struct cmdq_sysbuf *buf, *temp; s32 count, remain; remain = CMDQ_CMD_BUFFER_SIZE / CMDQ_SYSBUF_COPY_SIZE; buf = list_first_entry(&task->sysbuf, typeof(*buf), list_entry); list_for_each_entry_safe(pkt_buf, pkt_temp, &pkt->buf, list_entry) { if (list_is_last(&pkt_buf->list_entry, &pkt->buf)) remain = ceil(pkt->cmd_buf_size % CMDQ_CMD_BUFFER_SIZE, CMDQ_SYSBUF_COPY_SIZE); cmdq_msg("%s: pkt_buf va:%p pa:%pa remain:%d", __func__, pkt_buf->va_base, &pkt_buf->pa_base, remain); count = 0; list_for_each_entry_safe_from( buf, temp, &task->sysbuf, list_entry) { u64 inst = ((u64)CMDQ_OP_JUMP_PA << 32) | CMDQ_SET_ADDR(temp->base); cmdq_msg("%s: count:%d remain:%d buf(%d):%pa", __func__, count, remain, buf->index, &buf->base); if (list_is_last(&buf->list_entry, &task->sysbuf)) { spi_write_mem(buf->base, pkt_buf->va_base + CMDQ_SYSBUF_COPY_SIZE * count, task->cmd_size % CMDQ_SYSBUF_SIZE); break; } spi_write_mem(buf->base, pkt_buf->va_base + CMDQ_SYSBUF_COPY_SIZE * count, CMDQ_SYSBUF_COPY_SIZE); if (++count == remain) { // replace jump cmdq_msg("%s:jump pc:%pa:%#x inst:%#llx", __func__, &buf->base, CMDQ_SYSBUF_COPY_SIZE - CMDQ_INST_SIZE, inst); spi_write_mem(buf->base + CMDQ_SYSBUF_COPY_SIZE - CMDQ_INST_SIZE, &inst, CMDQ_INST_SIZE); break; } // insert jump cmdq_msg("%s:jump pc:%pa:%#x inst:%#llx", __func__, &buf->base, CMDQ_SYSBUF_COPY_SIZE, inst); spi_write_mem(buf->base + CMDQ_SYSBUF_COPY_SIZE, &inst, CMDQ_INST_SIZE); } } if (pkt->loop) { u64 inst = ((u64)CMDQ_OP_JUMP_PA << 32) | CMDQ_SET_ADDR(task->cmd_base); phys_addr_t end = cmdq_bdg_task_get_end(task) - CMDQ_INST_SIZE; spi_write_mem(end, &inst, CMDQ_INST_SIZE); } cmdq_bdg_dump_sysbuf(buf->base, task->cmd_size % CMDQ_SYSBUF_SIZE); } static inline void cmdq_bdg_task_connect(struct cmdq_task *task, struct cmdq_task *next) { phys_addr_t end; u64 val = ((u64)CMDQ_OP_JUMP_PA << 32) | CMDQ_SET_ADDR(next->cmd_base); u64 inst[2]; end = cmdq_bdg_task_get_end(task) - CMDQ_INST_SIZE; spi_read_mem(end, &inst[0], CMDQ_INST_SIZE); spi_write_mem(end, &val, CMDQ_INST_SIZE); spi_read_mem(end, &inst[1], CMDQ_INST_SIZE); cmdq_msg("%s: end:%pa inst:%#llx val:%#llx inst:%#llx", __func__, &end, inst[0], val, inst[1]); } static inline void cmdq_bdg_task_insert_thread(struct cmdq_task *task, const phys_addr_t pa, struct list_head **pos) { struct cmdq_task *prev = NULL, *curr, *next = NULL; list_for_each_entry_reverse( curr, &task->thread->task_busy_list, list_entry) { prev = curr; if (next) next->pkt->priority += CMDQ_PKT_PRI_AGE; if (cmdq_bdg_task_running(curr, pa)) break; if (curr->pkt->priority >= task->pkt->priority) break; next = curr; } *pos = &prev->list_entry; cmdq_bdg_task_connect(prev, task); if (next) cmdq_bdg_task_connect(task, next); } static int cmdq_bdg_mbox_send_data(struct mbox_chan *chan, void *data) { struct cmdq *cmdq = dev_get_drvdata(chan->mbox->dev); struct cmdq_thread *thread = chan->con_priv; struct cmdq_pkt *pkt = data; struct cmdq_task *task, *last; struct cmdq_sysbuf *buf, *temp; struct list_head *pos; phys_addr_t pc, end; unsigned long flags; s32 count, remain; if (!pkt) { cmdq_msg("%s: pkt:%p", __func__, pkt); dump_stack(); return 0; } if (list_empty(&pkt->buf)) { cmdq_err("thread:%u pkt:%p without command", thread->idx, pkt); dump_stack(); cmdq_bdg_task_callback(pkt, -EINVAL); return -EINVAL; } task = pkt->bdg_data ? (struct cmdq_task *)pkt->bdg_data : NULL; if (task) { if (!pkt->reuse) { cmdq_msg("%s: thread:%u pkt:%p task:%p reuse done", __func__, thread->idx, pkt, task); cmdq_bdg_task_done(task, 0); return 0; } if (task->list_entry.next != &task->list_entry) { cmdq_err("thread:%u pkt:%p task:%p in list", thread->idx, pkt, task); cmdq_bdg_task_callback(pkt, -EEXIST); return -EEXIST; } cmdq_msg("%s: thread:%u pkt:%p data:%p task:%p existed", __func__, thread->idx, pkt, pkt->bdg_data, task); } else { task = kzalloc(sizeof(*task), GFP_ATOMIC); if (!task) { cmdq_bdg_task_callback(pkt, -ENOMEM); return -ENOMEM; } pkt->task_alloc = true; task->cmdq = cmdq; task->thread = thread; task->pkt = pkt; INIT_LIST_HEAD(&task->sysbuf); // allocate sysbuf task->cmd_size = pkt->cmd_buf_size + CMDQ_INST_SIZE * (ceil(pkt->cmd_buf_size, CMDQ_SYSBUF_COPY_SIZE) - 1); count = ceil(task->cmd_size, CMDQ_SYSBUF_SIZE); remain = atomic_read(&cmdq->buf_count); if (remain < count) { cmdq_err( "thread:%u pkt:%p cmd_buf_size:%ld cmd_size:%ld count:%d without enough sysbuf:%d", thread->idx, pkt, pkt->cmd_buf_size, task->cmd_size, count, remain); cmdq_bdg_task_callback(pkt, -ENOMEM); kfree(task); return -ENOMEM; } remain = atomic_sub_return(count, &cmdq->buf_count); cmdq_msg( "%s: thread:%u pkt:%p cmd_buf_size:%ld cmd_size:%ld count:%d remain:%d", __func__, thread->idx, pkt, pkt->cmd_buf_size, task->cmd_size, count, remain); list_for_each_entry_safe(buf, temp, &cmdq->sysbuf, list_entry) { list_move_tail(&buf->list_entry, &task->sysbuf); if (!task->cmd_base) task->cmd_base = buf->base; if (!--count) break; } spin_unlock_irqrestore(&thread->chan->lock, flags); cmdq_bdg_mbox_write_sysbuf(task); spin_lock_irqsave(&thread->chan->lock, flags); pkt->bdg_data = (void *)task; } // insert task spin_unlock_irqrestore(&thread->chan->lock, flags); if (list_empty(&thread->task_busy_list)) { WARN_ON(cmdq_bdg_clk_enable(thread)); WARN_ON(cmdq_bdg_thread_warm_reset(thread)); spi_write_reg(cmdq->base_pa + CMDQ_THR_SLOT_CYCLES, CMDQ_THR_SLOT_CYCLES_COUNT * CMDQ_THR_SLOT_CYCLES_UNIT); cmdq_bdg_thread_set_reg(thread, CMDQ_THR_QOS, thread->priority); cmdq_bdg_thread_set_end(thread, cmdq_bdg_task_get_end(task)); cmdq_bdg_thread_set_pc(thread, task->cmd_base); cmdq_bdg_thread_set_reg(thread, CMDQ_THR_IRQ_FLAG_EN, CMDQ_THR_IRQ_DONE | CMDQ_THR_IRQ_ERROR); if (thread->timeout_ms != CMDQ_NO_TIMEOUT) { mod_timer(&thread->timeout, jiffies + msecs_to_jiffies(thread->timeout_ms)); thread->timer_mod = sched_clock(); } list_add_tail(&task->list_entry, &thread->task_busy_list); pc = cmdq_bdg_thread_get_pc(thread); end = cmdq_bdg_thread_get_end(thread); // irq clear spislv_write_register_mask( SYSREG_IRQ_MSK_CLR, BIT(10), BIT(10)); cmdq_msg("%s:single pkt:%p thread:%u pc:%pa end:%pa irq:%#x", __func__, pkt, thread->idx, &pc, &end, spi_read_reg(SYSREG_IRQ_CTRL2)); cmdq_bdg_thread_enable(thread); } else { WARN_ON(cmdq_bdg_thread_suspend(thread)); pc = cmdq_bdg_thread_get_pc(thread); end = cmdq_bdg_thread_get_end(thread); if (pc == end - CMDQ_INST_SIZE || pc == end) { cmdq_bdg_thread_set_pc(thread, task->cmd_base); list_add_tail( &task->list_entry, &thread->task_busy_list); } else { cmdq_bdg_task_insert_thread(task, pc, &pos); cmdq_bdg_thread_set_pc(task->thread, cmdq_bdg_thread_get_pc(task->thread)); list_add(&task->list_entry, pos); } last = list_last_entry( &thread->task_busy_list, typeof(*last), list_entry); cmdq_bdg_thread_set_end(thread, cmdq_bdg_task_get_end(last)); pc = cmdq_bdg_thread_get_pc(thread); end = cmdq_bdg_thread_get_end(thread); cmdq_msg("%s:multiple pkt:%p thread:%u pc:%pa end:%pa", __func__, pkt, thread->idx, &pc, &end); cmdq_bdg_thread_resume(thread); } mmprofile_log_ex(cmdq->mmp.task_send, MMPROFILE_FLAG_PULSE, thread->idx, (unsigned long)pkt); spin_lock_irqsave(&thread->chan->lock, flags); return 0; } static int cmdq_bdg_mbox_startup(struct mbox_chan *chan) { struct cmdq_thread *thread = chan->con_priv; thread->occupied = true; return 0; } static void cmdq_bdg_mbox_shutdown(struct mbox_chan *chan) { struct cmdq_thread *thread = chan->con_priv; cmdq_bdg_thread_shutdown(thread); thread->occupied = false; } static bool cmdq_bdg_mbox_last_tx_done(struct mbox_chan *chan) { return true; } static struct mbox_chan *cmdq_bdg_xlate(struct mbox_controller *mbox, const struct of_phandle_args *sp) { struct cmdq_thread *thread; s32 idx = sp->args[0]; if (idx >= mbox->num_chans) return ERR_PTR(-EINVAL); thread = mbox->chans[idx].con_priv; thread->chan = &mbox->chans[idx]; thread->timeout_ms = sp->args[1] ? sp->args[1] : CMDQ_TIMEOUT_DEFAULT; thread->priority = sp->args[2]; return &mbox->chans[idx]; } void cmdq_bdg_mmprofile_task_wait(struct mbox_chan *chan, struct cmdq_pkt *pkt) { struct cmdq_thread *thread = chan->con_priv; struct cmdq *cmdq = container_of(chan->mbox, typeof(*cmdq), mbox); mmprofile_log_ex(cmdq->mmp.task_wait, MMPROFILE_FLAG_PULSE, thread->idx, (unsigned long)pkt); } static const struct mbox_chan_ops cmdq_bdg_mbox_chan_ops = { .send_data = cmdq_bdg_mbox_send_data, .startup = cmdq_bdg_mbox_startup, .shutdown = cmdq_bdg_mbox_shutdown, .last_tx_done = cmdq_bdg_mbox_last_tx_done, }; static int cmdq_bdg_suspend(struct device *dev) { return 0; } static int cmdq_bdg_resume(struct device *dev) { return 0; } static const struct dev_pm_ops cmdq_bdg_pm_ops = { .suspend = cmdq_bdg_suspend, .resume = cmdq_bdg_resume, }; static inline void cmdq_bdg_mmp_init(struct cmdq *cmdq) { mmprofile_enable(true); if (cmdq->mmp.cmdq) { mmprofile_start(true); return; } cmdq->mmp.cmdq = mmprofile_register_event(MMP_ROOT_EVENT, "cmdq_bdg"); cmdq->mmp.thread_enable = mmprofile_register_event(cmdq->mmp.cmdq, "thread_enable"); cmdq->mmp.thread_suspend = mmprofile_register_event(cmdq->mmp.cmdq, "thread_suspend"); cmdq->mmp.task_send = mmprofile_register_event(cmdq->mmp.cmdq, "task_send"); cmdq->mmp.task_wait = mmprofile_register_event(cmdq->mmp.cmdq, "task_wait"); cmdq->mmp.task_done = mmprofile_register_event(cmdq->mmp.cmdq, "task_done"); mmprofile_enable_event_recursive(cmdq->mmp.cmdq, true); mmprofile_start(true); } static int cmdq_bdg_probe(struct platform_device *pdev) { struct cmdq_sysbuf *buf; struct cmdq *cmdq; s32 i, ret = 0; cmdq = devm_kzalloc(&pdev->dev, sizeof(*cmdq), GFP_KERNEL); if (!cmdq) return -ENOMEM; cmdq->base = (void *)GCE_BASE; cmdq->base_pa = GCE_BASE; cmdq->mbox.chans = devm_kcalloc(&pdev->dev, CMDQ_THR_MAX_COUNT, sizeof(*cmdq->mbox.chans), GFP_KERNEL); if (!cmdq->mbox.chans) return -ENOMEM; for (i = 0; i < ARRAY_SIZE(cmdq->thread); i++) { cmdq->thread[i].base = cmdq->base + CMDQ_THR_BASE + CMDQ_THR_SIZE * i; cmdq->thread[i].gce_pa = cmdq->base_pa; INIT_LIST_HEAD(&cmdq->thread[i].task_busy_list); timer_setup(&cmdq->thread[i].timeout, cmdq_bdg_thread_timeout, 0); INIT_WORK(&cmdq->thread[i].timeout_work, cmdq_bdg_thread_timeout_work); cmdq->thread[i].idx = i; cmdq->mbox.chans[i].con_priv = &cmdq->thread[i]; } cmdq->mbox.dev = &pdev->dev; cmdq->mbox.ops = &cmdq_bdg_mbox_chan_ops; cmdq->mbox.num_chans = CMDQ_THR_MAX_COUNT; cmdq->mbox.of_xlate = cmdq_bdg_xlate; ret = mbox_controller_register(&cmdq->mbox); if (ret) { cmdq_err("mbox_controller_register failed:%d", ret); return ret; } cmdq->timeout_wq = create_singlethread_workqueue("cmdq_bdg_timeout_wq"); // cmdq->buf_dump_wq INIT_LIST_HEAD(&cmdq->sysbuf); for (i = 0; i < SYSBUF_SIZE / CMDQ_SYSBUF_SIZE; i++) { buf = kzalloc(sizeof(*buf), GFP_KERNEL); if (!buf) return -ENOMEM; buf->index = i; buf->base = SYSBUF_BASE + CMDQ_SYSBUF_SIZE * i; list_add_tail(&buf->list_entry, &cmdq->sysbuf); } atomic_set(&cmdq->buf_count, SYSBUF_SIZE / CMDQ_SYSBUF_SIZE); cmdq_bdg_mmp_init(cmdq); platform_set_drvdata(pdev, cmdq); cmdq_msg("%s: pdev:%p buf_count:%d", __func__, pdev, atomic_read(&cmdq->buf_count)); g_cmdq = cmdq; return ret; } static int cmdq_bdg_remove(struct platform_device *pdev) { return 0; } static const struct of_device_id cmdq_bdg_of_ids[] = { {.compatible = "mediatek,mailbox-gce-bdg",}, {} }; static struct platform_driver cmdq_bdg_drv = { .probe = cmdq_bdg_probe, .remove = cmdq_bdg_remove, .driver = { .name = "cmdq-bdg-mailbox", .of_match_table = cmdq_bdg_of_ids, .pm = &cmdq_bdg_pm_ops, }, }; static __init int cmdq_bdg_init(void) { s32 ret = platform_driver_register(&cmdq_bdg_drv); if (ret) cmdq_err("platform_driver_register failed:%d", ret); return ret; } arch_initcall(cmdq_bdg_init);