tegrakernel/kernel/nvidia/drivers/cpuidle/cpuidle-tegra19x.c

706 lines
17 KiB
C
Raw Permalink Normal View History

2022-02-16 09:13:02 -06:00
/*
* drivers/cpuidle/cpuidle-tegra19x.c
*
* Copyright (C) 2017-2019, NVIDIA Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include <linux/kernel.h>
#include <linux/cpuidle.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/debugfs.h>
#include <soc/tegra/chip-id.h>
#include <linux/tegra-mce.h>
#include <linux/t194_nvg.h>
#include <linux/suspend.h>
#include <linux/ktime.h>
#include <linux/time.h>
#include <linux/tick.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include "../../kernel/irq/internals.h"
#include <linux/pm_qos.h>
#include <linux/cpu_pm.h>
#include <linux/psci.h>
#include <linux/version.h>
#include <linux/cpuhotplug.h>
#include <linux/atomic.h>
#include <linux/platform/tegra/t19x-cpuidle.h>
#include <linux/of_gpio.h>
#include <asm/cpuidle.h>
#include <asm/suspend.h>
#include <asm/cputype.h> /* cpuid */
#include <asm/cpu.h>
#include <asm/arch_timer.h>
#include "../../drivers/cpuidle/dt_idle_states.h"
#include "../../kernel/time/tick-internal.h"
#define PSCI_STATE_ID_WKTIM_MASK (~0xf000000f)
#define PSCI_STATE_ID_WKTIM_SHIFT 4
#define CORE_WAKE_MASK 0x180C
#define T19x_CPUIDLE_C7_STATE 2
#define T19x_CPUIDLE_C6_STATE 1
#define MCE_STAT_ID_SHIFT 16UL
/*
* BG_TIME is margin added to target_residency so that actual HW
* has better chance entering deep idle state instead of getting
* back to shallower one.
*/
#define BG_TIME 2000 /* in unit of us */
/* per CPU sleep_time holds target_residency for next expected idle state */
static DEFINE_PER_CPU(u32, sleep_time);
static u32 read_cluster_info(struct device_node *of_states);
static u32 deepest_cc_state;
static u64 forced_idle_state;
static u64 forced_cluster_idle_state;
static u64 test_c6_exit_latency;
static atomic_t entered_c6_cpu_count = ATOMIC_INIT(0);
static u32 testmode;
static struct cpuidle_driver t19x_cpu_idle_driver;
static int crossover_init(void);
static void program_cc_state(void *data);
static u32 tsc_per_sec, nsec_per_tsc_tick;
static u32 tsc_per_usec;
/* saved hotplug state */
static enum cpuhp_state hp_state;
#define T19x_NVG_CROSSOVER_C6 TEGRA_NVG_CHANNEL_CROSSOVER_C6_LOWER_BOUND
#define T19x_NVG_CROSSOVER_CC6 TEGRA_NVG_CHANNEL_CROSSOVER_CC6_LOWER_BOUND
static bool check_mce_version(void)
{
u32 mce_version_major, mce_version_minor;
int err;
err = tegra_mce_read_versions(&mce_version_major, &mce_version_minor);
if (!err && (mce_version_major >= TEGRA_NVG_VERSION_MAJOR))
return true;
else
return false;
}
int read_cpu_counter(void)
{
return atomic_read(&entered_c6_cpu_count);
}
EXPORT_SYMBOL(read_cpu_counter);
void clear_cpu_counter(void)
{
atomic_set(&entered_c6_cpu_count, 0);
}
EXPORT_SYMBOL(clear_cpu_counter);
static void t19x_cpu_enter_c6(int index)
{
int cpu = smp_processor_id();
struct cpuidle_driver *drv = &t19x_cpu_idle_driver;
per_cpu(sleep_time, cpu) = drv->states[index].target_residency;
arm_cpuidle_suspend(T19x_CPUIDLE_C6_STATE);
}
/*enter C6 function used in measuring C6 latency*/
static void test_t19x_cpu_enter_c6(u32 wake_time)
{
int cpu;
u64 val;
u32 mce_index;
cpu = smp_processor_id();
mce_index = (NVG_STAT_QUERY_C6_ENTRIES << MCE_STAT_ID_SHIFT)
+ (u32)cpu;
tegra_mce_read_cstate_stats(mce_index, &val);
trace_printk("cpu = %d C6_COUNT_BEFORE = %llu\n", cpu, val);
atomic_inc(&entered_c6_cpu_count);
t19x_cpu_enter_c6(T19x_CPUIDLE_C6_STATE);
trace_printk("Exiting C6\n");
tegra_mce_read_cstate_stats(mce_index, &val);
trace_printk("cpu = %d C6_COUNT_AFTER = %llu\n", cpu, val);
}
static void t19x_cpu_enter_c7(int index)
{
int cpu = smp_processor_id();
struct cpuidle_driver *drv = &t19x_cpu_idle_driver;
cpu_pm_enter(); /* power down notifier */
per_cpu(sleep_time, cpu) = drv->states[index].target_residency;
arm_cpuidle_suspend(T19x_CPUIDLE_C7_STATE);
cpu_pm_exit();
}
static int t19x_cpu_enter_state(
struct cpuidle_device *dev,
struct cpuidle_driver *drv,
int index)
{
if (tegra_platform_is_vdk()) {
asm volatile("wfi\n");
return index;
}
if (testmode) {
tegra_mce_update_cstate_info(forced_cluster_idle_state,
0, 0, 0, 0, 0);
if (forced_idle_state >= t19x_cpu_idle_driver.state_count) {
pr_err("%s: Requested invalid forced idle state\n",
__func__);
index = t19x_cpu_idle_driver.state_count;
} else
index = forced_idle_state;
}
if (index == T19x_CPUIDLE_C7_STATE)
t19x_cpu_enter_c7(index);
else if (index == T19x_CPUIDLE_C6_STATE)
t19x_cpu_enter_c6(index);
else
asm volatile("wfi\n");
return index;
}
static u32 t19x_make_power_state(u32 state)
{
int cpu = smp_processor_id();
u32 wake_time = (per_cpu(sleep_time, cpu) + BG_TIME) * tsc_per_usec;
if (testmode || test_c6_exit_latency)
wake_time = 0xFFFFEEEE;
/* The 8-LSB bits of wake time is lost and only 24 MSB bits
of wake time can fit into the additional state id bits */
state = state | ((wake_time >> PSCI_STATE_ID_WKTIM_SHIFT)
& PSCI_STATE_ID_WKTIM_MASK);
return state;
}
static struct cpuidle_driver t19x_cpu_idle_driver = {
.name = "tegra19x_cpuidle_driver",
.owner = THIS_MODULE,
/*
* State at index 0 is standby wfi and considered standard
* on all ARM platforms. If in some platforms simple wfi
* can't be used as "state 0", DT bindings must be implemented
* to work around this issue and allow installing a special
* handler for idle state index 0.
*/
.states[0] = {
.enter = t19x_cpu_enter_state,
.exit_latency = 1,
.target_residency = 1,
.power_usage = UINT_MAX,
.flags = 0,
.name = "C1",
.desc = "c1-cpu-clockgated",
}
};
static bool is_timer_irq(struct irq_desc *desc)
{
return desc && desc->action && (desc->action->flags & IRQF_TIMER);
}
static void suspend_all_device_irqs(void)
{
struct irq_desc *desc;
int irq;
for_each_irq_desc(irq, desc) {
unsigned long flags;
/* Don't disable the 'wakeup' interrupt */
if (is_timer_irq(desc))
continue;
raw_spin_lock_irqsave(&desc->lock, flags);
__disable_irq(desc);
raw_spin_unlock_irqrestore(&desc->lock, flags);
}
for_each_irq_desc(irq, desc) {
if (is_timer_irq(desc))
continue;
synchronize_irq(irq);
}
}
static void resume_all_device_irqs(void)
{
struct irq_desc *desc;
int irq;
for_each_irq_desc(irq, desc) {
unsigned long flags;
if (is_timer_irq(desc))
continue;
raw_spin_lock_irqsave(&desc->lock, flags);
__enable_irq(desc);
raw_spin_unlock_irqrestore(&desc->lock, flags);
}
}
static u64 dbg_gpio;
static struct dentry *cpuidle_debugfs_node;
static int forced_idle_write(void *data, u64 val)
{
unsigned long timer_interval_us = (ulong)val;
ktime_t time, interval, sleep;
u32 pmstate;
u32 wake_time;
val = (val * 1000) / nsec_per_tsc_tick;
if (val > 0xffffffff)
val = 0xffffffff;
wake_time = val;
if (forced_idle_state >= t19x_cpu_idle_driver.state_count) {
pr_err("%s: Requested invalid forced idle state\n", __func__);
return -EINVAL;
}
suspend_all_device_irqs();
preempt_disable();
tick_nohz_idle_enter();
stop_critical_timings();
local_fiq_disable();
local_irq_disable();
interval = ktime_set(0, (NSEC_PER_USEC * timer_interval_us));
time = ktime_get();
sleep = ktime_add(time, interval);
tick_program_event(sleep, true);
pmstate = forced_idle_state;
if (dbg_gpio)
gpio_set_value(dbg_gpio, 1);
tegra_mce_update_cstate_info(forced_cluster_idle_state, 0, 0, 0, 0, 0);
if (pmstate == T19x_CPUIDLE_C7_STATE)
t19x_cpu_enter_c7(pmstate);
else if (pmstate == T19x_CPUIDLE_C6_STATE) {
if (test_c6_exit_latency)
test_t19x_cpu_enter_c6(wake_time);
else
t19x_cpu_enter_c6(pmstate);
}
else
asm volatile("wfi\n");
sleep = ktime_sub(ktime_get(), time);
time = ktime_sub(sleep, interval);
if (dbg_gpio)
gpio_set_value(dbg_gpio, 0);
pr_info("idle: %lld, exit latency: %lld\n",
ktime_to_ns(sleep), ktime_to_ns(time));
local_irq_enable();
local_fiq_enable();
start_critical_timings();
tick_nohz_idle_exit();
preempt_enable_no_resched();
resume_all_device_irqs();
return 0;
}
void force_idle_c6(u64 delay)
{
forced_idle_write(NULL, delay);
}
EXPORT_SYMBOL(force_idle_c6);
struct xover_smp_call_data {
int index;
int value;
};
static void program_single_crossover(void *data)
{
struct xover_smp_call_data *xover_data =
(struct xover_smp_call_data *)data;
tegra_mce_update_crossover_time(xover_data->index,
xover_data->value * tsc_per_usec);
}
static int setup_crossover(int index, int value)
{
struct xover_smp_call_data xover_data;
xover_data.index = index;
xover_data.value = value;
on_each_cpu_mask(cpu_online_mask, program_single_crossover,
&xover_data, 1);
return 0;
}
static int c6_xover_write(void *data, u64 val)
{
return setup_crossover(T19x_NVG_CROSSOVER_C6, (u32) val);
}
static int cc6_xover_write(void *data, u64 val)
{
return setup_crossover(T19x_NVG_CROSSOVER_CC6, (u32) val);
}
static int set_testmode(void *data, u64 val)
{
testmode = (u32)val;
if (testmode) {
setup_crossover(T19x_NVG_CROSSOVER_C6, 0);
setup_crossover(T19x_NVG_CROSSOVER_CC6, 0);
} else {
/* Restore the cluster state */
on_each_cpu_mask(cpu_online_mask,
program_cc_state, &deepest_cc_state, 1);
/* Restore the crossover values */
crossover_init();
}
return 0;
}
static int cc_state_set(void *data, u64 val)
{
deepest_cc_state = (u32)val;
on_each_cpu_mask(cpu_online_mask, program_cc_state,
&deepest_cc_state, 1);
return 0;
}
static int cc_state_get(void *data, u64 *val)
{
*val = (u64) deepest_cc_state;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(duration_us_fops, NULL, forced_idle_write, "%llu\n");
DEFINE_SIMPLE_ATTRIBUTE(xover_c6_fops, NULL, c6_xover_write, "%llu\n");
DEFINE_SIMPLE_ATTRIBUTE(xover_cc6_fops, NULL,
cc6_xover_write, "%llu\n");
DEFINE_SIMPLE_ATTRIBUTE(cc_state_fops, cc_state_get,
cc_state_set, "%llu\n");
DEFINE_SIMPLE_ATTRIBUTE(testmode_fops, NULL, set_testmode, "%llu\n");
static int cpuidle_debugfs_init(void)
{
struct dentry *dfs_file;
cpuidle_debugfs_node = debugfs_create_dir("tegra_cpuidle", NULL);
if (!cpuidle_debugfs_node)
goto err_out;
dfs_file = debugfs_create_u64("forced_idle_state", 0644,
cpuidle_debugfs_node, &forced_idle_state);
if (!dfs_file)
goto err_out;
dfs_file = debugfs_create_u64("test_c6_exit_latency", 0644,
cpuidle_debugfs_node, &test_c6_exit_latency);
if (!dfs_file)
goto err_out;
dfs_file = debugfs_create_u64("forced_cluster_idle_state", 0644,
cpuidle_debugfs_node, &forced_cluster_idle_state);
if (!dfs_file)
goto err_out;
dfs_file = debugfs_create_file("forced_idle_duration_us", 0200,
cpuidle_debugfs_node, NULL, &duration_us_fops);
if (!dfs_file)
goto err_out;
dfs_file = debugfs_create_file("testmode", 0200,
cpuidle_debugfs_node, NULL, &testmode_fops);
if (!dfs_file)
goto err_out;
dfs_file = debugfs_create_file("crossover_c1_c6", 0200,
cpuidle_debugfs_node, NULL, &xover_c6_fops);
if (!dfs_file)
goto err_out;
dfs_file = debugfs_create_file("crossover_cc1_cc6", 0200,
cpuidle_debugfs_node, NULL, &xover_cc6_fops);
if (!dfs_file)
goto err_out;
dfs_file = debugfs_create_file("deepest_cc_state", 0644,
cpuidle_debugfs_node, NULL, &cc_state_fops);
if (!dfs_file)
goto err_out;
dfs_file = debugfs_create_u64("dbg_gpio", 0644, cpuidle_debugfs_node,
&dbg_gpio);
if (!dfs_file)
goto err_out;
return 0;
err_out:
pr_err("%s: Couldn't create debugfs node for cpuidle\n", __func__);
debugfs_remove_recursive(cpuidle_debugfs_node);
return -ENOMEM;
}
static const struct of_device_id t19x_idle_of_match[] = {
{ .compatible = "nvidia,tegra194-cpuidle-core",
.data = t19x_cpu_enter_state },
{ },
};
static u32 read_cluster_info(struct device_node *of_states)
{
u32 power = UINT_MAX;
u32 value, pmstate, deepest_pmstate = 0;
struct device_node *child;
int err;
for_each_child_of_node(of_states, child) {
if (of_property_match_string(child, "status", "okay"))
continue;
err = of_property_read_u32(child, "power", &value);
if (err) {
pr_warn(" %s missing power property\n",
child->full_name);
continue;
}
err = of_property_read_u32(child, "pmstate", &pmstate);
if (err) {
pr_warn(" %s missing pmstate property\n",
child->full_name);
continue;
}
/* Enable the deepest power state */
if (value > power)
continue;
power = value;
deepest_pmstate = pmstate;
}
return deepest_pmstate;
}
struct xover_table {
char *name;
int index;
};
static void send_crossover(void *data)
{
struct device_node *child;
struct device_node *of_states = (struct device_node *)data;
u32 value;
int i;
struct xover_table table1[] = {
{"crossover_c1_c6", T19x_NVG_CROSSOVER_C6},
{"crossover_cc1_cc6", T19x_NVG_CROSSOVER_CC6},
};
for_each_child_of_node(of_states, child)
for (i = 0; i < sizeof(table1)/sizeof(table1[0]); i++) {
if (of_property_read_u32(child,
table1[i].name, &value) == 0)
tegra_mce_update_crossover_time
(table1[i].index, value * tsc_per_usec);
}
}
static int crossover_init(void)
{
struct device_node *cpu_xover;
cpu_xover = of_find_node_by_name(NULL,
"cpu_crossover_thresholds");
pr_debug("cpuidle: Init Power Crossover thresholds.\n");
if (!cpu_xover) {
pr_err("WARNING: cpuidle: %s: DT entry ", __func__);
pr_err("missing for Crossover thresholds\n");
} else
on_each_cpu_mask(cpu_online_mask, send_crossover,
cpu_xover, 1);
return 0;
}
static void program_cc_state(void *data)
{
u32 *cc_state = (u32 *)data;
tegra_mce_update_cstate_info(*cc_state, 0, 0, 0, 0, 0);
}
static int tegra_suspend_notify_callback(struct notifier_block *nb,
unsigned long action, void *pcpu)
{
switch (action) {
case PM_POST_SUSPEND:
/*
* Re-program deepest allowed cluster and cluster group power state
* after system resumes from SC7
*/
on_each_cpu_mask(cpu_online_mask, program_cc_state,
&deepest_cc_state, 1);
break;
}
return NOTIFY_OK;
}
static struct notifier_block suspend_notifier = {
.notifier_call = tegra_suspend_notify_callback,
};
static int tegra_cpu_online(unsigned int cpu)
{
/*
* Re-program deepest allowed cluster and cluster group power state
* after a core in that cluster is onlined.
*/
smp_call_function_single(cpu, program_cc_state,
&deepest_cc_state, 1);
return 0;
}
static int __init tegra19x_cpuidle_probe(struct platform_device *pdev)
{
int cpu_number;
struct device_node *cpu_cc_states;
int err;
struct cpumask *cpumask;
if (!check_mce_version()) {
pr_err("cpuidle: Incompatible MCE version. Not registering\n");
return -ENODEV;
}
tsc_per_sec = arch_timer_get_cntfrq();
nsec_per_tsc_tick = 1000000000/tsc_per_sec;
tsc_per_usec = tsc_per_sec / 1000000;
cpumask = kmalloc(sizeof(struct cpumask), GFP_KERNEL);
cpumask_clear(cpumask);
for_each_online_cpu(cpu_number) {
cpumask_set_cpu(cpu_number, cpumask);
err = arm_cpuidle_init(cpu_number);
if (err) {
pr_err("cpuidle: failed to init ops for cpu %d \n",
cpu_number);
goto probe_exit;
}
}
crossover_init();
cpu_cc_states =
of_find_node_by_name(NULL, "cpu_cluster_power_states");
pr_info("cpuidle: Initializing cpuidle driver\n");
extended_ops.make_power_state = t19x_make_power_state;
/* read cluster state info from DT */
deepest_cc_state = read_cluster_info(cpu_cc_states);
on_each_cpu_mask(cpu_online_mask, program_cc_state,
&deepest_cc_state, 1);
t19x_cpu_idle_driver.cpumask = cpumask;
err = dt_init_idle_driver(&t19x_cpu_idle_driver,
t19x_idle_of_match, 1);
if (err <= 0) {
pr_err("cpuidle: failed to init idle driver states \n");
err = -ENODEV;
goto probe_exit;
}
err = cpuidle_register(&t19x_cpu_idle_driver, NULL);
if (err) {
pr_err("cpuidle: failed to register cpuidle driver \n");
goto probe_exit;
}
err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
"tegra_cpu:online",
tegra_cpu_online,
NULL);
if (err < 0) {
pr_err("unable to register cpuhp state\n");
goto cpuhp_error;
}
hp_state = err;
cpuidle_debugfs_init();
register_pm_notifier(&suspend_notifier);
return 0;
cpuhp_error:
cpuidle_unregister(&t19x_cpu_idle_driver);
probe_exit:
kfree(cpumask);
pr_err("cpuidle: failed to register cpuidle driver\n");
return err;
}
static int tegra19x_cpuidle_remove(struct platform_device *pdev)
{
cpuidle_unregister(&t19x_cpu_idle_driver);
kfree(t19x_cpu_idle_driver.cpumask);
cpuhp_remove_state(hp_state);
unregister_pm_notifier(&suspend_notifier);
return 0;
}
static const struct of_device_id tegra19x_cpuidle_of[] = {
{ .compatible = "nvidia,tegra19x-cpuidle" },
{}
};
static struct platform_driver tegra19x_cpuidle_driver __refdata = {
.probe = tegra19x_cpuidle_probe,
.remove = tegra19x_cpuidle_remove,
.driver = {
.owner = THIS_MODULE,
.name = "cpuidle-tegra19x",
.of_match_table = of_match_ptr(tegra19x_cpuidle_of)
}
};
module_platform_driver(tegra19x_cpuidle_driver);