/* * 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