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