/*
* Copyright (c) 2016-2019, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#ifdef CONFIG_PM
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "../../kernel/irq/internals.h"
#include "../../kernel/time/tick-internal.h"
enum tegra210_idle_index {
C7_IDX = 0,
CC6_IDX,
CC7_IDX,
IDLE_STATE_MAX,
};
struct tegra210_pm_data {
bool cc4_no_retention;
bool cc3_no_hvc;
bool cc6_allow;
/* Idle state index array */
int idle_state_idx[IDLE_STATE_MAX];
};
static struct tegra210_pm_data t210_pm_data;
#ifdef CONFIG_PREEMPT_RT_FULL
static DEFINE_RAW_SPINLOCK(tegra210_cpu_pm_lock);
#else
static DEFINE_RWLOCK(tegra210_cpu_pm_lock);
#endif
static RAW_NOTIFIER_HEAD(tegra210_cpu_pm_chain);
static void tegra210_bpmp_enable_suspend(int mode, int flags)
{
s32 mb[] = { cpu_to_le32(mode), cpu_to_le32(flags) };
tegra_bpmp_send_receive_atomic(MRQ_ENABLE_SUSPEND,
&mb, sizeof(mb), NULL, 0);
}
static int tegra210_bpmp_suspend(void)
{
tegra210_bpmp_enable_suspend(TEGRA_PM_SC7, 0);
return 0;
}
static struct syscore_ops bpmp_sc7_suspend_ops = {
.suspend = tegra210_bpmp_suspend,
.save = tegra210_bpmp_suspend,
};
static int tegra_of_idle_state_idx_from_name(char *state_name)
{
struct device_node *state_node, *cpu_node;
int i, id_found = 0;
cpu_node = of_cpu_device_node_get(0);
for (i = 0; ; i++) {
state_node = of_parse_phandle(cpu_node, "cpu-idle-states", i);
if (!state_node)
break;
if (!of_device_is_available(state_node))
continue;
if (!strcmp(state_node->name, state_name))
id_found = 1;
of_node_put(state_node);
if (id_found)
break;
}
of_node_put(cpu_node);
return id_found ? (i + 1) : 0;
}
static int proc_idle_state_enter(int cpu, int idle_state)
{
u32 ctrl;
ctrl = 0xffffffff;
if (t210_pm_data.cc4_no_retention)
ctrl &= ~BIT(1);
/* We don't allow retention without HVC */
if (t210_pm_data.cc3_no_hvc)
ctrl &= ~(BIT(1) | BIT(0));
flowctrl_write_cc4_ctrl(cpu, ctrl);
if (idle_state == t210_pm_data.idle_state_idx[CC6_IDX] &&
!t210_pm_data.cc6_allow)
return -ENODEV;
return 0;
}
static void proc_idle_state_exit(int cpu, int idle_state)
{
flowctrl_write_cc4_ctrl(cpu, 0);
}
static int tegra210_cpu_pm_notifier(struct notifier_block *self,
unsigned long cmd, void *v)
{
int idle_state = (long)v;
int cpu = smp_processor_id();
switch (cmd) {
case CPU_PM_ENTER:
if (proc_idle_state_enter(cpu, idle_state))
return NOTIFY_BAD;
break;
case CPU_PM_EXIT:
proc_idle_state_exit(cpu, idle_state);
break;
default:
return NOTIFY_DONE;
}
return NOTIFY_OK;
}
static struct notifier_block tegra210_cpu_pm_nb = {
.notifier_call = tegra210_cpu_pm_notifier,
};
static int tegra210_cpu_pm_notify(enum cpu_pm_event event, void *v,
int nr_to_call, int *nr_calls)
{
int ret;
ret = __raw_notifier_call_chain(&tegra210_cpu_pm_chain, event, v,
nr_to_call, nr_calls);
return notifier_to_errno(ret);
}
int tegra210_cpu_pm_enter(void *idle_idx)
{
int ret = 0;
#ifdef CONFIG_PREEMPT_RT_FULL
unsigned long flags;
raw_spin_lock_irqsave(&tegra210_cpu_pm_lock, flags);
#else
read_lock(&tegra210_cpu_pm_lock);
#endif
ret = tegra210_cpu_pm_notify(CPU_PM_ENTER, idle_idx, -1, NULL);
#ifdef CONFIG_PREEMPT_RT_FULL
raw_spin_unlock_irqrestore(&tegra210_cpu_pm_lock, flags);
#else
read_unlock(&tegra210_cpu_pm_lock);
#endif
return ret;
}
EXPORT_SYMBOL_GPL(tegra210_cpu_pm_enter);
int tegra210_cpu_pm_exit(void *idle_idx)
{
int ret;
#ifdef CONFIG_PREEMPT_RT_FULL
unsigned long flags;
raw_spin_lock_irqsave(&tegra210_cpu_pm_lock, flags);
#else
read_lock(&tegra210_cpu_pm_lock);
#endif
ret = tegra210_cpu_pm_notify(CPU_PM_EXIT, idle_idx, -1, NULL);
#ifdef CONFIG_PREEMPT_RT_FULL
raw_spin_unlock_irqrestore(&tegra210_cpu_pm_lock, flags);
#else
read_unlock(&tegra210_cpu_pm_lock);
#endif
return ret;
}
EXPORT_SYMBOL_GPL(tegra210_cpu_pm_exit);
static int tegra210_cpu_pm_register_notifier(struct notifier_block *nb)
{
unsigned long flags;
int ret;
#ifdef CONFIG_PREEMPT_RT_FULL
raw_spin_lock_irqsave(&tegra210_cpu_pm_lock, flags);
#else
write_lock_irqsave(&tegra210_cpu_pm_lock, flags);
#endif
ret = raw_notifier_chain_register(&tegra210_cpu_pm_chain, nb);
#ifdef CONFIG_PREEMPT_RT_FULL
raw_spin_unlock_irqrestore(&tegra210_cpu_pm_lock, flags);
#else
write_unlock_irqrestore(&tegra210_cpu_pm_lock, flags);
#endif
return ret;
}
static void do_cc4_init(void)
{
flowctrl_update(FLOW_CTLR_CC4_HVC_CONTROL,
2 << 3 | FLOW_CTRL_CC4_HVC_ENABLE);
flowctrl_update(FLOW_CTRL_CC4_RETENTION_CONTROL, 2 << 3);
flowctrl_update(FLOW_CTRL_CC4_HVC_RETRY, 2);
}
static struct syscore_ops cc4_syscore_ops = {
.restore = do_cc4_init,
.resume = do_cc4_init
};
static int tegra210_cpuidle_cc4_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct regulator *reg;
uint32_t uv;
int r;
do_cc4_init();
/* T210 BPMP supports CC4 retention only with max77621 or ovr2. */
t210_pm_data.cc4_no_retention = of_property_read_bool(dev->of_node,
"cc4-no-retention");
t210_pm_data.cc3_no_hvc = of_property_read_bool(dev->of_node,
"cc3-no-hvc");
/* If cc4-microvolt is not found, assume not max77621 */
if (of_property_read_u32(dev->of_node, "cc4-microvolt", &uv))
goto out;
reg = regulator_get(dev, "vdd-cpu");
if (IS_ERR(reg)) {
dev_err(dev, "vdd-cpu regulator get failed\n");
return PTR_ERR(reg);
}
r = regulator_set_sleep_voltage(reg, uv - 100000, uv + 100000);
if (r)
dev_err(dev, "failed to set retention voltage: %d\n", r);
dev_info(dev, "retention voltage is %u uv\n", uv);
out:
register_syscore_ops(&cc4_syscore_ops);
return 0;
}
#ifdef CONFIG_DEBUG_FS
static struct dentry *cpuidle_debugfs_root;
static unsigned int state_enable;
static u64 idle_state;
static int fast_enable_show(struct seq_file *s, void *data)
{
struct cpuidle_device *dev = __this_cpu_read(cpuidle_devices);
struct cpuidle_driver *drv = cpuidle_get_cpu_driver(dev);
int i;
if (!drv) {
seq_puts(s, "Failed to get cpuidle driver\n");
return 0;
}
seq_puts(s, "Usage: write state index to disable or enable\n");
seq_puts(s, " name idx Enabled\n");
seq_puts(s, "--------------------------\n");
for (i = drv->safe_state_index + 1; i < drv->state_count; i++) {
seq_printf(s, " %s %d %d\n", drv->states[i].name,
i, !drv->states[i].disabled);
}
return 0;
}
static int fast_enable_open(struct inode *inode, struct file *file)
{
return single_open(file, fast_enable_show, inode->i_private);
}
static ssize_t fast_enable_write(struct file *fp, const char __user *ubuf,
size_t count, loff_t *pos)
{
int cpu;
struct cpuidle_device *dev;
struct cpuidle_driver *drv;
if (kstrtouint_from_user(ubuf, count, 0, &state_enable) < 0)
return -EINVAL;
for_each_present_cpu(cpu) {
dev = per_cpu(cpuidle_devices, cpu);
drv = cpuidle_get_cpu_driver(dev);
if (!drv) {
pr_err("%s: Failed to get cpuidle driver on cpu:%d\n", __func__, cpu);
return -ENOTSUPP;
}
if (state_enable <= drv->safe_state_index ||
state_enable >= drv->state_count)
return -EINVAL;
if (drv->states[state_enable].disabled)
drv->states[state_enable].disabled = false;
else
drv->states[state_enable].disabled = true;
}
return count;
}
static const struct file_operations fast_cluster_enable_fops = {
.open = fast_enable_open,
.read = seq_read,
.llseek = seq_lseek,
.write = fast_enable_write,
.release = single_release,
};
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);
desc->istate |= IRQS_SUSPENDED;
raw_spin_unlock_irqrestore(&desc->lock, flags);
synchronize_hardirq(irq);
}
}
static void resume_all_device_irqs(void)
{
struct irq_desc *desc;
int irq;
for_each_irq_desc(irq, desc) {
unsigned long flags;
/* No need to re-enable the 'wakeup' interrupt */
if (is_timer_irq(desc))
continue;
raw_spin_lock_irqsave(&desc->lock, flags);
desc->istate &= ~IRQS_SUSPENDED;
__enable_irq(desc);
raw_spin_unlock_irqrestore(&desc->lock, flags);
}
}
static int idle_write(void *data, u64 val)
{
struct cpuidle_driver *drv;
unsigned long timer_interval_us = (ulong)val;
ktime_t time, interval, sleep;
preempt_disable();
drv = cpuidle_get_driver();
if ((idle_state != t210_pm_data.idle_state_idx[C7_IDX]) &&
(idle_state != t210_pm_data.idle_state_idx[CC6_IDX])) {
pr_err("%s: Request forced idle state C7/CC4: 1, CC6:2\n", __func__);
preempt_enable_no_resched();
return -EINVAL;
}
pr_info("CPU idle in state: %llu, duration: %lu us\n", idle_state, timer_interval_us);
suspend_all_device_irqs();
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);
arm_cpuidle_suspend(idle_state);
sleep = ktime_sub(ktime_get(), time);
time = ktime_sub(sleep, interval);
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
pr_debug("idle: %lld, exit latency: %lld\n", sleep.tv64, time.tv64);
#else
pr_debug("idle: %lld, exit latency: %lld\n", sleep, time);
#endif
local_irq_enable();
local_fiq_enable();
start_critical_timings();
tick_nohz_idle_exit();
resume_all_device_irqs();
preempt_enable_no_resched();
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(duration_us_fops, NULL, idle_write, "%llu\n");
static int debugfs_init(void)
{
struct dentry *dfs_file;
cpuidle_debugfs_root = debugfs_create_dir("cpuidle_t210", NULL);
if (!cpuidle_debugfs_root) {
pr_err("failed to create cpuidle_t210 node\n");
return -ENOMEM;
}
dfs_file = debugfs_create_file("fast_cluster_states_enable", 0644,
cpuidle_debugfs_root, NULL, &fast_cluster_enable_fops);
if (!dfs_file) {
pr_err("failed to create ast_cluster_states_enable\n");
goto err_out;
}
dfs_file = debugfs_create_u64("forced_idle_state", 0644,
cpuidle_debugfs_root, &idle_state);
if (!dfs_file) {
pr_err("failed to create forced_idle_state\n");
goto err_out;
}
dfs_file = debugfs_create_file("forced_idle_duration_us", 0200,
cpuidle_debugfs_root, NULL, &duration_us_fops);
if (!dfs_file) {
pr_err("failed to create forced_idle_duration_us\n");
goto err_out;
}
return 0;
err_out:
debugfs_remove_recursive(cpuidle_debugfs_root);
return -ENOMEM;
}
#else
static inline int debugfs_init(void) { return 0; }
#endif
static const struct of_device_id tegra210_cpuidle_of[] = {
{ .compatible = "nvidia,tegra210-cpuidle" },
{}
};
static struct platform_driver tegra210_cpuidle_driver = {
.probe = tegra210_cpuidle_cc4_probe,
.driver = {
.owner = THIS_MODULE,
.name = "cpuidle-tegra210",
.of_match_table = tegra210_cpuidle_of,
}
};
static int __init tegra210_cpuidle_init(void)
{
struct cpuidle_device *dev = __this_cpu_read(cpuidle_devices);
struct cpuidle_driver *drv = cpuidle_get_cpu_driver(dev);
int i;
if (tegra_get_chip_id() != TEGRA210)
goto out;
if (!dev || !drv) {
pr_err("%s: no cpuidle devices or driver\n", __func__);
return -ENODEV;
}
/*
* To avoid the race condition between DFLL clock ready
* and CC4 engagement. Put this in late_inticall.
*/
platform_driver_register(&tegra210_cpuidle_driver);
debugfs_init();
/*
* Disable CC6 during boot. They can be enabled later using the
* fast_cluster_enable knobs from userspace.
*/
for (i = drv->safe_state_index + 1; i < drv->state_count; i++)
if (i == t210_pm_data.idle_state_idx[CC6_IDX])
drv->states[i].disabled = true;
t210_pm_data.cc6_allow = true;
out:
return 0;
}
late_initcall(tegra210_cpuidle_init);
static int __init tegra210_pm_init(void)
{
if (tegra_get_chip_id() != TEGRA210)
goto out;
/* Disable CC4 until DFLL clk is ready */
t210_pm_data.cc4_no_retention = true;
/*
* CC6 also needs to be disabled until DFLL clk is ready, but there is
* no explicit hook that notifies when the DFLL init is complete. The
* cluster states can be disabled in the arm cpuidle driver which may
* not be ready here. Use this flag until the tegra_cpuidle driver
* disables the arm cpuidle driver's states during late_init.
*/
t210_pm_data.cc6_allow = false;
t210_pm_data.idle_state_idx[C7_IDX] =
tegra_of_idle_state_idx_from_name("c7");
t210_pm_data.idle_state_idx[CC6_IDX] =
tegra_of_idle_state_idx_from_name("cc6");
t210_pm_data.idle_state_idx[CC7_IDX] =
tegra_of_idle_state_idx_from_name("cc7");
tegra210_cpu_pm_register_notifier(&tegra210_cpu_pm_nb);
register_syscore_ops(&bpmp_sc7_suspend_ops);
out:
return 0;
}
device_initcall(tegra210_pm_init);
#endif