kernel-brax3-ubuntu-touch/drivers/misc/mediatek/adsp/adsp_core.c
erascape f319b992b1 kernel-5.15: Initial import brax3 UT kernel
* halium configs enabled

Signed-off-by: erascape <erascape@proton.me>
2025-09-23 15:17:10 +00:00

724 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019 MediaTek Inc.
*/
#include <linux/module.h> /* needed by all modules */
#include <linux/init.h> /* needed by module macros */
#include <linux/device.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/suspend.h>
#include <linux/arm-smccc.h> /* for Kernel Native SMC API */
#include <linux/soc/mediatek/mtk_sip_svc.h> /* for SMC ID table */
#if IS_ENABLED(CONFIG_MTK_DEVAPC)
#include <linux/soc/mediatek/devapc_public.h>
#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 <Chien-Wei.Hsu@mediatek.com>");
MODULE_DESCRIPTION("MTK AUDIO DSP Device Driver");
MODULE_LICENSE("GPL v2");