458 lines
10 KiB
C
458 lines
10 KiB
C
|
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
|
/*
|
||
|
|
* Copyright (c) 2018 MediaTek Inc.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <linux/cpu_pm.h>
|
||
|
|
#include <linux/cpufreq.h>
|
||
|
|
#include <linux/io.h>
|
||
|
|
#include <linux/notifier.h>
|
||
|
|
#include <linux/smp.h>
|
||
|
|
|
||
|
|
#include "mtk_qos_ipi.h"
|
||
|
|
#include "mtk_qos_prefetch.h"
|
||
|
|
#include "mtk_qos_prefetch_data.h"
|
||
|
|
#include <mtk_qos_sram.h>
|
||
|
|
#include <linux/arm-smccc.h>
|
||
|
|
#include <mtk_secure_api.h>
|
||
|
|
|
||
|
|
/* #define QOS_PREFETCH_USE_SMP_CALL */
|
||
|
|
/* #define QOS_PREFETCH_USE_TIMER */
|
||
|
|
|
||
|
|
#define CLUSTER0_MASK 0xFF
|
||
|
|
#define QOS_PREFETCH_VALUE(v, i) (((v) >> ((i) * 4)) & 0xf)
|
||
|
|
|
||
|
|
#define PREFETCH_DEFAULT_VAL (0xffffffff)
|
||
|
|
unsigned int prefetch_val = PREFETCH_DEFAULT_VAL;
|
||
|
|
|
||
|
|
static int qos_prefetch_cpumask = CLUSTER0_MASK;
|
||
|
|
spinlock_t qos_prefetch_cpumask_lock;
|
||
|
|
|
||
|
|
static int qos_prefetch_enabled;
|
||
|
|
static int qos_prefetch_forced;
|
||
|
|
static int qos_prefetch_log_enabled;
|
||
|
|
static unsigned int qos_prefetch_count;
|
||
|
|
static unsigned int qos_prefetch_buf[3];
|
||
|
|
static BLOCKING_NOTIFIER_HEAD(qos_prefetch_chain_head);
|
||
|
|
|
||
|
|
#ifdef QOS_PREFETCH_USE_TIMER
|
||
|
|
#define QOS_PREFETCH_TIME msecs_to_jiffies(1000)
|
||
|
|
struct timer_list qos_prefetch_timer;
|
||
|
|
#endif /* QOS_PREFETCH_USE_TIMER */
|
||
|
|
|
||
|
|
static void qos_prefetch_set_prefetch(void *info)
|
||
|
|
{
|
||
|
|
unsigned long level = *(unsigned int *)info;
|
||
|
|
struct arm_smccc_res res;
|
||
|
|
|
||
|
|
// level
|
||
|
|
// 0: default
|
||
|
|
// 1: ctrl 1
|
||
|
|
// 2: ctrl 2
|
||
|
|
// 3: ctrl 3
|
||
|
|
arm_smccc_smc(MTK_SIP_CACHE_CONTROL,
|
||
|
|
1, level, 0, 0, 0, 0, 0, &res);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void qos_prefetch_update_single(int target_cpu, int val)
|
||
|
|
{
|
||
|
|
#ifdef QOS_PREFETCH_USE_SMP_CALL
|
||
|
|
int cpu = smp_processor_id();
|
||
|
|
|
||
|
|
if (cpu != target_cpu)
|
||
|
|
smp_call_function_single(cpu, qos_prefetch_set_prefetch,
|
||
|
|
&val, 1);
|
||
|
|
else
|
||
|
|
#endif /* QOS_PREFETCH_USE_SMP_CALL */
|
||
|
|
qos_prefetch_set_prefetch(&val);
|
||
|
|
}
|
||
|
|
|
||
|
|
int is_qos_prefetch_enabled(void)
|
||
|
|
{
|
||
|
|
return qos_prefetch_enabled;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(is_qos_prefetch_enabled);
|
||
|
|
|
||
|
|
#ifdef CONFIG_CPU_PM
|
||
|
|
static int qos_prefetch_cpuhp_online(unsigned int cpu)
|
||
|
|
{
|
||
|
|
unsigned long spinlock_flags;
|
||
|
|
unsigned int val;
|
||
|
|
|
||
|
|
if (prefetch_val == PREFETCH_DEFAULT_VAL)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
spin_lock_irqsave(&qos_prefetch_cpumask_lock,
|
||
|
|
spinlock_flags);
|
||
|
|
|
||
|
|
qos_prefetch_cpumask |= BIT(cpu);
|
||
|
|
|
||
|
|
if (is_qos_prefetch_enabled()) {
|
||
|
|
val = QOS_PREFETCH_VALUE(prefetch_val, cpu);
|
||
|
|
if (val)
|
||
|
|
qos_prefetch_update_single(cpu, val);
|
||
|
|
}
|
||
|
|
|
||
|
|
spin_unlock_irqrestore(&qos_prefetch_cpumask_lock,
|
||
|
|
spinlock_flags);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int qos_prefetch_cpuhp_offline(unsigned int cpu)
|
||
|
|
{
|
||
|
|
unsigned long spinlock_flags;
|
||
|
|
|
||
|
|
spin_lock_irqsave(&qos_prefetch_cpumask_lock,
|
||
|
|
spinlock_flags);
|
||
|
|
|
||
|
|
qos_prefetch_cpumask &= ~BIT(cpu);
|
||
|
|
|
||
|
|
spin_unlock_irqrestore(&qos_prefetch_cpumask_lock,
|
||
|
|
spinlock_flags);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int qos_prefetch_sched_pm_notifier(struct notifier_block *self,
|
||
|
|
unsigned long cmd, void *v)
|
||
|
|
{
|
||
|
|
unsigned int cpu = smp_processor_id();
|
||
|
|
|
||
|
|
if (cmd == CPU_PM_EXIT)
|
||
|
|
qos_prefetch_cpuhp_online(cpu);
|
||
|
|
else if (cmd == CPU_PM_ENTER)
|
||
|
|
qos_prefetch_cpuhp_offline(cpu);
|
||
|
|
|
||
|
|
return NOTIFY_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
static struct notifier_block qos_prefetch_sched_pm_notifier_block = {
|
||
|
|
.notifier_call = qos_prefetch_sched_pm_notifier,
|
||
|
|
};
|
||
|
|
|
||
|
|
static void qos_prefetch_sched_pm_init(void)
|
||
|
|
{
|
||
|
|
cpu_pm_register_notifier(&qos_prefetch_sched_pm_notifier_block);
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
static inline void qos_prefetch_sched_pm_init(void) { }
|
||
|
|
#endif /* CONFIG_CPU_PM */
|
||
|
|
|
||
|
|
void qos_prefetch_enable(int enable)
|
||
|
|
{
|
||
|
|
#if defined(CONFIG_MTK_TINYSYS_SSPM_SUPPORT)
|
||
|
|
struct qos_ipi_data qos_ipi_d;
|
||
|
|
|
||
|
|
qos_ipi_d.cmd = QOS_IPI_QOS_PREFETCH_ENABLE;
|
||
|
|
qos_ipi_d.u.qos_prefetch_enable.enable = enable;
|
||
|
|
qos_ipi_to_sspm_command(&qos_ipi_d, 2);
|
||
|
|
#endif
|
||
|
|
qos_prefetch_enabled = enable;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(qos_prefetch_enable);
|
||
|
|
|
||
|
|
void qos_prefetch_force(int force)
|
||
|
|
{
|
||
|
|
#if defined(CONFIG_MTK_TINYSYS_SSPM_SUPPORT)
|
||
|
|
struct qos_ipi_data qos_ipi_d;
|
||
|
|
|
||
|
|
/* index from 1 to 4 */
|
||
|
|
if (force > 0 && force <= 4)
|
||
|
|
qos_prefetch_forced = force;
|
||
|
|
else
|
||
|
|
qos_prefetch_forced = 0;
|
||
|
|
|
||
|
|
qos_ipi_d.cmd = QOS_IPI_QOS_PREFETCH_FORCE;
|
||
|
|
qos_ipi_d.u.qos_prefetch_force.force = qos_prefetch_forced;
|
||
|
|
qos_ipi_to_sspm_command(&qos_ipi_d, 2);
|
||
|
|
#else
|
||
|
|
qos_prefetch_forced = 0;
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(qos_prefetch_force);
|
||
|
|
|
||
|
|
int is_qos_prefetch_force(void)
|
||
|
|
{
|
||
|
|
return qos_prefetch_forced;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(is_qos_prefetch_force);
|
||
|
|
|
||
|
|
static void qos_prefetch_setting(int cmd, int val)
|
||
|
|
{
|
||
|
|
#if defined(CONFIG_MTK_TINYSYS_SSPM_SUPPORT)
|
||
|
|
struct qos_ipi_data qos_ipi_d;
|
||
|
|
|
||
|
|
qos_ipi_d.cmd = cmd;
|
||
|
|
qos_ipi_d.u.qos_prefetch_setting.val = val;
|
||
|
|
qos_ipi_to_sspm_command(&qos_ipi_d, 2);
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
ssize_t qos_prefetch_setting_get(char *buf)
|
||
|
|
{
|
||
|
|
int ret = 0;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
ret += sprintf(buf + ret, "qos_cpu_opp_bound ");
|
||
|
|
for (i = 0; i < QOS_CPUS_NR; i++)
|
||
|
|
ret += sprintf(buf + ret, "%d ", qos_cpu_opp_bound[i]);
|
||
|
|
ret += sprintf(buf + ret, "\n");
|
||
|
|
|
||
|
|
ret += sprintf(buf + ret, "qos_cpu_power_ratio_up %d\n",
|
||
|
|
qos_cpu_power_ratio_up);
|
||
|
|
ret += sprintf(buf + ret, "qos_cpu_power_ratio_dn %d\n",
|
||
|
|
qos_cpu_power_ratio_dn);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(qos_prefetch_setting_get);
|
||
|
|
|
||
|
|
ssize_t qos_prefetch_setting_set(const char *buf, size_t count)
|
||
|
|
{
|
||
|
|
int ret = -EINVAL;
|
||
|
|
char cmd[64];
|
||
|
|
u32 val_1;
|
||
|
|
u32 val_2;
|
||
|
|
|
||
|
|
if (!buf)
|
||
|
|
return -ENOMEM;
|
||
|
|
|
||
|
|
if (count >= PAGE_SIZE)
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
ret = sscanf(buf, "%63s %d %d", cmd, &val_1, &val_2);
|
||
|
|
if (ret < 1) {
|
||
|
|
ret = -EPERM;
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!strcmp(cmd, "qos_cpu_opp_bound")) {
|
||
|
|
if (ret == 3 && val_1 < QOS_CPUS_NR) {
|
||
|
|
qos_cpu_opp_bound[val_1] = val_2;
|
||
|
|
#if defined(CONFIG_MTK_TINYSYS_SSPM_SUPPORT)
|
||
|
|
qos_prefetch_setting(QOS_IPI_QOS_PREFETCH_CPU_OPP,
|
||
|
|
val_1 << 16 | val_2);
|
||
|
|
#endif /* CONFIG_MTK_TINYSYS_SSPM_SUPPORT */
|
||
|
|
}
|
||
|
|
} else if (!strcmp(cmd, "qos_cpu_power_ratio_up")) {
|
||
|
|
qos_cpu_power_ratio_up = val_1;
|
||
|
|
#if defined(CONFIG_MTK_TINYSYS_SSPM_SUPPORT)
|
||
|
|
qos_prefetch_setting(QOS_IPI_QOS_PREFETCH_POWER_RATIO_UP,
|
||
|
|
val_1);
|
||
|
|
#endif /* CONFIG_MTK_TINYSYS_SSPM_SUPPORT */
|
||
|
|
} else if (!strcmp(cmd, "qos_cpu_power_ratio_dn")) {
|
||
|
|
qos_cpu_power_ratio_dn = val_1;
|
||
|
|
#if defined(CONFIG_MTK_TINYSYS_SSPM_SUPPORT)
|
||
|
|
qos_prefetch_setting(QOS_IPI_QOS_PREFETCH_POWER_RATIO_DN,
|
||
|
|
val_1);
|
||
|
|
#endif /* CONFIG_MTK_TINYSYS_SSPM_SUPPORT */
|
||
|
|
}
|
||
|
|
|
||
|
|
out:
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
return count;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(qos_prefetch_setting_set);
|
||
|
|
|
||
|
|
int is_qos_prefetch_log_enabled(void)
|
||
|
|
{
|
||
|
|
return qos_prefetch_log_enabled;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(is_qos_prefetch_log_enabled);
|
||
|
|
|
||
|
|
void qos_prefetch_log_enable(int enable)
|
||
|
|
{
|
||
|
|
qos_prefetch_log_enabled = enable;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(qos_prefetch_log_enable);
|
||
|
|
|
||
|
|
unsigned int get_qos_prefetch_count(void)
|
||
|
|
{
|
||
|
|
return qos_prefetch_count;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(get_qos_prefetch_count);
|
||
|
|
|
||
|
|
unsigned int *get_qos_prefetch_buf(void)
|
||
|
|
{
|
||
|
|
return qos_prefetch_buf;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(get_qos_prefetch_buf);
|
||
|
|
|
||
|
|
int register_prefetch_notifier(struct notifier_block *nb)
|
||
|
|
{
|
||
|
|
return blocking_notifier_chain_register
|
||
|
|
(&qos_prefetch_chain_head, nb);
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(register_prefetch_notifier);
|
||
|
|
|
||
|
|
int unregister_prefetch_notifier(struct notifier_block *nb)
|
||
|
|
{
|
||
|
|
return blocking_notifier_chain_unregister
|
||
|
|
(&qos_prefetch_chain_head, nb);
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(unregister_prefetch_notifier);
|
||
|
|
|
||
|
|
int prefetch_notifier_call_chain(unsigned long val, void *v)
|
||
|
|
{
|
||
|
|
int ret = NOTIFY_DONE;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
if (!is_qos_prefetch_enabled())
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
if (val > 0 && val <= 4) {
|
||
|
|
for (i = 0; (val & BIT(i)) == 0; i++)
|
||
|
|
;
|
||
|
|
qos_prefetch_count++;
|
||
|
|
qos_prefetch_buf[i]++;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (is_qos_prefetch_log_enabled()) {
|
||
|
|
pr_info("#@# %s(%d) val 0x%lx\n",
|
||
|
|
__func__, __LINE__, val);
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = blocking_notifier_call_chain(&qos_prefetch_chain_head, val, v);
|
||
|
|
|
||
|
|
return notifier_to_errno(ret);
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(prefetch_notifier_call_chain);
|
||
|
|
|
||
|
|
void qos_prefetch_update_all(void)
|
||
|
|
{
|
||
|
|
#ifdef QOS_PREFETCH_STATUS_OFFSET
|
||
|
|
unsigned long spinlock_flags;
|
||
|
|
unsigned int val;
|
||
|
|
int cpu_live;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
spin_lock_irqsave(&qos_prefetch_cpumask_lock,
|
||
|
|
spinlock_flags);
|
||
|
|
|
||
|
|
prefetch_val = qos_sram_read(QOS_PREFETCH_STATUS_OFFSET);
|
||
|
|
|
||
|
|
for (i = 0; i < 8; i++) {
|
||
|
|
|
||
|
|
if (!cpu_online(i) || cpu_isolated(i))
|
||
|
|
continue;
|
||
|
|
|
||
|
|
cpu_live = qos_prefetch_cpumask & BIT(i);
|
||
|
|
if (cpu_live) {
|
||
|
|
val = QOS_PREFETCH_VALUE(prefetch_val, i);
|
||
|
|
|
||
|
|
if (val)
|
||
|
|
qos_prefetch_update_single(i, val);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
spin_unlock_irqrestore(&qos_prefetch_cpumask_lock,
|
||
|
|
spinlock_flags);
|
||
|
|
#endif /* QOS_PREFETCH_STATUS_OFFSET */
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(qos_prefetch_update_all);
|
||
|
|
|
||
|
|
#ifdef QOS_PREFETCH_USE_TIMER
|
||
|
|
static void qos_prefetch_timer_fn(unsigned long data)
|
||
|
|
{
|
||
|
|
if (is_qos_prefetch_enabled())
|
||
|
|
qos_prefetch_update_all();
|
||
|
|
}
|
||
|
|
#endif /* QOS_PREFETCH_USE_TIMER */
|
||
|
|
|
||
|
|
#ifdef CONFIG_CPU_FREQ
|
||
|
|
static int cpu_freq_old;
|
||
|
|
|
||
|
|
static int qos_prefetch_cpu_freq_callback(struct notifier_block *nb,
|
||
|
|
unsigned long val, void *data)
|
||
|
|
{
|
||
|
|
struct cpufreq_freqs *freq = data;
|
||
|
|
int cpu = freq->cpu;
|
||
|
|
#ifdef QOS_PREFETCH_USE_TIMER
|
||
|
|
unsigned long expires;
|
||
|
|
#endif /* QOS_PREFETCH_USE_TIMER */
|
||
|
|
|
||
|
|
if (cpu < 4)
|
||
|
|
return NOTIFY_OK;
|
||
|
|
|
||
|
|
switch (val) {
|
||
|
|
case CPUFREQ_POSTCHANGE:
|
||
|
|
if (freq->new == cpu_freq_old)
|
||
|
|
return NOTIFY_OK;
|
||
|
|
|
||
|
|
cpu_freq_old = freq->new;
|
||
|
|
|
||
|
|
if (is_qos_prefetch_enabled()) {
|
||
|
|
qos_prefetch_update_all();
|
||
|
|
|
||
|
|
#ifdef QOS_PREFETCH_USE_TIMER
|
||
|
|
expires = jiffies + QOS_PREFETCH_TIME;
|
||
|
|
mod_timer(&qos_prefetch_timer, expires);
|
||
|
|
#endif /* QOS_PREFETCH_USE_TIMER */
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
return NOTIFY_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
static struct notifier_block qos_prefetch_cpu_freq_notifier = {
|
||
|
|
.notifier_call = qos_prefetch_cpu_freq_callback,
|
||
|
|
};
|
||
|
|
#endif
|
||
|
|
|
||
|
|
void qos_prefetch_tick(int cpu)
|
||
|
|
{
|
||
|
|
unsigned int val;
|
||
|
|
|
||
|
|
if (!is_qos_prefetch_enabled())
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (prefetch_val == PREFETCH_DEFAULT_VAL)
|
||
|
|
return;
|
||
|
|
|
||
|
|
val = QOS_PREFETCH_VALUE(prefetch_val, cpu);
|
||
|
|
|
||
|
|
if (val)
|
||
|
|
qos_prefetch_update_single(cpu, val);
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(qos_prefetch_tick);
|
||
|
|
|
||
|
|
void qos_prefetch_init(void)
|
||
|
|
{
|
||
|
|
int i;
|
||
|
|
|
||
|
|
spin_lock_init(&qos_prefetch_cpumask_lock);
|
||
|
|
qos_prefetch_sched_pm_init();
|
||
|
|
|
||
|
|
#ifdef CONFIG_CPU_FREQ
|
||
|
|
cpufreq_register_notifier(&qos_prefetch_cpu_freq_notifier,
|
||
|
|
CPUFREQ_TRANSITION_NOTIFIER);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#ifdef QOS_PREFETCH_USE_TIMER
|
||
|
|
init_timer_deferrable(&qos_prefetch_timer);
|
||
|
|
qos_prefetch_timer.function = qos_prefetch_timer_fn;
|
||
|
|
qos_prefetch_timer.data = 0;
|
||
|
|
#endif /* QOS_PREFETCH_USE_TIMER */
|
||
|
|
|
||
|
|
#if defined(CONFIG_MTK_TINYSYS_SSPM_SUPPORT)
|
||
|
|
|
||
|
|
for (i = 0; i < QOS_CPUS_NR; i++) {
|
||
|
|
qos_prefetch_setting(QOS_IPI_QOS_PREFETCH_CPU_OPP,
|
||
|
|
i << 16 | (qos_cpu_opp_bound[i] & 0xffff));
|
||
|
|
}
|
||
|
|
qos_prefetch_setting(QOS_IPI_QOS_PREFETCH_POWER_RATIO_UP,
|
||
|
|
qos_cpu_power_ratio_up);
|
||
|
|
qos_prefetch_setting(QOS_IPI_QOS_PREFETCH_POWER_RATIO_DN,
|
||
|
|
qos_cpu_power_ratio_dn);
|
||
|
|
#endif /* CONFIG_MTK_TINYSYS_SSPM_SUPPORT */
|
||
|
|
|
||
|
|
qos_prefetch_enable(0);
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(qos_prefetch_init);
|
||
|
|
|