1/* 2 * CPU idle driver for Tegra CPUs 3 * 4 * Copyright (c) 2010-2012, NVIDIA Corporation. 5 * Copyright (c) 2011 Google, Inc. 6 * Author: Colin Cross <ccross@android.com> 7 * Gary King <gking@nvidia.com> 8 * 9 * Rework for 3.3 by Peter De Schrijver <pdeschrijver@nvidia.com> 10 * 11 * This program is free software; you can redistribute it and/or modify 12 * it under the terms of the GNU General Public License as published by 13 * the Free Software Foundation; either version 2 of the License, or 14 * (at your option) any later version. 15 * 16 * This program is distributed in the hope that it will be useful, but WITHOUT 17 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 18 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 19 * more details. 20 */ 21 22#include <linux/clk/tegra.h> 23#include <linux/tick.h> 24#include <linux/cpuidle.h> 25#include <linux/cpu_pm.h> 26#include <linux/kernel.h> 27#include <linux/module.h> 28 29#include <asm/cpuidle.h> 30#include <asm/smp_plat.h> 31#include <asm/suspend.h> 32 33#include "pm.h" 34#include "sleep.h" 35 36#ifdef CONFIG_PM_SLEEP 37static int tegra30_idle_lp2(struct cpuidle_device *dev, 38 struct cpuidle_driver *drv, 39 int index); 40#endif 41 42static struct cpuidle_driver tegra_idle_driver = { 43 .name = "tegra_idle", 44 .owner = THIS_MODULE, 45#ifdef CONFIG_PM_SLEEP 46 .state_count = 2, 47#else 48 .state_count = 1, 49#endif 50 .states = { 51 [0] = ARM_CPUIDLE_WFI_STATE_PWR(600), 52#ifdef CONFIG_PM_SLEEP 53 [1] = { 54 .enter = tegra30_idle_lp2, 55 .exit_latency = 2000, 56 .target_residency = 2200, 57 .power_usage = 0, 58 .name = "powered-down", 59 .desc = "CPU power gated", 60 }, 61#endif 62 }, 63}; 64 65#ifdef CONFIG_PM_SLEEP 66static bool tegra30_cpu_cluster_power_down(struct cpuidle_device *dev, 67 struct cpuidle_driver *drv, 68 int index) 69{ 70 /* All CPUs entering LP2 is not working. 71 * Don't let CPU0 enter LP2 when any secondary CPU is online. 72 */ 73 if (num_online_cpus() > 1 || !tegra_cpu_rail_off_ready()) { 74 cpu_do_idle(); 75 return false; 76 } 77 78 tick_broadcast_enter(); 79 80 tegra_idle_lp2_last(); 81 82 tick_broadcast_exit(); 83 84 return true; 85} 86 87#ifdef CONFIG_SMP 88static bool tegra30_cpu_core_power_down(struct cpuidle_device *dev, 89 struct cpuidle_driver *drv, 90 int index) 91{ 92 tick_broadcast_enter(); 93 94 smp_wmb(); 95 96 cpu_suspend(0, tegra30_sleep_cpu_secondary_finish); 97 98 tick_broadcast_exit(); 99 100 return true; 101} 102#else 103static inline bool tegra30_cpu_core_power_down(struct cpuidle_device *dev, 104 struct cpuidle_driver *drv, 105 int index) 106{ 107 return true; 108} 109#endif 110 111static int tegra30_idle_lp2(struct cpuidle_device *dev, 112 struct cpuidle_driver *drv, 113 int index) 114{ 115 bool entered_lp2 = false; 116 bool last_cpu; 117 118 local_fiq_disable(); 119 120 last_cpu = tegra_set_cpu_in_lp2(); 121 cpu_pm_enter(); 122 123 if (dev->cpu == 0) { 124 if (last_cpu) 125 entered_lp2 = tegra30_cpu_cluster_power_down(dev, drv, 126 index); 127 else 128 cpu_do_idle(); 129 } else { 130 entered_lp2 = tegra30_cpu_core_power_down(dev, drv, index); 131 } 132 133 cpu_pm_exit(); 134 tegra_clear_cpu_in_lp2(); 135 136 local_fiq_enable(); 137 138 smp_rmb(); 139 140 return (entered_lp2) ? index : 0; 141} 142#endif 143 144int __init tegra30_cpuidle_init(void) 145{ 146 return cpuidle_register(&tegra_idle_driver, NULL); 147} 148