From 0cad7a04fbffc1e17c26460d9e701abdff59510b Mon Sep 17 00:00:00 2001 From: Allen-KH Cheng Date: Fri, 27 Aug 2021 14:21:40 +0800 Subject: [PATCH] drivers: mtk: add drivers for mt8195 Add irq, ipc and timer drivers for mtk mt8195 About mt8195: Two domains of IRQ: IRQ_EXT_DOMAIN0 and IRQ_EXT_DOMAIN1 IRQ num: 25 interrupts IPC: use mbx0 and mbx1 to transfer ipc msg Signed-off-by: YC Hung Signed-off-by: Allen-KH Cheng --- src/drivers/mediatek/mt8195/interrupt.c | 266 ++++++++++++++++++++++++ src/drivers/mediatek/mt8195/ipc.c | 156 ++++++++++++++ src/drivers/mediatek/mt8195/timer.c | 104 +++++++++ 3 files changed, 526 insertions(+) create mode 100644 src/drivers/mediatek/mt8195/interrupt.c create mode 100644 src/drivers/mediatek/mt8195/ipc.c create mode 100644 src/drivers/mediatek/mt8195/timer.c diff --git a/src/drivers/mediatek/mt8195/interrupt.c b/src/drivers/mediatek/mt8195/interrupt.c new file mode 100644 index 000000000..6b49387fa --- /dev/null +++ b/src/drivers/mediatek/mt8195/interrupt.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2021 Mediatek +// +// Author: Allen-KH Cheng + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* fa00558c-d653-4851-a03a-b21f125a9524 */ +DECLARE_SOF_UUID("irq-mt8195", irq_mt8195_uuid, 0xfa00558c, 0xd653, 0x4851, + 0xa0, 0x3a, 0xb2, 0x1f, 0x12, 0x5a, 0x95, 0x24); + +DECLARE_TR_CTX(int_tr, SOF_UUID(irq_mt8195_uuid), LOG_LEVEL_INFO); + +/* os timer reg value * 77ns 13M os timer + * 1 ms: 12987* 1.5ms: 19436 + * 2 ms: 25974* 3 ms: 38961 + */ + +static void irq_mask_all(void) +{ + io_reg_update_bits(RG_DSP_IRQ_EN, 0xffffffff, 0x0); + io_reg_update_bits(DSP_IRQ_EN, 0xffffffff, 0x0); +} + +static void mtk_irq_mask(struct irq_desc *desc, uint32_t irq, unsigned int core) +{ + int32_t in_irq, level; + + if (!desc) { + level = GET_INTLEVEL(irq); + in_irq = GET_INTERRUPT_ID(irq); + if (level > MAX_IRQ_NUM) { + tr_err(&int_tr, "Invalid interrupt %d", irq); + return; + } + + io_reg_update_bits(RG_DSP_IRQ_EN, BIT(in_irq), 0x0); + } else { + switch (desc->irq) { + case IRQ_EXT_DOMAIN0: + io_reg_update_bits(RG_DSP_IRQ_EN, BIT(irq + IRQ_EXT_DOMAIN0_OFFSET), 0x0); + break; + case IRQ_EXT_DOMAIN1: + io_reg_update_bits(DSP_IRQ_EN, BIT(irq), 0x0); + break; + default: + tr_err(&int_tr, "Invalid interrupt %d", desc->irq); + return; + } + } +} + +static void mtk_irq_unmask(struct irq_desc *desc, uint32_t irq, unsigned int core) +{ + int32_t in_irq, level; + + if (!desc) { + level = GET_INTLEVEL(irq); + in_irq = GET_INTERRUPT_ID(irq); + if (level > MAX_IRQ_NUM) { + tr_err(&int_tr, "Invalid interrupt %d", irq); + return; + } + + io_reg_update_bits(RG_DSP_IRQ_EN, BIT(in_irq), BIT(in_irq)); + } else { + switch (desc->irq) { + case IRQ_EXT_DOMAIN0: + io_reg_update_bits(RG_DSP_IRQ_EN, BIT(irq + IRQ_EXT_DOMAIN0_OFFSET), + BIT(irq + IRQ_EXT_DOMAIN0_OFFSET)); + break; + case IRQ_EXT_DOMAIN1: + io_reg_update_bits(DSP_IRQ_EN, BIT(irq), BIT(irq)); + break; + default: + tr_err(&int_tr, "Invalid interrupt %d", desc->irq); + return; + } + } +} + +static uint64_t mtk_get_irq_interrupts(uint32_t irq) +{ + uint32_t irq_status; + + switch (irq) { + case IRQ_NUM_EXT_LEVEL23: + irq_status = io_reg_read(DSP_IRQ_STATUS); + irq_status &= IRQ_EXT_DOMAIN2_MASK; + break; + default: + irq_status = io_reg_read(RG_DSP_IRQ_STATUS); + irq_status &= IRQ_EXT_DOMAIN1_MASK; + break; + } + return irq_status; +} + +static int get_first_irq(uint64_t ints) +{ + return ffs(ints) - 1; +} + +static inline void mtk_handle_irq(struct irq_cascade_desc *cascade, + uint32_t line_index, uint64_t status) +{ + int core = cpu_get_id(); + struct list_item *clist; + struct irq_desc *child = NULL; + int bit; + bool handled; + + while (status) { + bit = get_first_irq(status); + handled = false; + status &= ~(1ull << bit); + + spin_lock(&cascade->lock); + + list_for_item(clist, &cascade->child[bit].list) { + child = container_of(clist, struct irq_desc, irq_list); + + if (child->handler && (child->cpu_mask & 1 << core)) { + child->handler(child->handler_arg); + handled = true; + } + } + + spin_unlock(&cascade->lock); + + if (!handled) { + tr_err(&int_tr, "irq_handler(): not handled, bit %d", bit); + if (line_index == IRQ_NUM_EXT_LEVEL23) + io_reg_update_bits(DSP_IRQ_EN, BIT(bit), 0x0); + else + io_reg_update_bits(RG_DSP_IRQ_EN, BIT(bit), 0x0); + } + } +} + +static inline void irq_handler(void *data, uint32_t line_index) +{ + struct irq_desc *parent = data; + struct irq_cascade_desc *cascade = + container_of(parent, struct irq_cascade_desc, desc); + uint64_t status; + + status = mtk_get_irq_interrupts(line_index); + + if (status) + /* Handle current interrupts */ + mtk_handle_irq(cascade, line_index, status); + else + tr_err(&int_tr, "invalid interrupt status"); +} + +#define DEFINE_IRQ_HANDLER(n) \ + static void irqhandler_##n(void *arg) \ + { \ + irq_handler(arg, n); \ + } + +DEFINE_IRQ_HANDLER(1) +DEFINE_IRQ_HANDLER(23) + +static const char mtk_irq_ext_domain0[] = "mtk_irq_ext_domain0"; +static const char mtk_irq_ext_domain1[] = "mtk_irq_ext_domain1"; + +static const struct irq_cascade_ops irq_ops = { + .mask = mtk_irq_mask, + .unmask = mtk_irq_unmask, +}; + +static const struct irq_cascade_tmpl dsp_irq[] = { + { + .name = mtk_irq_ext_domain0, + .irq = IRQ_NUM_EXT_LEVEL01, + .handler = irqhandler_1, + .ops = &irq_ops, + .global_mask = false, + }, + { + .name = mtk_irq_ext_domain1, + .irq = IRQ_NUM_EXT_LEVEL23, + .handler = irqhandler_23, + .ops = &irq_ops, + .global_mask = false, + }, +}; + +uint32_t mtk_get_irq_domain_id(int32_t irq) +{ + uint32_t in_irq = GET_INTERRUPT_ID(irq); + int32_t level = GET_INTLEVEL(irq); + + if (in_irq >= DOMAIN1_MAX_IRQ_NUM) + in_irq -= DOMAIN1_MAX_IRQ_NUM; + + if (level == IRQ_EXT_DOMAIN0) + return interrupt_get_irq(in_irq, dsp_irq[0].name); + else + return interrupt_get_irq(in_irq, dsp_irq[1].name); +} + +void platform_interrupt_init(void) +{ + int i; + + irq_mask_all(); + + for (i = 0; i < ARRAY_SIZE(dsp_irq); i++) + interrupt_cascade_register(dsp_irq + i); +} + +void platform_interrupt_set(uint32_t irq) +{ + if (interrupt_is_dsp_direct(irq)) + arch_interrupt_set(irq); +} + +void platform_interrupt_clear(uint32_t irq, uint32_t mask) +{ + if (interrupt_is_dsp_direct(irq)) + arch_interrupt_clear(irq); +} + +uint32_t platform_interrupt_get_enabled(void) +{ + return 0; +} + +void interrupt_mask(uint32_t irq, unsigned int cpu) +{ + struct irq_cascade_desc *cascade = interrupt_get_parent(irq); + + if (cascade && cascade->ops->mask) + cascade->ops->mask(&cascade->desc, irq - cascade->irq_base, + cpu); + else + mtk_irq_mask(NULL, irq, 0); +} + +void interrupt_unmask(uint32_t irq, unsigned int cpu) +{ + struct irq_cascade_desc *cascade = interrupt_get_parent(irq); + + if (cascade && cascade->ops->unmask) + cascade->ops->unmask(&cascade->desc, irq - cascade->irq_base, + cpu); + else + mtk_irq_unmask(NULL, irq, 0); +} diff --git a/src/drivers/mediatek/mt8195/ipc.c b/src/drivers/mediatek/mt8195/ipc.c new file mode 100644 index 000000000..9daaece5e --- /dev/null +++ b/src/drivers/mediatek/mt8195/ipc.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2021 Mediatek +// +// Author: Allen-KH Cheng + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IPC_DSPMBOX_DSP_RSP 0 +#define IPC_DSPMBOX_DSP_REQ 1 + +/* 389c9186-5a7d-4ad1-a02c-a02ecdadfb33 */ +DECLARE_SOF_UUID("ipc-task", ipc_task_uuid, 0x389c9186, 0x5a7d, 0x4ad1, + 0xa0, 0x2c, 0xa0, 0x2e, 0xcd, 0xad, 0xfb, 0x33); + +static struct ipc *local_ipc; + +struct ipc_data { + struct ipc_data_host_buffer dh_buffer; +}; + +static void mbox0_handler(void *args) +{ + uint32_t op = io_reg_read(MTK_DSP_MBOX_IN_CMD(0)); + + /* clear interrupt */ + io_reg_write(MTK_DSP_MBOX_IN_CMD_CLR(0), op); + ipc_schedule_process(local_ipc); +} + +static void mbox1_handler(void *args) +{ + uint32_t op = io_reg_read(MTK_DSP_MBOX_IN_CMD(1)); + + /* clear interrupt */ + io_reg_write(MTK_DSP_MBOX_IN_CMD_CLR(1), op); + local_ipc->is_notification_pending = false; +} + +void trigger_irq_to_host_rsp(void) +{ + io_reg_write(MTK_DSP_MBOX_OUT_CMD(0), ADSP_IPI_OP_RSP); +} + +void trigger_irq_to_host_req(void) +{ + io_reg_write(MTK_DSP_MBOX_OUT_CMD(1), ADSP_IPI_OP_REQ); +} + +enum task_state ipc_platform_do_cmd(void *data) +{ + ipc_cmd_hdr *hdr; + + hdr = mailbox_validate(); + ipc_cmd(hdr); + + return SOF_TASK_STATE_COMPLETED; +} + +void ipc_platform_complete_cmd(void *data) +{ + struct ipc *ipc = data; + + trigger_irq_to_host_rsp(); + while (ipc->pm_prepare_D3) + wait_for_interrupt(0); +} + +int ipc_platform_send_msg(struct ipc_msg *msg) +{ + struct ipc *ipc = ipc_get(); + + if (ipc->is_notification_pending) + return -EBUSY; + + /* now send the message */ + mailbox_dspbox_write(0, msg->tx_data, msg->tx_size); + list_item_del(&msg->list); + ipc->is_notification_pending = true; + + /* now interrupt host to tell it we have sent a message */ + trigger_irq_to_host_req(); + return 0; +} + +#if CONFIG_HOST_PTABLE +struct ipc_data_host_buffer *ipc_platform_get_host_buffer(struct ipc *ipc) +{ + struct ipc_data *iipc = ipc_get_drvdata(ipc); + + return &iipc->dh_buffer; +} +#endif + +int platform_ipc_init(struct ipc *ipc) +{ + uint32_t mbox_irq0, mbox_irq1; +#if CONFIG_HOST_PTABLE + struct ipc_data *iipc; + + iipc = rzalloc(SOF_MEM_ZONE_SYS, 0, SOF_MEM_CAPS_RAM, sizeof(*iipc)); + ipc_set_drvdata(ipc, iipc); +#else + ipc_set_drvdata(ipc, NULL); +#endif + + local_ipc = ipc; + + /* schedule */ + schedule_task_init_edf(&ipc->ipc_task, SOF_UUID(ipc_task_uuid), &ipc_task_ops, ipc, 0, 0); + +#if CONFIG_HOST_PTABLE + /* allocate page table buffer */ + iipc->dh_buffer.page_table = + rzalloc(SOF_MEM_ZONE_SYS, 0, SOF_MEM_CAPS_RAM, PLATFORM_PAGE_TABLE_SIZE); + + iipc->dh_buffer.dmac = dma_get(DMA_DIR_HMEM_TO_LMEM, 0, DMA_DEV_HOST, DMA_ACCESS_SHARED); + if (!iipc->dh_buffer.dmac) { + tr_err(&ipc_tr, "Unable to find DMA for host page table"); + panic(SOF_IPC_PANIC_IPC); + } +#endif + + mbox_irq0 = mtk_get_irq_domain_id(LX_MBOX_IRQ0_B); + mbox_irq1 = mtk_get_irq_domain_id(LX_MBOX_IRQ1_B); + interrupt_register(mbox_irq0, mbox0_handler, ipc); + interrupt_register(mbox_irq1, mbox1_handler, ipc); + interrupt_enable(mbox_irq0, ipc); + interrupt_enable(mbox_irq1, ipc); + + return 0; +} diff --git a/src/drivers/mediatek/mt8195/timer.c b/src/drivers/mediatek/mt8195/timer.c new file mode 100644 index 000000000..40e81cb21 --- /dev/null +++ b/src/drivers/mediatek/mt8195/timer.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2021 Mediatek +// +// Author:Fengquan Chen + +#include +#include +#include +#include +#include +#include +#include +#include + +void platform_timer_start(struct timer *timer) +{ + /* nothing to do for cpu timer */ +} + +void platform_timer_stop(struct timer *timer) +{ + arch_timer_disable(timer); +} + +int64_t platform_timer_set(struct timer *timer, uint64_t ticks) +{ + return arch_timer_set(timer, ticks); +} + +void platform_timer_clear(struct timer *timer) +{ + arch_timer_clear(timer); +} + +uint64_t platform_timer_get(struct timer *timer) +{ + return arch_timer_get_system(timer); +} + +/* IRQs off in arch_timer_get_system() */ +uint64_t platform_timer_get_atomic(struct timer *timer) +{ + return arch_timer_get_system(timer); +} + +/* get timestamp for host stream DMA position */ +void platform_host_timestamp(struct comp_dev *host, + struct sof_ipc_stream_posn *posn) +{ + int err; + + /* get host position */ + err = comp_position(host, posn); + if (err == 0) + posn->flags |= SOF_TIME_HOST_VALID | SOF_TIME_HOST_64; +} + +/* get timestamp for DAI stream DMA position */ +void platform_dai_timestamp(struct comp_dev *dai, + struct sof_ipc_stream_posn *posn) +{ + int err; + + /* get DAI position */ + err = comp_position(dai, posn); + if (err == 0) + posn->flags |= SOF_TIME_DAI_VALID; + + posn->wallclock = timer_get_system(timer_get()) - posn->wallclock; + posn->flags |= SOF_TIME_WALL_VALID | SOF_TIME_WALL_64; +} + +/* get current wallclock for componnent */ +void platform_dai_wallclock(struct comp_dev *dai, uint64_t *wallclock) +{ + *wallclock = timer_get_system(timer_get()); +} + +int timer_register(struct timer *timer, void(*handler)(void *arg), void *arg) +{ + switch (timer->id) { + case TIMER0: + case TIMER1: + return arch_timer_register(timer, handler, arg); + default: + return -EINVAL; + } +} + +void timer_unregister(struct timer *timer, void *arg) +{ + interrupt_unregister(timer->irq, arg); +} + +void timer_enable(struct timer *timer, void *arg, int core) +{ + interrupt_enable(timer->irq, arg); +} + +void timer_disable(struct timer *timer, void *arg, int core) +{ + interrupt_disable(timer->irq, arg); +}