928 lines
23 KiB
C
928 lines
23 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
//
|
||
|
// adsp_helper.c-- Mediatek ADSP kernel entry
|
||
|
//
|
||
|
// Copyright (c) 2018 MediaTek Inc.
|
||
|
// Author: HsinYi Chang <hsin-yi.chang@mediatek.com>
|
||
|
|
||
|
#include <linux/module.h> /* needed by all modules */
|
||
|
#include <linux/init.h> /* needed by module macros */
|
||
|
#include <linux/fs.h> /* needed by file_operations* */
|
||
|
#include <linux/miscdevice.h> /* needed by miscdevice* */
|
||
|
#include <linux/sysfs.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/device.h> /* needed by device_* */
|
||
|
#include <linux/vmalloc.h> /* needed by vmalloc */
|
||
|
#include <linux/uaccess.h> /* needed by copy_to_user */
|
||
|
#include <linux/fs.h> /* needed by file_operations* */
|
||
|
#include <linux/slab.h> /* needed by kmalloc */
|
||
|
#include <linux/poll.h> /* needed by poll */
|
||
|
#include <linux/mutex.h>
|
||
|
#include <linux/sched.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/suspend.h>
|
||
|
#include <linux/timer.h>
|
||
|
#include <linux/kobject.h>
|
||
|
#include <linux/notifier.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_address.h>
|
||
|
#include <linux/of_irq.h>
|
||
|
#include <linux/of_fdt.h>
|
||
|
#include <linux/ioport.h>
|
||
|
#include <linux/debugfs.h>
|
||
|
#include <linux/syscore_ops.h>
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/clk.h>
|
||
|
#include <linux/delay.h>
|
||
|
#ifdef wakelock
|
||
|
#include <linux/wakelock.h>
|
||
|
#endif
|
||
|
#include <mt-plat/sync_write.h>
|
||
|
#include <mt-plat/aee.h>
|
||
|
#ifdef CONFIG_MTK_TIMER_TIMESYNC
|
||
|
#include <mtk_sys_timer.h>
|
||
|
#include <mtk_sys_timer_typedefs.h>
|
||
|
#endif
|
||
|
#include <mtk_spm_sleep.h>
|
||
|
#ifdef CONFIG_MTK_EMI
|
||
|
#include <plat_debug_api.h>
|
||
|
#endif
|
||
|
#include "adsp_helper.h"
|
||
|
#include "adsp_excep.h"
|
||
|
#include "adsp_dvfs.h"
|
||
|
#include "adsp_clk.h"
|
||
|
#include "adsp_service.h"
|
||
|
#include "adsp_logger.h"
|
||
|
#include "adsp_bus_monitor.h"
|
||
|
#include "adsp_timesync.h"
|
||
|
|
||
|
#define ADSP_READY_TIMEOUT (40 * HZ) /* 40 seconds*/
|
||
|
|
||
|
struct adsp_regs adspreg;
|
||
|
char *adsp_core_ids[ADSP_CORE_TOTAL] = {"ADSP A"};
|
||
|
unsigned int adsp_ready[ADSP_CORE_TOTAL];
|
||
|
unsigned int adsp_enable;
|
||
|
#ifdef CONFIG_DEBUG_FS
|
||
|
static struct dentry *adsp_debugfs;
|
||
|
#endif
|
||
|
|
||
|
/* adsp workqueue & work */
|
||
|
struct workqueue_struct *adsp_workqueue;
|
||
|
static void adsp_notify_ws(struct work_struct *ws);
|
||
|
static void adsp_timeout_ws(struct work_struct *ws);
|
||
|
static DECLARE_WORK(adsp_notify_work, adsp_notify_ws);
|
||
|
static DECLARE_DELAYED_WORK(adsp_timeout_work, adsp_timeout_ws);
|
||
|
|
||
|
/* adsp notify */
|
||
|
static DEFINE_MUTEX(adsp_A_notify_mutex);
|
||
|
static BLOCKING_NOTIFIER_HEAD(adsp_A_notifier_list);
|
||
|
|
||
|
#ifdef CFG_RECOVERY_SUPPORT
|
||
|
static unsigned int adsp_timeout_times;
|
||
|
static struct workqueue_struct *adsp_reset_workqueue;
|
||
|
unsigned int adsp_recovery_flag[ADSP_CORE_TOTAL];
|
||
|
atomic_t adsp_reset_status = ATOMIC_INIT(ADSP_RESET_STATUS_STOP);
|
||
|
struct completion adsp_sys_reset_cp;
|
||
|
struct adsp_work_t adsp_sys_reset_work;
|
||
|
struct wakeup_source *adsp_reset_lock;
|
||
|
DEFINE_SPINLOCK(adsp_reset_spinlock);
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* register apps notification
|
||
|
* NOTE: this function may be blocked
|
||
|
* and should not be called in interrupt context
|
||
|
* @param nb: notifier block struct
|
||
|
*/
|
||
|
void adsp_A_register_notify(struct notifier_block *nb)
|
||
|
{
|
||
|
mutex_lock(&adsp_A_notify_mutex);
|
||
|
blocking_notifier_chain_register(&adsp_A_notifier_list, nb);
|
||
|
|
||
|
pr_debug("[ADSP] register adsp A notify callback..\n");
|
||
|
|
||
|
if (is_adsp_ready(ADSP_A_ID) == 1)
|
||
|
nb->notifier_call(nb, ADSP_EVENT_READY, NULL);
|
||
|
mutex_unlock(&adsp_A_notify_mutex);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(adsp_A_register_notify);
|
||
|
/*
|
||
|
* unregister apps notification
|
||
|
* NOTE: this function may be blocked
|
||
|
* and should not be called in interrupt context
|
||
|
* @param nb: notifier block struct
|
||
|
*/
|
||
|
void adsp_A_unregister_notify(struct notifier_block *nb)
|
||
|
{
|
||
|
mutex_lock(&adsp_A_notify_mutex);
|
||
|
blocking_notifier_chain_unregister(&adsp_A_notifier_list, nb);
|
||
|
mutex_unlock(&adsp_A_notify_mutex);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(adsp_A_unregister_notify);
|
||
|
|
||
|
#ifdef CFG_RECOVERY_SUPPORT
|
||
|
void adsp_extern_notify(enum ADSP_NOTIFY_EVENT notify_status)
|
||
|
{
|
||
|
blocking_notifier_call_chain(&adsp_A_notifier_list,
|
||
|
notify_status, NULL);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* adsp_set_reset_status, set and return scp reset status function
|
||
|
* return value:
|
||
|
* 0: scp not in reset status
|
||
|
* 1: scp in reset status
|
||
|
*/
|
||
|
unsigned int adsp_set_reset_status(void)
|
||
|
{
|
||
|
unsigned long spin_flags;
|
||
|
|
||
|
spin_lock_irqsave(&adsp_reset_spinlock, spin_flags);
|
||
|
if (atomic_read(&adsp_reset_status) == ADSP_RESET_STATUS_START) {
|
||
|
spin_unlock_irqrestore(&adsp_reset_spinlock, spin_flags);
|
||
|
return 1;
|
||
|
}
|
||
|
/* adsp not in reset status, set it and return*/
|
||
|
atomic_set(&adsp_reset_status, ADSP_RESET_STATUS_START);
|
||
|
spin_unlock_irqrestore(&adsp_reset_spinlock, spin_flags);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* callback function for work struct
|
||
|
* NOTE: this function may be blocked
|
||
|
* and should not be called in interrupt context
|
||
|
* @param ws: work struct
|
||
|
*/
|
||
|
void adsp_sys_reset_ws(struct work_struct *ws)
|
||
|
{
|
||
|
struct adsp_work_t *sws = container_of(ws, struct adsp_work_t, work);
|
||
|
unsigned int adsp_reset_type = sws->flags;
|
||
|
unsigned int adsp_reset_flag = 0; /* make sure adsp is in idle state */
|
||
|
int timeout = 100; /* max wait 2s */
|
||
|
|
||
|
/*set adsp not ready*/
|
||
|
adsp_recovery_flag[ADSP_A_ID] = ADSP_RECOVERY_START;
|
||
|
adsp_ready[ADSP_A_ID] = 0;
|
||
|
adsp_register_feature(SYSTEM_FEATURE_ID);
|
||
|
adsp_extern_notify(ADSP_EVENT_STOP);
|
||
|
|
||
|
/* wake lock AP*/
|
||
|
__pm_stay_awake(adsp_reset_lock);
|
||
|
|
||
|
pr_info("%s(): adsp_aed_reset\n", __func__);
|
||
|
if (adsp_reset_type == ADSP_RESET_TYPE_AWAKE)
|
||
|
adsp_aed_reset(EXCEP_KERNEL, ADSP_A_ID);
|
||
|
else
|
||
|
adsp_aed_reset(EXCEP_RUNTIME, ADSP_A_ID);
|
||
|
|
||
|
/*wait adsp ee finished in 10s*/
|
||
|
if (wait_for_completion_interruptible_timeout(&adsp_sys_reset_cp,
|
||
|
msecs_to_jiffies(10000)) == 0) {
|
||
|
pr_info("%s: adsp ee time out\n", __func__);
|
||
|
/*timeout check adsp status again*/
|
||
|
if (is_adsp_ready(ADSP_A_ID) != -1) {
|
||
|
pr_info("%s: adsp reset state incorrect\n", __func__);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* enable clock for access ADSP Reg*/
|
||
|
adsp_enable_dsp_clk(true);
|
||
|
/* dump bus status if has bus hang*/
|
||
|
if (is_adsp_bus_monitor_alert()) {
|
||
|
#ifdef CONFIG_MTK_EMI
|
||
|
#ifdef CONFIG_MTK_LASTBUS_INTERFACE
|
||
|
dump_emi_outstanding(); /* check infra, dump all info*/
|
||
|
lastbus_timeout_dump(); /* check infra/peri, dump both info */
|
||
|
#endif
|
||
|
#endif
|
||
|
adsp_bus_monitor_dump();
|
||
|
}
|
||
|
|
||
|
if (adsp_reset_type == ADSP_RESET_TYPE_AWAKE)
|
||
|
pr_info("%s(): adsp awake fail, wait system back\n", __func__);
|
||
|
|
||
|
/* make sure adsp is in idle state */
|
||
|
while (--timeout) {
|
||
|
if ((readl(ADSP_SLEEP_STATUS_REG) & ADSP_A_IS_WFI) &&
|
||
|
(readl(ADSP_DBG_PEND_CNT) == 0)) {
|
||
|
adsp_reset();
|
||
|
if (readl(ADSP_SLEEP_STATUS_REG) & ADSP_A_IS_ACTIVE) {
|
||
|
adsp_reset_flag = 1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
msleep(20);
|
||
|
}
|
||
|
|
||
|
if (!adsp_reset_flag) {
|
||
|
if (readl(ADSP_DBG_PEND_CNT))
|
||
|
pr_info("%s(): failed, bypass and wait\n", __func__);
|
||
|
else
|
||
|
adsp_reset();
|
||
|
}
|
||
|
#if ADSP_BOOT_TIME_OUT_MONITOR
|
||
|
if (!work_busy(&adsp_timeout_work.work))
|
||
|
queue_delayed_work(adsp_workqueue, &adsp_timeout_work,
|
||
|
jiffies + ADSP_READY_TIMEOUT);
|
||
|
#endif
|
||
|
adsp_enable_dsp_clk(false);
|
||
|
}
|
||
|
/*
|
||
|
*/
|
||
|
void adsp_send_reset_wq(enum ADSP_RESET_TYPE type, enum adsp_core_id core_id)
|
||
|
{
|
||
|
adsp_sys_reset_work.flags = (unsigned int) type;
|
||
|
queue_work(adsp_reset_workqueue, &adsp_sys_reset_work.work);
|
||
|
}
|
||
|
|
||
|
void adsp_recovery_init(void)
|
||
|
{
|
||
|
/*create wq for scp reset*/
|
||
|
adsp_reset_workqueue = create_singlethread_workqueue("ADSP_RESET_WQ");
|
||
|
/*init reset work*/
|
||
|
INIT_WORK(&adsp_sys_reset_work.work, adsp_sys_reset_ws);
|
||
|
/*init completion for identify adsp aed finished*/
|
||
|
init_completion(&adsp_sys_reset_cp);
|
||
|
adsp_reset_lock = wakeup_source_register(NULL, "adsp reset wakelock");
|
||
|
/* init reset by cmd flag*/
|
||
|
adsp_recovery_flag[ADSP_A_ID] = ADSP_RECOVERY_OK;
|
||
|
}
|
||
|
#endif
|
||
|
/*
|
||
|
* callback function for work struct
|
||
|
* notify apps to start their tasks or generate an exception according to flag
|
||
|
* NOTE: this function may be blocked
|
||
|
* and should not be called in interrupt context
|
||
|
* @param ws: work struct
|
||
|
*/
|
||
|
static void adsp_notify_ws(struct work_struct *ws)
|
||
|
{
|
||
|
#ifdef CFG_RECOVERY_SUPPORT
|
||
|
if (adsp_recovery_flag[ADSP_A_ID] == ADSP_RECOVERY_START) {
|
||
|
mutex_lock(&adsp_A_notify_mutex);
|
||
|
adsp_recovery_flag[ADSP_A_ID] = ADSP_RECOVERY_OK;
|
||
|
atomic_set(&adsp_reset_status, ADSP_RESET_STATUS_STOP);
|
||
|
adsp_extern_notify(ADSP_EVENT_READY);
|
||
|
adsp_deregister_feature(SYSTEM_FEATURE_ID);
|
||
|
mutex_unlock(&adsp_A_notify_mutex);
|
||
|
__pm_relax(adsp_reset_lock);
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* callback function for work struct
|
||
|
* notify apps to start their tasks or generate an exception according to flag
|
||
|
* NOTE: this function may be blocked
|
||
|
* and should not be called in interrupt context
|
||
|
* @param ws: work struct
|
||
|
*/
|
||
|
static void adsp_timeout_ws(struct work_struct *ws)
|
||
|
{
|
||
|
#ifdef CFG_RECOVERY_SUPPORT
|
||
|
if (adsp_timeout_times < 5) {
|
||
|
adsp_timeout_times++;
|
||
|
__pm_relax(adsp_reset_lock);
|
||
|
pr_debug("%s(): cnt (%d)\n", __func__, adsp_timeout_times);
|
||
|
adsp_send_reset_wq(ADSP_RESET_TYPE_AWAKE, ADSP_A_ID);
|
||
|
} else
|
||
|
WARN_ON(1); /* reboot */
|
||
|
#else
|
||
|
adsp_aed(EXCEP_BOOTUP, ADSP_A_ID);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void adsp_reset_ready(uint32_t id)
|
||
|
{
|
||
|
adsp_ready[id] = 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* handle notification from adsp
|
||
|
* mark adsp is ready for running tasks
|
||
|
* @param id: ipi id
|
||
|
* @param data: ipi data
|
||
|
* @param len: length of ipi data
|
||
|
*/
|
||
|
void adsp_A_ready_ipi_handler(int id, void *data, unsigned int len)
|
||
|
{
|
||
|
unsigned int adsp_image_size = *(unsigned int *)data;
|
||
|
|
||
|
if (!adsp_ready[ADSP_A_ID]) {
|
||
|
#if ADSP_BOOT_TIME_OUT_MONITOR
|
||
|
cancel_delayed_work(&adsp_timeout_work);
|
||
|
#endif
|
||
|
/* set adsp ready flag and clear SPM interrupt */
|
||
|
adsp_ready[ADSP_A_ID] = 1;
|
||
|
writel(0x0, ADSP_TO_SPM_REG);
|
||
|
|
||
|
#ifdef CFG_RECOVERY_SUPPORT
|
||
|
adsp_timeout_times = 0;
|
||
|
queue_work(adsp_workqueue, &adsp_notify_work);
|
||
|
#endif
|
||
|
}
|
||
|
/* verify adsp image size */
|
||
|
if (adsp_image_size != ADSP_A_TCM_SIZE) {
|
||
|
pr_info("[ADSP]image size ERROR! AP=0x%zx,ADSP=0x%x\n",
|
||
|
ADSP_A_TCM_SIZE, adsp_image_size);
|
||
|
WARN_ON(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* @return: 1 if adsp is ready for running tasks
|
||
|
*/
|
||
|
int is_adsp_ready(uint32_t id)
|
||
|
{
|
||
|
if (id >= ADSP_CORE_TOTAL)
|
||
|
return -EINVAL;
|
||
|
#ifdef CFG_RECOVERY_SUPPORT
|
||
|
/* exception */
|
||
|
if (atomic_read(&adsp_reset_status) == ADSP_RESET_STATUS_START ||
|
||
|
adsp_recovery_flag[ADSP_A_ID] == ADSP_RECOVERY_START)
|
||
|
return -1;
|
||
|
#endif
|
||
|
return adsp_ready[id];
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* power on adsp
|
||
|
* generate error if power on fail
|
||
|
* @return: 1 if success
|
||
|
*/
|
||
|
uint32_t adsp_power_on(uint32_t enable)
|
||
|
{
|
||
|
if (enable) {
|
||
|
adsp_enable_clock();
|
||
|
adsp_sw_reset();
|
||
|
adsp_set_clock_freq(CLK_DEFAULT_ACTIVE_CK);
|
||
|
adsp_A_send_spm_request(true);
|
||
|
} else {
|
||
|
adsp_set_clock_freq(CLK_DEFAULT_26M_CK);
|
||
|
adsp_disable_clock();
|
||
|
}
|
||
|
pr_debug("-%s (%x)\n", __func__, enable);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static inline ssize_t adsp_A_status_show(struct device *kobj,
|
||
|
struct device_attribute *attr,
|
||
|
char *buf)
|
||
|
{
|
||
|
unsigned int status = 0;
|
||
|
char *adsp_status;
|
||
|
|
||
|
adsp_enable_dsp_clk(true);
|
||
|
status = readl(ADSP_A_SYS_STATUS);
|
||
|
adsp_enable_dsp_clk(false);
|
||
|
|
||
|
switch (status) {
|
||
|
case ADSP_STATUS_ACTIVE:
|
||
|
adsp_status = "ADSP A is active";
|
||
|
break;
|
||
|
case ADSP_STATUS_SUSPEND:
|
||
|
adsp_status = "ADSP A is suspend";
|
||
|
break;
|
||
|
case ADSP_STATUS_SLEEP:
|
||
|
adsp_status = "ADSP A is sleep";
|
||
|
break;
|
||
|
case ADSP_STATUS_RESET:
|
||
|
adsp_status = "ADSP A is reset";
|
||
|
break;
|
||
|
default:
|
||
|
adsp_status = "ADSP A in unknown status";
|
||
|
break;
|
||
|
}
|
||
|
return scnprintf(buf, PAGE_SIZE, "%s\n", adsp_status);
|
||
|
}
|
||
|
DEVICE_ATTR_RO(adsp_A_status);
|
||
|
|
||
|
static inline ssize_t adsp_ipi_test_store(struct device *kobj,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
enum adsp_ipi_status ret;
|
||
|
int value = 0;
|
||
|
|
||
|
if (kstrtoint(buf, 10, &value))
|
||
|
return -EINVAL;
|
||
|
|
||
|
adsp_register_feature(SYSTEM_FEATURE_ID);
|
||
|
|
||
|
if (is_adsp_ready(ADSP_A_ID) == 1) {
|
||
|
ret = adsp_ipi_send(ADSP_IPI_TEST1, &value, sizeof(value),
|
||
|
0, ADSP_A_ID);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* BE CAREFUL! this cmd shouldn't let adsp process over 1s.
|
||
|
* Otherwise, you should register other feature before.
|
||
|
*/
|
||
|
adsp_deregister_feature(SYSTEM_FEATURE_ID);
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
static inline ssize_t adsp_ipi_test_show(struct device *kobj,
|
||
|
struct device_attribute *attr,
|
||
|
char *buf)
|
||
|
{
|
||
|
unsigned int value = 0x5A5A;
|
||
|
enum adsp_ipi_status ret;
|
||
|
|
||
|
if (is_adsp_ready(ADSP_A_ID) == 1) {
|
||
|
ret = adsp_ipi_send(ADSP_IPI_TEST1, &value, sizeof(value),
|
||
|
0, ADSP_A_ID);
|
||
|
return scnprintf(buf, PAGE_SIZE, "ADSP ipi send ret=%d\n", ret);
|
||
|
} else
|
||
|
return scnprintf(buf, PAGE_SIZE, "ADSP is not ready\n");
|
||
|
}
|
||
|
DEVICE_ATTR_RW(adsp_ipi_test);
|
||
|
|
||
|
static inline ssize_t adsp_uart_switch_store(struct device *kobj,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
enum adsp_ipi_status ret;
|
||
|
unsigned int value = 0;
|
||
|
|
||
|
if (kstrtoint(buf, 10, &value))
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (is_adsp_ready(ADSP_A_ID) == 1) {
|
||
|
ret = adsp_ipi_send(ADSP_IPI_TEST1, &value, sizeof(value),
|
||
|
0, ADSP_A_ID);
|
||
|
}
|
||
|
return count;
|
||
|
}
|
||
|
DEVICE_ATTR_WO(adsp_uart_switch);
|
||
|
|
||
|
static inline ssize_t adsp_suspend_cmd_show(struct device *kobj,
|
||
|
struct device_attribute *attr,
|
||
|
char *buf)
|
||
|
{
|
||
|
return adsp_dump_feature_state(buf, PAGE_SIZE);
|
||
|
}
|
||
|
|
||
|
static inline ssize_t adsp_suspend_cmd_store(struct device *kobj,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
uint32_t id = 0;
|
||
|
char *temp = NULL, *token1 = NULL, *token2 = NULL;
|
||
|
char *pin = NULL;
|
||
|
char delim[] = " ,\t\n";
|
||
|
|
||
|
temp = kstrdup(buf, GFP_KERNEL);
|
||
|
pin = temp;
|
||
|
token1 = strsep(&pin, delim);
|
||
|
if (token1 == NULL)
|
||
|
return -EINVAL;
|
||
|
token2 = strsep(&pin, delim);
|
||
|
if (token2 == NULL)
|
||
|
return -EINVAL;
|
||
|
|
||
|
id = adsp_get_feature_index(token2);
|
||
|
|
||
|
if ((strcmp(token1, "regi") == 0) && (id >= 0))
|
||
|
adsp_register_feature(id);
|
||
|
|
||
|
if ((strcmp(token1, "deregi") == 0) && (id >= 0))
|
||
|
adsp_deregister_feature(id);
|
||
|
|
||
|
kfree(temp);
|
||
|
return count;
|
||
|
}
|
||
|
DEVICE_ATTR_RW(adsp_suspend_cmd);
|
||
|
|
||
|
#ifdef CFG_RECOVERY_SUPPORT
|
||
|
static ssize_t adsp_recovery_flag_show(struct device *dev,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
return scnprintf(buf, PAGE_SIZE, "%d\n", adsp_recovery_flag[ADSP_A_ID]);
|
||
|
}
|
||
|
DEVICE_ATTR_RO(adsp_recovery_flag);
|
||
|
#endif
|
||
|
|
||
|
static struct attribute *adsp_default_attrs[] = {
|
||
|
&dev_attr_adsp_A_status.attr,
|
||
|
&dev_attr_adsp_ipi_test.attr,
|
||
|
&dev_attr_adsp_uart_switch.attr,
|
||
|
&dev_attr_adsp_suspend_cmd.attr,
|
||
|
#ifdef CFG_RECOVERY_SUPPORT
|
||
|
&dev_attr_adsp_recovery_flag.attr,
|
||
|
#endif
|
||
|
NULL,
|
||
|
};
|
||
|
|
||
|
static struct attribute_group adsp_default_attr_group = {
|
||
|
.attrs = adsp_default_attrs,
|
||
|
};
|
||
|
|
||
|
static const struct attribute_group *adsp_attr_groups[] = {
|
||
|
&adsp_default_attr_group,
|
||
|
&adsp_awake_attr_group,
|
||
|
&adsp_dvfs_attr_group,
|
||
|
&adsp_logger_attr_group,
|
||
|
&adsp_excep_attr_group,
|
||
|
NULL,
|
||
|
};
|
||
|
|
||
|
const struct file_operations adsp_device_fops = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.read = adsp_A_log_if_read,
|
||
|
.open = adsp_A_log_if_open,
|
||
|
.poll = adsp_A_log_if_poll,
|
||
|
.unlocked_ioctl = adsp_driver_ioctl,
|
||
|
#ifdef CONFIG_COMPAT
|
||
|
.compat_ioctl = adsp_driver_compat_ioctl,
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
static struct miscdevice adsp_device = {
|
||
|
.minor = MISC_DYNAMIC_MINOR,
|
||
|
.name = "adsp",
|
||
|
.groups = adsp_attr_groups,
|
||
|
.fops = &adsp_device_fops,
|
||
|
};
|
||
|
|
||
|
static ssize_t adsp_debug_read(struct file *file, char __user *buf,
|
||
|
size_t count, loff_t *pos)
|
||
|
{
|
||
|
char *buffer = NULL; /* for reduce kernel stack */
|
||
|
int ret = 0;
|
||
|
size_t n = 0, max_size;
|
||
|
|
||
|
buffer = adsp_get_reserve_mem_virt(ADSP_A_DEBUG_DUMP_MEM_ID);
|
||
|
max_size = adsp_get_reserve_mem_size(ADSP_A_DEBUG_DUMP_MEM_ID);
|
||
|
|
||
|
n = strnlen(buffer, max_size);
|
||
|
|
||
|
ret = simple_read_from_buffer(buf, count, pos, buffer, n);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static ssize_t adsp_debug_write(struct file *filp, const char __user *buffer,
|
||
|
size_t count, loff_t *ppos)
|
||
|
{
|
||
|
char buf[64];
|
||
|
|
||
|
if (copy_from_user(buf, buffer, min(count, sizeof(buf))))
|
||
|
return -EFAULT;
|
||
|
|
||
|
if (adsp_register_feature(SYSTEM_FEATURE_ID) == 0) {
|
||
|
adsp_ipi_send(ADSP_IPI_ADSP_TIMER, buf,
|
||
|
min(count, sizeof(buf)), 0, ADSP_A_ID);
|
||
|
adsp_deregister_feature(SYSTEM_FEATURE_ID);
|
||
|
}
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
static const struct file_operations adsp_debug_ops = {
|
||
|
.open = simple_open,
|
||
|
.read = adsp_debug_read,
|
||
|
.write = adsp_debug_write,
|
||
|
};
|
||
|
|
||
|
void adsp_update_memory_protect_info(void)
|
||
|
{
|
||
|
struct adsp_mpu_info_t *mpu_info;
|
||
|
phys_addr_t nc_addr = adsp_get_reserve_mem_phys(ADSP_MPU_NONCACHE_ID);
|
||
|
|
||
|
mpu_info = (struct adsp_mpu_info_t *)ADSP_A_MPUINFO_BUFFER;
|
||
|
mpu_info->data_non_cache_addr = nc_addr;
|
||
|
mpu_info->data_non_cache_size =
|
||
|
adsp_get_reserve_mem_phys(ADSP_A_SHARED_MEM_END)
|
||
|
+ adsp_get_reserve_mem_size(ADSP_A_SHARED_MEM_END)
|
||
|
- nc_addr;
|
||
|
}
|
||
|
|
||
|
void adsp_enable_dsp_clk(bool enable)
|
||
|
{
|
||
|
if (enable) {
|
||
|
pr_debug("enable dsp clk\n");
|
||
|
adsp_enable_clock();
|
||
|
} else {
|
||
|
pr_debug("disable dsp clk\n");
|
||
|
adsp_disable_clock();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int adsp_system_sleep_suspend(struct device *dev)
|
||
|
{
|
||
|
mutex_lock(&adsp_suspend_mutex);
|
||
|
if ((is_adsp_ready(ADSP_A_ID) == 1) || adsp_feature_is_active()) {
|
||
|
adsp_timesync_suspend(1);
|
||
|
adsp_awake_unlock_adsppll(ADSP_A_ID, 1);
|
||
|
}
|
||
|
mutex_unlock(&adsp_suspend_mutex);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int adsp_system_sleep_resume(struct device *dev)
|
||
|
{
|
||
|
mutex_lock(&adsp_suspend_mutex);
|
||
|
if ((is_adsp_ready(ADSP_A_ID) == 1) || adsp_feature_is_active()) {
|
||
|
/*wake adsp up*/
|
||
|
adsp_awake_unlock_adsppll(ADSP_A_ID, 0);
|
||
|
adsp_timesync_resume();
|
||
|
}
|
||
|
mutex_unlock(&adsp_suspend_mutex);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int adsp_syscore_suspend(void)
|
||
|
{
|
||
|
if ((is_adsp_ready(ADSP_A_ID) != 1) && !adsp_feature_is_active()) {
|
||
|
adsp_bus_sleep_protect(true);
|
||
|
spm_adsp_mem_protect();
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void adsp_syscore_resume(void)
|
||
|
{
|
||
|
if ((is_adsp_ready(ADSP_A_ID) != 1) && !adsp_feature_is_active()) {
|
||
|
spm_adsp_mem_unprotect();
|
||
|
adsp_bus_sleep_protect(false);
|
||
|
/* release adsp sw_reset,
|
||
|
* let ap is able to write adsp cfg/dtcm
|
||
|
* no matter adsp is suspend.
|
||
|
*/
|
||
|
adsp_enable_clock();
|
||
|
writel((ADSP_A_SW_RSTN | ADSP_A_SW_DBG_RSTN), ADSP_A_REBOOT);
|
||
|
udelay(1);
|
||
|
writel(0, ADSP_A_REBOOT);
|
||
|
|
||
|
#if ADSP_BUS_MONITOR_INIT_ENABLE
|
||
|
adsp_bus_monitor_init(); /* reinit bus monitor hw */
|
||
|
#endif
|
||
|
adsp_disable_clock();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef CFG_RECOVERY_SUPPORT
|
||
|
/* user-space event notify */
|
||
|
static int adsp_user_event_notify(struct notifier_block *nb,
|
||
|
unsigned long event, void *ptr)
|
||
|
{
|
||
|
struct device *dev = adsp_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, uevent(%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,
|
||
|
};
|
||
|
#endif
|
||
|
|
||
|
static int adsp_device_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct resource *res;
|
||
|
u64 sysram[2] = {0, 0};
|
||
|
struct device *dev = &pdev->dev;
|
||
|
|
||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||
|
adspreg.cfg = devm_ioremap_resource(dev, res);
|
||
|
adspreg.cfgregsize = resource_size(res);
|
||
|
if (IS_ERR(adspreg.cfg))
|
||
|
goto ERROR;
|
||
|
|
||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||
|
adspreg.iram = devm_ioremap_resource(dev, res);
|
||
|
adspreg.i_tcmsize = resource_size(res);
|
||
|
if (IS_ERR(adspreg.iram))
|
||
|
goto ERROR;
|
||
|
|
||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
|
||
|
adspreg.dram = devm_ioremap_resource(dev, res);
|
||
|
adspreg.d_tcmsize = resource_size(res);
|
||
|
if (IS_ERR(adspreg.dram))
|
||
|
goto ERROR;
|
||
|
|
||
|
adspreg.infracfg_ao = of_iomap(dev->of_node, 3);
|
||
|
if (IS_ERR(adspreg.infracfg_ao))
|
||
|
goto ERROR;
|
||
|
|
||
|
adspreg.pericfg = of_iomap(dev->of_node, 4);
|
||
|
if (IS_ERR(adspreg.pericfg))
|
||
|
goto ERROR;
|
||
|
|
||
|
adspreg.total_tcmsize = adspreg.i_tcmsize + adspreg.d_tcmsize;
|
||
|
|
||
|
adspreg.wdt_irq = platform_get_irq(pdev, 0);
|
||
|
if (request_irq(adspreg.wdt_irq, adsp_A_wdt_handler,
|
||
|
IRQF_TRIGGER_LOW, "ADSP A WDT", NULL))
|
||
|
goto ERROR;
|
||
|
|
||
|
adspreg.ipc_irq = platform_get_irq(pdev, 1);
|
||
|
if (request_irq(adspreg.ipc_irq, adsp_A_irq_handler,
|
||
|
IRQF_TRIGGER_LOW, "ADSP A IPC2HOST", NULL))
|
||
|
goto ERROR;
|
||
|
|
||
|
of_property_read_u64_array(pdev->dev.of_node, "sysram",
|
||
|
sysram, ARRAY_SIZE(sysram));
|
||
|
|
||
|
adspreg.sysram = ioremap_wc(sysram[0], sysram[1]);
|
||
|
adspreg.sysram_size = sysram[1];
|
||
|
if (IS_ERR(adspreg.sysram))
|
||
|
goto ERROR;
|
||
|
adsp_set_reserve_mblock(ADSP_A_SYSTEM_MEM_ID, sysram[0],
|
||
|
adspreg.sysram, adspreg.sysram_size);
|
||
|
adsp_reserve_memory_ioremap(adspreg.sharedram, adspreg.shared_size);
|
||
|
|
||
|
adspreg.clkctrl = adspreg.cfg + ADSP_CLK_CTRL_OFFSET;
|
||
|
adsp_clk_device_probe(&pdev->dev);
|
||
|
|
||
|
adsp_enable = 1;
|
||
|
return 0;
|
||
|
ERROR:
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
static int adsp_device_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
adsp_clk_device_remove(&pdev->dev);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct of_device_id adsp_of_ids[] = {
|
||
|
{ .compatible = "mediatek,audio_dsp", },
|
||
|
{}
|
||
|
};
|
||
|
|
||
|
static const struct dev_pm_ops adsp_pm_ops = {
|
||
|
SET_SYSTEM_SLEEP_PM_OPS(adsp_system_sleep_suspend,
|
||
|
adsp_system_sleep_resume)
|
||
|
};
|
||
|
|
||
|
static struct syscore_ops adsp_syscore_ops = {
|
||
|
.resume = adsp_syscore_resume,
|
||
|
.suspend = adsp_syscore_suspend,
|
||
|
};
|
||
|
|
||
|
static struct platform_driver mtk_adsp_device = {
|
||
|
.probe = adsp_device_probe,
|
||
|
.remove = adsp_device_remove,
|
||
|
.driver = {
|
||
|
.name = "adsp",
|
||
|
.owner = THIS_MODULE,
|
||
|
#ifdef CONFIG_OF
|
||
|
.of_match_table = adsp_of_ids,
|
||
|
#endif
|
||
|
#ifdef CONFIG_PM
|
||
|
.pm = &adsp_pm_ops,
|
||
|
#endif
|
||
|
},
|
||
|
};
|
||
|
/*
|
||
|
* driver initialization entry point
|
||
|
*/
|
||
|
static int __init adsp_init(void)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
adsp_enable = 0;
|
||
|
ret = platform_driver_register(&mtk_adsp_device);
|
||
|
if (unlikely(ret != 0)) {
|
||
|
pr_err("[ADSP] platform driver register fail\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = misc_register(&adsp_device);
|
||
|
if (unlikely(ret != 0)) {
|
||
|
pr_err("[ADSP] misc register failed\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
register_syscore_ops(&adsp_syscore_ops);
|
||
|
|
||
|
return ret;
|
||
|
|
||
|
}
|
||
|
|
||
|
static int __init adsp_module_init(void)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
if (!adsp_enable) {
|
||
|
pr_info("[adsp] core 0 is not enabled\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
adsp_workqueue = create_workqueue("ADSP_WQ");
|
||
|
#ifdef CONFIG_DEBUG_FS
|
||
|
adsp_debugfs = debugfs_create_file("audiodsp", S_IFREG | 0644, NULL,
|
||
|
(void *)&adsp_device, &adsp_debug_ops);
|
||
|
if (IS_ERR(adsp_debugfs))
|
||
|
return PTR_ERR(adsp_debugfs);
|
||
|
#endif
|
||
|
/* adsp initialize */
|
||
|
adsp_dvfs_init();
|
||
|
adsp_power_on(true);
|
||
|
adsp_update_memory_protect_info();
|
||
|
adsp_awake_init();
|
||
|
|
||
|
ret = adsp_excep_init();
|
||
|
if (ret)
|
||
|
goto ERROR;
|
||
|
|
||
|
adsp_suspend_init();
|
||
|
adsp_A_irq_init();
|
||
|
adsp_A_ipi_init();
|
||
|
adsp_ipi_registration(ADSP_IPI_ADSP_A_READY, adsp_A_ready_ipi_handler,
|
||
|
"adsp_A_ready");
|
||
|
|
||
|
ret = adsp_timesync_init();
|
||
|
if (ret)
|
||
|
goto ERROR;
|
||
|
|
||
|
#if ADSP_LOGGER_ENABLE
|
||
|
ret = adsp_logger_init();
|
||
|
if (ret)
|
||
|
goto ERROR;
|
||
|
#endif
|
||
|
#if ADSP_TRAX
|
||
|
ret = adsp_trax_init();
|
||
|
if (ret)
|
||
|
goto ERROR;
|
||
|
#endif
|
||
|
#ifdef CFG_RECOVERY_SUPPORT
|
||
|
adsp_recovery_init();
|
||
|
adsp_A_register_notify(&adsp_uevent_notifier);
|
||
|
#endif
|
||
|
#if ADSP_BUS_MONITOR_INIT_ENABLE
|
||
|
adsp_bus_monitor_init();
|
||
|
#endif
|
||
|
adsp_release_runstall(true);
|
||
|
|
||
|
#if ADSP_BOOT_TIME_OUT_MONITOR
|
||
|
queue_delayed_work(adsp_workqueue, &adsp_timeout_work,
|
||
|
jiffies + ADSP_READY_TIMEOUT);
|
||
|
#endif
|
||
|
pr_debug("[ADSP] driver_init_done\n");
|
||
|
return ret;
|
||
|
|
||
|
ERROR:
|
||
|
pr_info("%s fail ret(%d)\n", __func__, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* driver exit point
|
||
|
*/
|
||
|
static void __exit adsp_exit(void)
|
||
|
{
|
||
|
free_irq(adspreg.wdt_irq, NULL);
|
||
|
free_irq(adspreg.ipc_irq, NULL);
|
||
|
|
||
|
misc_deregister(&adsp_device);
|
||
|
#ifdef CONFIG_DEBUG_FS
|
||
|
debugfs_remove(adsp_debugfs);
|
||
|
#endif
|
||
|
flush_workqueue(adsp_workqueue);
|
||
|
destroy_workqueue(adsp_workqueue);
|
||
|
#ifdef CFG_RECOVERY_SUPPORT
|
||
|
flush_workqueue(adsp_reset_workqueue);
|
||
|
destroy_workqueue(adsp_reset_workqueue);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static int __init adsp_late_init(void)
|
||
|
{
|
||
|
adsp_set_emimpu_region();
|
||
|
pr_info("[ADSP] late_init done\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
subsys_initcall(adsp_init);
|
||
|
module_init(adsp_module_init);
|
||
|
module_exit(adsp_exit);
|
||
|
late_initcall(adsp_late_init);
|
||
|
|