// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include /* needed by all modules */ #include /* needed by module macros */ #include #include #include #include #include /* for Kernel Native SMC API */ #include /* for SMC ID table */ #if IS_ENABLED(CONFIG_MTK_DEVAPC) #include #endif #include "adsp_clk.h" #include "adsp_timesync.h" #include "adsp_semaphore.h" #include "adsp_platform.h" #include "adsp_platform_driver.h" #include "adsp_excep.h" #include "adsp_mbox.h" #include "adsp_core.h" #define ADSP_MAGIC_PATTERN (0xAD5BAD5B) #define adsp_smc_send(_opid, _val1, _val2) \ ({ \ struct arm_smccc_res res; \ arm_smccc_smc(MTK_SIP_KERNEL_ADSP_CONTROL, \ _opid, _val1, _val2, 0, 0, 0, 0, &res);\ res.a0; \ }) static int (*ipi_queue_send_msg_handler)( uint32_t core_id, /* enum adsp_core_id */ uint32_t ipi_id, /* enum adsp_ipi_id */ void *buf, uint32_t len, uint32_t wait_ms); struct adsp_priv *adsp_cores[ADSP_CORE_TOTAL]; struct adspsys_priv *adspsys; const struct attribute_group *adspsys_attr_groups[] = { &adsp_excep_attr_group, NULL, }; /* protect access tcm if set reset flag */ static DEFINE_MUTEX(access_lock); /* notifier */ static BLOCKING_NOTIFIER_HEAD(adsp_notifier_list); /* ------------------------------------------------ */ struct adsp_priv *_get_adsp_core(void *ptr, int id) { if (ptr) return container_of(ptr, struct adsp_priv, mdev); if (id < get_adsp_core_total() && id >= 0) return adsp_cores[id]; return NULL; } void set_adsp_state(struct adsp_priv *pdata, int state) { pdata->state = state; } EXPORT_SYMBOL(set_adsp_state); int get_adsp_state(struct adsp_priv *pdata) { return pdata->state; } EXPORT_SYMBOL(get_adsp_state); int is_adsp_ready(u32 cid) { if (unlikely(cid >= get_adsp_core_total())) return -EINVAL; if (unlikely(!adsp_cores[cid])) return -EINVAL; return (adsp_cores[cid]->state == ADSP_RUNNING); } EXPORT_SYMBOL(is_adsp_ready); int is_adsp_not_suspend(u32 cid) { if (unlikely(cid >= get_adsp_core_total())) return -EINVAL; if (unlikely(!adsp_cores[cid])) return -EINVAL; return (adsp_cores[cid]->state != ADSP_SUSPEND); } u32 get_adsp_core_total(void) { return adspsys ? adspsys->num_cores : 0; } EXPORT_SYMBOL(get_adsp_core_total); bool is_adsp_system_running(void) { unsigned int id; for (id = 0; id < get_adsp_core_total(); id++) { if (is_adsp_ready(id) > 0) return true; } return false; } bool is_adsp_system_suspend(void) { unsigned int id; for (id = 0; id < get_adsp_core_total(); id++) { if (is_adsp_not_suspend(id) > 0) return false; } return true; } int adsp_copy_to_sharedmem(struct adsp_priv *pdata, int id, const void *src, int count) { void __iomem *dst = NULL; const struct sharedmem_info *item; if (unlikely(id >= ADSP_SHAREDMEM_NUM)) return 0; item = pdata->mapping_table + id; if (item->offset) dst = pdata->dtcm + pdata->dtcm_size - item->offset; if (unlikely(!dst || !src)) return 0; if (count > item->size) count = item->size; if (get_adsp_clock_semaphore() != ADSP_OK) pr_notice("%s() get clock semaphore fail\n", __func__); memcpy_toio(dst, src, count); release_adsp_clock_semaphore(); return count; } EXPORT_SYMBOL(adsp_copy_to_sharedmem); int adsp_copy_from_sharedmem(struct adsp_priv *pdata, int id, void *dst, int count) { void __iomem *src = NULL; const struct sharedmem_info *item; if (unlikely(id >= ADSP_SHAREDMEM_NUM)) return 0; item = pdata->mapping_table + id; if (item->offset) src = pdata->dtcm + pdata->dtcm_size - item->offset; if (unlikely(!dst || !src)) return 0; if (count > item->size) count = item->size; if (get_adsp_clock_semaphore() != ADSP_OK) pr_notice("%s() get clock semaphore fail\n", __func__); memcpy_fromio(dst, src, count); release_adsp_clock_semaphore(); return count; } EXPORT_SYMBOL(adsp_copy_from_sharedmem); void hook_ipi_queue_send_msg_handler( int (*send_msg_handler)( uint32_t core_id, /* enum adsp_core_id */ uint32_t ipi_id, /* enum adsp_ipi_id */ void *buf, uint32_t len, uint32_t wait_ms)) { ipi_queue_send_msg_handler = send_msg_handler; } EXPORT_SYMBOL(hook_ipi_queue_send_msg_handler); void unhook_ipi_queue_send_msg_handler(void) { ipi_queue_send_msg_handler = NULL; } EXPORT_SYMBOL(unhook_ipi_queue_send_msg_handler); enum adsp_ipi_status adsp_push_message(enum adsp_ipi_id id, void *buf, unsigned int len, unsigned int wait_ms, unsigned int core_id) { int ret = -1; /* send msg to queue */ if (ipi_queue_send_msg_handler) ret = ipi_queue_send_msg_handler(core_id, id, buf, len, wait_ms); else ret = adsp_send_message(id, buf, len, wait_ms, core_id); return (ret == 0) ? ADSP_IPI_DONE : ADSP_IPI_ERROR; } EXPORT_SYMBOL(adsp_push_message); enum adsp_ipi_status adsp_send_message(enum adsp_ipi_id id, void *buf, unsigned int len, unsigned int wait, unsigned int core_id) { struct adsp_priv *pdata = NULL; struct mtk_ipi_msg msg; if (core_id >= get_adsp_core_total() || !buf) return ADSP_IPI_ERROR; pdata = get_adsp_core_by_id(core_id); if (get_adsp_state(pdata) != ADSP_RUNNING) { pr_notice("%s, adsp not enabled, id=%d", __func__, id); return ADSP_IPI_ERROR; } /* system is going to suspend, reject the following msg */ if (id == ADSP_IPI_DVFS_SUSPEND) set_adsp_state(pdata, ADSP_SUSPENDING); msg.ipihd.id = id; msg.ipihd.len = len; msg.ipihd.options = 0xffff0000; msg.ipihd.reserved = 0xdeadbeef; msg.data = buf; return adsp_mbox_send(pdata->send_mbox, &msg, wait); } EXPORT_SYMBOL(adsp_send_message); static irqreturn_t adsp_irq_top_handler(int irq, void *data) { struct irq_t *pdata = (struct irq_t *)data; if (get_adsp_clock_semaphore() != ADSP_OK) pr_notice("%s() get clock semaphore fail\n", __func__); adsp_mt_clr_spm(pdata->cid); if (!pdata->clear_irq) { release_adsp_clock_semaphore(); return IRQ_NONE; } pdata->clear_irq(pdata->cid); release_adsp_clock_semaphore(); if (pdata->irq_cb) pdata->irq_cb(irq, pdata->data, pdata->cid); /* wake up bottom half if necessary */ return pdata->thread_fn ? IRQ_WAKE_THREAD : IRQ_HANDLED; } static irqreturn_t adsp_irq_bottom_thread(int irq, void *data) { struct irq_t *pdata = (struct irq_t *)data; pdata->thread_fn(irq, pdata->data, pdata->cid); return IRQ_HANDLED; } int adsp_threaded_irq_registration(u32 core_id, u32 irq_id, void *handler, void *thread_fn, void *data) { int ret = 0; struct adsp_priv *pdata = get_adsp_core_by_id(core_id); if (unlikely(!pdata)) return -EACCES; if (!handler && !thread_fn) return -EINVAL; pdata->irq[irq_id].cid = core_id; pdata->irq[irq_id].irq_cb = handler; pdata->irq[irq_id].thread_fn = thread_fn; pdata->irq[irq_id].data = data; ret = request_threaded_irq(pdata->irq[irq_id].seq, adsp_irq_top_handler, thread_fn ? adsp_irq_bottom_thread : NULL, IRQF_TRIGGER_HIGH, pdata->name, &pdata->irq[irq_id]); if (ret < 0) { pr_info("%s(), request_irq(%d) err:%d\n", __func__, pdata->irq[irq_id].seq, ret); goto EXIT; } ret = enable_irq_wake(pdata->irq[irq_id].seq); if (ret < 0) { pr_info("%s(), enable_irq_wake(%d) err:%d\n", __func__, pdata->irq[irq_id].seq, ret); goto EXIT; } EXIT: return ret; } EXPORT_SYMBOL_GPL(adsp_threaded_irq_registration); void adsp_register_notify(struct notifier_block *nb) { blocking_notifier_chain_register(&adsp_notifier_list, nb); } EXPORT_SYMBOL(adsp_register_notify); void adsp_unregister_notify(struct notifier_block *nb) { blocking_notifier_chain_unregister(&adsp_notifier_list, nb); } EXPORT_SYMBOL(adsp_unregister_notify); void adsp_extern_notify_chain(enum ADSP_NOTIFY_EVENT event) { #ifdef CFG_RECOVERY_SUPPORT blocking_notifier_call_chain(&adsp_notifier_list, event, NULL); #endif } /* user-space event notify */ static int adsp_user_event_notify(struct notifier_block *nb, unsigned long event, void *ptr) { struct device *dev = adspsys->mdev.this_device; int ret = 0; if (!dev) return NOTIFY_DONE; switch (event) { case ADSP_EVENT_STOP: ret = kobject_uevent(&dev->kobj, KOBJ_OFFLINE); break; case ADSP_EVENT_READY: ret = kobject_uevent(&dev->kobj, KOBJ_ONLINE); break; default: pr_info("%s, ignore event %lu", __func__, event); break; } if (ret) pr_info("%s, uevnet(%lu) fail, ret %d", __func__, event, ret); return NOTIFY_OK; } struct notifier_block adsp_uevent_notifier = { .notifier_call = adsp_user_event_notify, .priority = AUDIO_HAL_FEATURE_PRI, }; #if IS_ENABLED(CONFIG_PM) static int adsp_pm_suspend_prepare(void) { int cid = 0, ret = 0; struct adsp_priv *pdata = NULL; for (cid = get_adsp_core_total() - 1; cid >= 0; cid--) { pdata = adsp_cores[cid]; if (pdata->state == ADSP_RUNNING) { ret = flush_suspend_work(pdata->id); pr_info("%s, flush_suspend_work ret %d, cid %d", __func__, ret, cid); } } if (is_adsp_system_running()) { adsp_timesync_suspend(APTIME_FREEZE); pr_info("%s, time sync freeze", __func__); adsp_smc_send(MTK_ADSP_KERNEL_OP_ENTER_LP, 0, 0); } return NOTIFY_DONE; } static int adsp_pm_post_suspend(void) { if (is_adsp_system_running()) { adsp_timesync_resume(); pr_info("%s, time sync unfreeze", __func__); adsp_smc_send(MTK_ADSP_KERNEL_OP_LEAVE_LP, 0, 0); } return NOTIFY_DONE; } static int adsp_pm_event(struct notifier_block *notifier, unsigned long pm_event, void *unused) { switch (pm_event) { case PM_POST_HIBERNATION: pr_notice("[ADSP] %s: PM_POST_HIBERNATION\n", __func__); return NOTIFY_DONE; case PM_SUSPEND_PREPARE: return adsp_pm_suspend_prepare(); case PM_POST_SUSPEND: return adsp_pm_post_suspend(); } return NOTIFY_OK; } static struct notifier_block adsp_pm_notifier_block = { .notifier_call = adsp_pm_event, .priority = 0, }; #endif void adsp_select_clock_mode(enum adsp_clk_mode mode) { if (adspsys) adspsys->clk_ops.select(mode); } int adsp_enable_clock(void) { return adspsys ? adspsys->clk_ops.enable() : 0; } void adsp_disable_clock(void) { if (adspsys) adspsys->clk_ops.disable(); } void switch_adsp_power(bool on) { if (on) { adsp_enable_clock(); adsp_select_clock_mode(CLK_DEFAULT_INIT); } else { adsp_select_clock_mode(CLK_LOW_POWER); adsp_disable_clock(); } } EXPORT_SYMBOL(switch_adsp_power); void adsp_latch_dump_region(bool en) { /* MUST! latch/unlatch region symmetric */ if (en) { mutex_lock(&access_lock); if (get_adsp_clock_semaphore() != ADSP_OK) pr_notice("%s() get clock semaphore fail\n", __func__); adsp_smc_send(MTK_ADSP_KERNEL_OP_CFG_LATCH, true, 0); } else { adsp_smc_send(MTK_ADSP_KERNEL_OP_CFG_LATCH, false, 0); release_adsp_clock_semaphore(); mutex_unlock(&access_lock); } } void adsp_core_clear(void) { if (get_adsp_clock_semaphore() != ADSP_OK) pr_notice("%s() get clock semaphore fail\n", __func__); adsp_smc_send(MTK_ADSP_KERNEL_OP_SYS_CLEAR, ADSP_MAGIC_PATTERN, 0); release_adsp_clock_semaphore(); } void adsp_core_start(u32 cid) { mutex_lock(&access_lock); if (get_adsp_clock_semaphore() != ADSP_OK) pr_notice("%s() get clock semaphore fail\n", __func__); adsp_smc_send(MTK_ADSP_KERNEL_OP_CORE_START, cid, 0); release_adsp_clock_semaphore(); mutex_unlock(&access_lock); } void adsp_core_stop(u32 cid) { if (get_adsp_clock_semaphore() != ADSP_OK) pr_notice("%s() get clock semaphore fail\n", __func__); adsp_smc_send(MTK_ADSP_KERNEL_OP_CORE_STOP, cid, 0); release_adsp_clock_semaphore(); } int adsp_reset(void) { #ifdef CFG_RECOVERY_SUPPORT int ret = 0; unsigned int cid = 0; struct adsp_priv *pdata; if (!is_adsp_axibus_idle(&ret)) { pr_info("%s, adsp_axibus busy:0x%x, try again", __func__, ret); return -EAGAIN; } /* clear adsp cfg */ adsp_core_clear(); /* choose default clk mux */ adsp_select_clock_mode(CLK_LOW_POWER); adsp_select_clock_mode(CLK_DEFAULT_INIT); /* reload adsp */ ret = adsp_smc_send(MTK_ADSP_KERNEL_OP_RELOAD, ADSP_MAGIC_PATTERN, 0); if (ret < 0) { pr_info("%s, adsp reload fail\n", __func__); return ret; } /* restart adsp */ for (cid = 0; cid < get_adsp_core_total(); cid++) { pdata = adsp_cores[cid]; reinit_completion(&pdata->done); adsp_core_start(cid); ret = wait_for_completion_timeout(&pdata->done, HZ); if (unlikely(ret == 0)) { adsp_core_stop(cid); pr_warn("%s, core %d reset timeout\n", __func__, cid); return -ETIME; } } for (cid = 0; cid < get_adsp_core_total(); cid++) { pdata = adsp_cores[cid]; if (pdata->ops->after_bootup) pdata->ops->after_bootup(pdata); } pr_info("[ADSP] reset adsp done\n"); #endif return 0; } static void adsp_ready_ipi_handler(int id, void *data, unsigned int len) { unsigned int cid = *(unsigned int *)data; struct adsp_priv *pdata = get_adsp_core_by_id(cid); if (unlikely(!pdata)) return; if (get_adsp_state(pdata) != ADSP_RUNNING) { set_adsp_state(pdata, ADSP_RUNNING); complete(&pdata->done); } } static void adsp_suspend_ipi_handler(int id, void *data, unsigned int len) { unsigned int cid = *(unsigned int *)data; struct adsp_priv *pdata = get_adsp_core_by_id(cid); if (unlikely(!pdata)) return; complete(&pdata->done); } static int adsp_system_init(void) { int ret = 0; if (!adspsys) return -EFAULT; adsp_hardware_init(adspsys); /* ipi of ready/suspend ack */ adsp_ipi_registration(ADSP_IPI_ADSP_A_READY, adsp_ready_ipi_handler, "adsp_ready"); adsp_ipi_registration(ADSP_IPI_DVFS_SUSPEND, adsp_suspend_ipi_handler, "adsp_suspend_ack"); /* time sync with adsp */ adsp_timesync_init(); /* hw semaphore */ adsp_semaphore_init(adspsys->desc->semaphore_ways, adspsys->desc->semaphore_ctrl, adspsys->desc->semaphore_retry); /* exception init */ adspsys->workq = alloc_workqueue("adsp_wq", WORK_CPU_UNBOUND | WQ_HIGHPRI, 0); init_waitqueue_head(&adspsys->waitq); init_adsp_exception_control(adspsys->dev, adspsys->workq, &adspsys->waitq); #if IS_ENABLED(CONFIG_PM) ret = register_pm_notifier(&adsp_pm_notifier_block); if (ret) pr_warn("[ADSP] failed to register PM notifier %d\n", ret); #endif /* register misc device */ adspsys->mdev.minor = MISC_DYNAMIC_MINOR; adspsys->mdev.name = "adsp"; adspsys->mdev.fops = &adspsys_file_ops; adspsys->mdev.groups = adspsys_attr_groups; ret = misc_register(&adspsys->mdev); if (unlikely(ret != 0)) pr_warn("%s(), misc_register fail, %d\n", __func__, ret); /* kernel event to userspace */ adsp_register_notify(&adsp_uevent_notifier); return ret; } #if IS_ENABLED(CONFIG_MTK_DEVAPC) static bool devapc_power_cb(void) { return adsp_smc_send(MTK_ADSP_KERNEL_OP_QUERY_STATE, 0, 0); } static struct devapc_power_callbacks devapc_power_handle = { .type = DEVAPC_TYPE_ADSP, .query_power = devapc_power_cb, }; #endif int adsp_system_bootup(void) { int ret = 0; unsigned int cid = 0; struct adsp_priv *pdata; ret = adsp_system_init(); if (ret) goto ERROR; adsp_core_clear(); for (cid = 0; cid < get_adsp_core_total(); cid++) { pdata = adsp_cores[cid]; if (unlikely(!pdata)) { ret = -EFAULT; goto ERROR; } ret = pdata->ops->initialize(pdata); if (unlikely(ret)) { pr_warn("%s, initialize %d is fail\n", __func__, cid); goto ERROR; } reinit_completion(&pdata->done); adsp_core_start(cid); ret = wait_for_completion_timeout(&pdata->done, 2 * HZ); if (unlikely(ret == 0)) { adsp_core_stop(cid); pr_warn("%s, core %d boot_up timeout\n", __func__, cid); ret = -ETIME; goto ERROR; } } adsp_register_feature(SYSTEM_FEATURE_ID); /* regi for trigger suspend */ for (cid = 0; cid < get_adsp_core_total(); cid++) { pdata = adsp_cores[cid]; if (pdata->ops->after_bootup) pdata->ops->after_bootup(pdata); } adsp_deregister_feature(SYSTEM_FEATURE_ID); #if IS_ENABLED(CONFIG_MTK_DEVAPC) register_devapc_power_callback(&devapc_power_handle); #endif pr_info("%s done\n", __func__); return 0; ERROR: pr_info("%s fail ret(%d)\n", __func__, ret); return ret; } EXPORT_SYMBOL(adsp_system_bootup); void register_adspsys(struct adspsys_priv *mt_adspsys) { adspsys = mt_adspsys; pr_info("%s(), %p done\n", __func__, mt_adspsys); } EXPORT_SYMBOL(register_adspsys); void register_adsp_core(struct adsp_priv *pdata) { adsp_cores[pdata->id] = pdata; pr_info("%s(), id %d, %p done\n", __func__, pdata->id, pdata); } EXPORT_SYMBOL(register_adsp_core); MODULE_AUTHOR("Chien-Wei Hsu "); MODULE_DESCRIPTION("MTK AUDIO DSP Device Driver"); MODULE_LICENSE("GPL v2");