578 lines
14 KiB
C
578 lines
14 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* Copyright (c) 2020 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/sysfs.h>
|
|
#ifdef CONFIG_DEBUG_FS
|
|
#include <linux/debugfs.h>
|
|
#endif
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/io.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/ktime.h>
|
|
#include <linux/syscore_ops.h>
|
|
#include "adsp_clk.h"
|
|
#include "adsp_reserved_mem.h"
|
|
#include "adsp_logger.h"
|
|
#include "adsp_excep.h"
|
|
#include "adsp_reg.h"
|
|
#include "adsp_platform.h"
|
|
#include "adsp_platform_driver.h"
|
|
#include "adsp_core.h"
|
|
#include "adsp_ipi.h"
|
|
#include "adsp_bus_monitor.h"
|
|
#include "adsp_timesync.h"
|
|
|
|
#include <linux/suspend.h>
|
|
#include <linux/arm-smccc.h> /* for Kernel Native SMC API */
|
|
#include <mt-plat/mtk_secure_api.h> /* for SMC ID table */
|
|
#include <mt6781-afe-common.h>
|
|
|
|
struct wait_queue_head adsp_waitq;
|
|
struct workqueue_struct *adsp_wq;
|
|
struct adsp_com adsp_common;
|
|
struct adsp_priv *adsp_cores[ADSP_CORE_TOTAL];
|
|
static u32 adsp_load;
|
|
|
|
static int adsp_core0_init(struct adsp_priv *pdata);
|
|
static int adsp_core0_suspend(void);
|
|
static int adsp_core0_resume(void);
|
|
static void adsp_logger_init0_cb(struct work_struct *ws);
|
|
|
|
|
|
static int adsp_after_bootup(struct adsp_priv *pdata);
|
|
|
|
static const struct adsp_description adsp_c0_desc = {
|
|
.id = 0,
|
|
.name = "adsp_0",
|
|
.sharedmems = {
|
|
[ADSP_SHAREDMEM_MPUINFO] = {0x0020, 0x0020},
|
|
[ADSP_SHAREDMEM_TIMESYNC] = {0x0040, 0x0020},
|
|
[ADSP_SHAREDMEM_IPCBUF] = {0x0280, 0x0240},
|
|
[ADSP_SHAREDMEM_AUDIO_IPIBUF] = {0x0680, 0x0400},
|
|
[ADSP_SHAREDMEM_WAKELOCK] = {0x0684, 0x0004},
|
|
[ADSP_SHAREDMEM_SYS_STATUS] = {0x0688, 0x0004},
|
|
[ADSP_SHAREDMEM_BUS_MON_DUMP] = {0x0744, 0x00BC},
|
|
[ADSP_SHAREDMEM_INFRA_BUS_DUMP] = {0x07E4, 0x00A0},
|
|
},
|
|
.ops = {
|
|
.initialize = adsp_core0_init,
|
|
.after_bootup = adsp_after_bootup,
|
|
}
|
|
};
|
|
|
|
/*------------------------------------------------------------*/
|
|
/* adsp operation */
|
|
|
|
int adsp_core0_init(struct adsp_priv *pdata)
|
|
{
|
|
int ret = 0;
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
pdata->debugfs = debugfs_create_file("audiodsp0", S_IFREG | 0644, NULL,
|
|
pdata, &adsp_debug_ops);
|
|
#endif
|
|
adsp_wq = alloc_workqueue("adsp_wq", WORK_CPU_UNBOUND | WQ_HIGHPRI, 0);
|
|
pdata->wq = adsp_wq;
|
|
init_waitqueue_head(&adsp_waitq);
|
|
|
|
init_adsp_feature_control(pdata->id, pdata->feature_set, 1000,
|
|
adsp_wq,
|
|
adsp_core0_suspend,
|
|
adsp_core0_resume);
|
|
|
|
adsp_update_mpu_memory_info(pdata);
|
|
|
|
/* exception init & irq */
|
|
init_adsp_exception_control(pdata->dev, adsp_wq, &adsp_waitq);
|
|
adsp_irq_registration(pdata->id, ADSP_IRQ_WDT_ID, adsp_wdt_handler,
|
|
pdata);
|
|
|
|
/* logger */
|
|
pdata->log_ctrl = adsp_logger_init(ADSP_A_LOGGER_MEM_ID, adsp_logger_init0_cb);
|
|
|
|
/* ipi */
|
|
adsp_ipi_init();
|
|
adsp_irq_registration(pdata->id, ADSP_IRQ_IPC_ID, adsp_ipi_handler,
|
|
pdata);
|
|
|
|
adsp_awake_init(pdata, ADSP_A_SW_INT);
|
|
|
|
#if ADSP_BUS_MONITOR_INIT_ENABLE
|
|
adsp_bus_monitor_init(pdata);
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
bool is_adsp_load(void)
|
|
{
|
|
return adsp_load;
|
|
}
|
|
|
|
static int adsp_after_bootup(struct adsp_priv *pdata)
|
|
{
|
|
#ifdef BRINGUP_ADSP
|
|
/* disable adsp suspend by registering feature */
|
|
_adsp_register_feature(pdata->id, SYSTEM_FEATURE_ID, 0);
|
|
#endif
|
|
return adsp_awake_unlock(pdata->id);
|
|
}
|
|
|
|
static bool is_adsp_core_suspend(struct adsp_priv *pdata)
|
|
{
|
|
u32 status = 0;
|
|
|
|
if (unlikely(!pdata))
|
|
return false;
|
|
|
|
adsp_copy_from_sharedmem(pdata,
|
|
ADSP_SHAREDMEM_SYS_STATUS,
|
|
&status, sizeof(status));
|
|
|
|
return check_hifi_status(ADSP_A_IS_WFI) &&
|
|
check_hifi_status(ADSP_AXI_BUS_IS_IDLE) &&
|
|
(status == ADSP_SUSPEND) &&
|
|
is_adsp_genirq_idle(pdata->id);
|
|
}
|
|
|
|
static void show_adsp_core_suspend(struct adsp_priv *pdata)
|
|
{
|
|
u32 status = 0;
|
|
|
|
if (unlikely(!pdata))
|
|
return;
|
|
|
|
adsp_copy_from_sharedmem(pdata,
|
|
ADSP_SHAREDMEM_SYS_STATUS,
|
|
&status, sizeof(status));
|
|
|
|
pr_info("%s(), IS_WFI(%d), IS_BUS_IDLE(%d), STATUS(%d), IRQ_IDLE(%d)",
|
|
__func__,
|
|
check_hifi_status(ADSP_A_IS_WFI),
|
|
check_hifi_status(ADSP_AXI_BUS_IS_IDLE),
|
|
status,
|
|
is_adsp_genirq_idle(pdata->id));
|
|
}
|
|
|
|
int adsp_core0_suspend(void)
|
|
{
|
|
int ret = 0, retry = 10;
|
|
u32 status = 0;
|
|
struct adsp_priv *pdata = adsp_cores[ADSP_A_ID];
|
|
ktime_t start = ktime_get();
|
|
|
|
if (get_adsp_state(pdata) == ADSP_RUNNING) {
|
|
reinit_completion(&pdata->done);
|
|
adsp_timesync_suspend(APTIME_UNFREEZE);
|
|
ret = adsp_push_message(ADSP_IPI_DVFS_SUSPEND, &status,
|
|
sizeof(status), 2000, pdata->id);
|
|
if (ret != ADSP_IPI_DONE) {
|
|
ret = -EPIPE;
|
|
goto ERROR;
|
|
}
|
|
|
|
/* wait core suspend ack timeout 2s */
|
|
ret = wait_for_completion_timeout(&pdata->done, 2 * HZ);
|
|
if (!ret) {
|
|
ret = -ETIMEDOUT;
|
|
goto ERROR;
|
|
}
|
|
|
|
while (--retry && !is_adsp_core_suspend(pdata))
|
|
usleep_range(100, 200);
|
|
|
|
if (retry == 0 || get_adsp_state(pdata) == ADSP_RESET) {
|
|
show_adsp_core_suspend(pdata);
|
|
ret = -ETIME;
|
|
goto ERROR;
|
|
}
|
|
|
|
if (get_adsp_state(pdata) == ADSP_RESET) {
|
|
ret = -EFAULT;
|
|
goto ERROR;
|
|
}
|
|
|
|
adsp_mt_stop(pdata->id);
|
|
switch_adsp_power(false);
|
|
set_adsp_state(pdata, ADSP_SUSPEND);
|
|
}
|
|
pr_info("%s(), done elapse %lld us", __func__,
|
|
ktime_us_delta(ktime_get(), start));
|
|
return 0;
|
|
ERROR:
|
|
pr_warn("%s(), can't going to suspend, ret(%d)\n", __func__, ret);
|
|
adsp_aed_dispatch(EXCEP_KERNEL, pdata);
|
|
return ret;
|
|
}
|
|
|
|
int adsp_core0_resume(void)
|
|
{
|
|
int ret = 0;
|
|
struct adsp_priv *pdata = adsp_cores[ADSP_A_ID];
|
|
ktime_t start = ktime_get();
|
|
|
|
if (get_adsp_state(pdata) == ADSP_SUSPEND) {
|
|
switch_adsp_power(true);
|
|
adsp_mt_sw_reset(pdata->id);
|
|
|
|
#if ADSP_BUS_MONITOR_INIT_ENABLE
|
|
adsp_bus_monitor_init(pdata);
|
|
#endif
|
|
adsp_mt_set_bootup_mark(pdata->id);
|
|
|
|
reinit_completion(&pdata->done);
|
|
adsp_mt_run(pdata->id);
|
|
ret = wait_for_completion_timeout(&pdata->done, 2 * HZ);
|
|
|
|
if (get_adsp_state(pdata) != ADSP_RUNNING) {
|
|
pr_warn("%s, can't going to resume\n", __func__);
|
|
adsp_aed_dispatch(EXCEP_KERNEL, pdata);
|
|
return -ETIME;
|
|
}
|
|
adsp_timesync_resume();
|
|
}
|
|
pr_info("%s(), done elapse %lld us", __func__,
|
|
ktime_us_delta(ktime_get(), start));
|
|
return 0;
|
|
}
|
|
|
|
void adsp_logger_init0_cb(struct work_struct *ws)
|
|
{
|
|
int ret;
|
|
uint64_t info[6];
|
|
|
|
info[0] = adsp_get_reserve_mem_phys(ADSP_A_LOGGER_MEM_ID);
|
|
info[1] = (uint64_t)adsp_get_reserve_mem_size(ADSP_A_LOGGER_MEM_ID);
|
|
info[2] = adsp_get_reserve_mem_phys(ADSP_A_CORE_DUMP_MEM_ID);
|
|
info[3] = (uint64_t)adsp_get_reserve_mem_size(ADSP_A_CORE_DUMP_MEM_ID);
|
|
info[4] = adsp_get_reserve_mem_phys(ADSP_A_DEBUG_DUMP_MEM_ID);
|
|
info[5] = (uint64_t)adsp_get_reserve_mem_size(ADSP_A_DEBUG_DUMP_MEM_ID);
|
|
|
|
_adsp_register_feature(ADSP_A_ID, ADSP_LOGGER_FEATURE_ID, 0);
|
|
|
|
ret = adsp_push_message(ADSP_IPI_LOGGER_INIT, (void *)info,
|
|
sizeof(info), 20, ADSP_A_ID);
|
|
|
|
_adsp_deregister_feature(ADSP_A_ID, ADSP_LOGGER_FEATURE_ID, 0);
|
|
|
|
if (ret != ADSP_IPI_DONE)
|
|
pr_err("[ADSP]logger initial fail, ipi ret=%d\n", ret);
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
static const struct of_device_id adsp_of_ids[] = {
|
|
{ .compatible = "mediatek,adsp_core_0", .data = &adsp_c0_desc},
|
|
{}
|
|
};
|
|
|
|
static const struct of_device_id adsp_common_of_ids[] = {
|
|
{ .compatible = "mediatek,adsp_common"},
|
|
{}
|
|
};
|
|
|
|
const struct attribute_group *adsp_common_attr_groups[] = {
|
|
&adsp_excep_attr_group,
|
|
NULL,
|
|
};
|
|
|
|
const struct attribute_group *adsp_core_attr_groups[] = {
|
|
&adsp_default_attr_group,
|
|
NULL,
|
|
};
|
|
|
|
static struct miscdevice adsp_common_device = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "adsp",
|
|
.groups = adsp_common_attr_groups,
|
|
.fops = &adsp_common_file_ops,
|
|
};
|
|
|
|
/* user-space event notify */
|
|
static int adsp_user_event_notify(struct notifier_block *nb,
|
|
unsigned long event, void *ptr)
|
|
{
|
|
struct device *dev = adsp_common_device.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,
|
|
};
|
|
|
|
#ifdef CONFIG_PM
|
|
static int adsp_pm_event(struct notifier_block *notifier
|
|
, unsigned long pm_event, void *unused)
|
|
{
|
|
struct arm_smccc_res res;
|
|
|
|
switch (pm_event) {
|
|
case PM_POST_HIBERNATION:
|
|
pr_notice("[ADSP] %s: reboot\n", __func__);
|
|
adsp_reset();
|
|
return NOTIFY_DONE;
|
|
case PM_SUSPEND_PREPARE:
|
|
if (adsp_feature_is_active(ADSP_A_ID))
|
|
arm_smccc_smc(MTK_SIP_AUDIO_CONTROL,
|
|
MTK_AUDIO_SMC_OP_ADSP_REQUEST,
|
|
0, 0, 0, 0, 0, 0, &res);
|
|
|
|
return NOTIFY_DONE;
|
|
case PM_POST_SUSPEND:
|
|
if (adsp_feature_is_active(ADSP_A_ID))
|
|
arm_smccc_smc(MTK_SIP_AUDIO_CONTROL,
|
|
MTK_AUDIO_SMC_OP_ADSP_RELEASE,
|
|
0, 0, 0, 0, 0, 0, &res);
|
|
return NOTIFY_DONE;
|
|
}
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block adsp_pm_notifier_block = {
|
|
.notifier_call = adsp_pm_event,
|
|
.priority = 0,
|
|
};
|
|
#endif
|
|
|
|
static int adsp_common_drv_probe(struct platform_device *pdev)
|
|
{
|
|
int ret = 0;
|
|
struct device *dev = &pdev->dev;
|
|
|
|
/* indicate if adsp images is loaded successfully */
|
|
of_property_read_u32(dev->of_node, "load", &adsp_load);
|
|
if (!adsp_load)
|
|
pr_info("%s adsp disable\n", __func__);
|
|
|
|
adsp_common.infracfg_ao = of_iomap(dev->of_node, 0);
|
|
if (IS_ERR(adsp_common.infracfg_ao))
|
|
goto ERROR;
|
|
|
|
adsp_common.pericfg = of_iomap(dev->of_node, 1);
|
|
if (IS_ERR(adsp_common.pericfg))
|
|
goto ERROR;
|
|
|
|
ret = adsp_clk_device_probe(pdev);
|
|
if (ret) {
|
|
pr_warn("%s(), clk probe fail, %d\n", __func__, ret);
|
|
goto ERROR;
|
|
}
|
|
|
|
ret = adsp_mem_device_probe(pdev);
|
|
if (ret) {
|
|
pr_info("%s(), memory probe fail, %d\n", __func__, ret);
|
|
goto ERROR;
|
|
}
|
|
|
|
ret = misc_register(&adsp_common_device);
|
|
if (ret) {
|
|
pr_warn("%s(), misc_register fail, %d\n", __func__, ret);
|
|
goto ERROR;
|
|
}
|
|
|
|
adsp_register_notify(&adsp_uevent_notifier);
|
|
|
|
#ifdef CONFIG_PM
|
|
ret = register_pm_notifier(&adsp_pm_notifier_block);
|
|
if (ret)
|
|
pr_info("[ADSP] failed to register PM notifier %d\n", ret);
|
|
#endif
|
|
|
|
pr_info("%s, success\n", __func__);
|
|
ERROR:
|
|
return ret;
|
|
}
|
|
|
|
static int adsp_common_drv_remove(struct platform_device *pdev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int adsp_core_drv_probe(struct platform_device *pdev)
|
|
{
|
|
int ret = 0;
|
|
struct resource *res;
|
|
struct device *dev = &pdev->dev;
|
|
struct adsp_priv *pdata;
|
|
const struct adsp_description *desc;
|
|
const struct of_device_id *match;
|
|
u32 temp = 0;
|
|
|
|
/* create private data */
|
|
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
|
if (!pdata)
|
|
return -ENOMEM;
|
|
|
|
match = of_match_node(adsp_of_ids, dev->of_node);
|
|
if (!match)
|
|
return -ENODEV;
|
|
|
|
desc = (struct adsp_description *)match->data;
|
|
|
|
pdata->id = desc->id;
|
|
pdata->name = desc->name;
|
|
pdata->ops = &desc->ops;
|
|
pdata->mapping_table = desc->sharedmems;
|
|
|
|
pdata->dev = dev;
|
|
init_completion(&pdata->done);
|
|
|
|
/* get resource from platform_device */
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
pdata->cfg = devm_ioremap_resource(dev, res);
|
|
pdata->cfg_size = resource_size(res);
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
pdata->itcm = devm_ioremap_resource(dev, res);
|
|
pdata->itcm_size = resource_size(res);
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
|
|
pdata->dtcm = devm_ioremap_resource(dev, res);
|
|
pdata->dtcm_size = resource_size(res);
|
|
|
|
pdata->irq[ADSP_IRQ_WDT_ID].seq = platform_get_irq(pdev, 0);
|
|
pdata->irq[ADSP_IRQ_WDT_ID].clear_irq = adsp_mt_disable_wdt;
|
|
pdata->irq[ADSP_IRQ_IPC_ID].seq = platform_get_irq(pdev, 1);
|
|
/* adsp_ipi clr irq by itself */
|
|
/* pdata->irq[ADSP_IRQ_IPC_ID].clear_irq = adsp_mt_clr_sysirq; */
|
|
|
|
of_property_read_u32(dev->of_node, "sysram", &temp);
|
|
pdata->sysram_phys = (phys_addr_t)temp;
|
|
of_property_read_u32(dev->of_node, "sysram_size", &temp);
|
|
pdata->sysram_size = (size_t)temp;
|
|
if (pdata->sysram_phys == 0 || pdata->sysram_size == 0) {
|
|
ret = -ENODEV;
|
|
goto ERROR;
|
|
}
|
|
pdata->sysram = ioremap_wc(pdata->sysram_phys, pdata->sysram_size);
|
|
|
|
pdata->feature_set = 0xffffffff; //no need for one core platform
|
|
|
|
/* register misc device */
|
|
pdata->mdev.minor = MISC_DYNAMIC_MINOR;
|
|
pdata->mdev.name = desc->name;
|
|
pdata->mdev.fops = &adsp_core_file_ops;
|
|
pdata->mdev.groups = adsp_core_attr_groups;
|
|
|
|
ret = misc_register(&pdata->mdev);
|
|
if (unlikely(ret != 0))
|
|
goto ERROR;
|
|
|
|
/* add to adsp_core list */
|
|
adsp_cores[desc->id] = pdata;
|
|
|
|
pr_info("%s, id:%d success\n", __func__, pdata->id);
|
|
return 0;
|
|
ERROR:
|
|
pr_err("%s id:%d fail, ret = %d", __func__, pdata->id, ret);
|
|
return ret;
|
|
}
|
|
|
|
static int adsp_core_drv_remove(struct platform_device *pdev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int adsp_ap_suspend(struct device *dev)
|
|
{
|
|
int cid = 0, ret = 0;
|
|
struct adsp_priv *pdata = NULL;
|
|
|
|
for (cid = 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__);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int adsp_ap_resume(struct device *dev)
|
|
{
|
|
if (is_adsp_system_running()) {
|
|
adsp_timesync_resume();
|
|
pr_info("%s, time sync unfreeze", __func__);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops adsp_ap_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(adsp_ap_suspend,
|
|
adsp_ap_resume)
|
|
};
|
|
|
|
static struct platform_driver adsp_common_driver = {
|
|
.probe = adsp_common_drv_probe,
|
|
.remove = adsp_common_drv_remove,
|
|
.driver = {
|
|
.name = "adsp_common",
|
|
.owner = THIS_MODULE,
|
|
#ifdef CONFIG_OF
|
|
.of_match_table = adsp_common_of_ids,
|
|
#endif
|
|
#ifdef CONFIG_PM
|
|
.pm = &adsp_ap_pm_ops,
|
|
#endif
|
|
},
|
|
};
|
|
|
|
static struct platform_driver adsp_core0_driver = {
|
|
.probe = adsp_core_drv_probe,
|
|
.remove = adsp_core_drv_remove,
|
|
.driver = {
|
|
.name = "adsp_core0",
|
|
.owner = THIS_MODULE,
|
|
#ifdef CONFIG_OF
|
|
.of_match_table = adsp_of_ids,
|
|
#endif
|
|
},
|
|
};
|
|
|
|
static struct platform_driver * const drivers[] = {
|
|
&adsp_common_driver,
|
|
&adsp_core0_driver,
|
|
};
|
|
|
|
int create_adsp_drivers(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = platform_register_drivers(drivers, ARRAY_SIZE(drivers));
|
|
|
|
return (is_adsp_load() && adsp_cores[0] && ~ret);
|
|
}
|
|
|