1/*
2 * linux/arch/arm/mach-omap2/cpuidle34xx.c
3 *
4 * OMAP3 CPU IDLE Routines
5 *
6 * Copyright (C) 2008 Texas Instruments, Inc.
7 * Rajendra Nayak <rnayak@ti.com>
8 *
9 * Copyright (C) 2007 Texas Instruments, Inc.
10 * Karthik Dasu <karthik-dp@ti.com>
11 *
12 * Copyright (C) 2006 Nokia Corporation
13 * Tony Lindgren <tony@atomide.com>
14 *
15 * Copyright (C) 2005 Texas Instruments, Inc.
16 * Richard Woodruff <r-woodruff2@ti.com>
17 *
18 * Based on pm.c for omap2
19 *
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License version 2 as
22 * published by the Free Software Foundation.
23 */
24
25#include <linux/sched.h>
26#include <linux/cpuidle.h>
27#include <linux/export.h>
28#include <linux/cpu_pm.h>
29#include <asm/cpuidle.h>
30
31#include "powerdomain.h"
32#include "clockdomain.h"
33
34#include "pm.h"
35#include "control.h"
36#include "common.h"
37
38/* Mach specific information to be recorded in the C-state driver_data */
39struct omap3_idle_statedata {
40	u8 mpu_state;
41	u8 core_state;
42	u8 per_min_state;
43	u8 flags;
44};
45
46static struct powerdomain *mpu_pd, *core_pd, *per_pd, *cam_pd;
47
48/*
49 * Possible flag bits for struct omap3_idle_statedata.flags:
50 *
51 * OMAP_CPUIDLE_CX_NO_CLKDM_IDLE: don't allow the MPU clockdomain to go
52 *    inactive.  This in turn prevents the MPU DPLL from entering autoidle
53 *    mode, so wakeup latency is greatly reduced, at the cost of additional
54 *    energy consumption.  This also prevents the CORE clockdomain from
55 *    entering idle.
56 */
57#define OMAP_CPUIDLE_CX_NO_CLKDM_IDLE		BIT(0)
58
59/*
60 * Prevent PER OFF if CORE is not in RETention or OFF as this would
61 * disable PER wakeups completely.
62 */
63static struct omap3_idle_statedata omap3_idle_data[] = {
64	{
65		.mpu_state = PWRDM_POWER_ON,
66		.core_state = PWRDM_POWER_ON,
67		/* In C1 do not allow PER state lower than CORE state */
68		.per_min_state = PWRDM_POWER_ON,
69		.flags = OMAP_CPUIDLE_CX_NO_CLKDM_IDLE,
70	},
71	{
72		.mpu_state = PWRDM_POWER_ON,
73		.core_state = PWRDM_POWER_ON,
74		.per_min_state = PWRDM_POWER_RET,
75	},
76	{
77		.mpu_state = PWRDM_POWER_RET,
78		.core_state = PWRDM_POWER_ON,
79		.per_min_state = PWRDM_POWER_RET,
80	},
81	{
82		.mpu_state = PWRDM_POWER_OFF,
83		.core_state = PWRDM_POWER_ON,
84		.per_min_state = PWRDM_POWER_RET,
85	},
86	{
87		.mpu_state = PWRDM_POWER_RET,
88		.core_state = PWRDM_POWER_RET,
89		.per_min_state = PWRDM_POWER_OFF,
90	},
91	{
92		.mpu_state = PWRDM_POWER_OFF,
93		.core_state = PWRDM_POWER_RET,
94		.per_min_state = PWRDM_POWER_OFF,
95	},
96	{
97		.mpu_state = PWRDM_POWER_OFF,
98		.core_state = PWRDM_POWER_OFF,
99		.per_min_state = PWRDM_POWER_OFF,
100	},
101};
102
103/**
104 * omap3_enter_idle - Programs OMAP3 to enter the specified state
105 * @dev: cpuidle device
106 * @drv: cpuidle driver
107 * @index: the index of state to be entered
108 */
109static int omap3_enter_idle(struct cpuidle_device *dev,
110			    struct cpuidle_driver *drv,
111			    int index)
112{
113	struct omap3_idle_statedata *cx = &omap3_idle_data[index];
114
115	if (omap_irq_pending() || need_resched())
116		goto return_sleep_time;
117
118	/* Deny idle for C1 */
119	if (cx->flags & OMAP_CPUIDLE_CX_NO_CLKDM_IDLE) {
120		clkdm_deny_idle(mpu_pd->pwrdm_clkdms[0]);
121	} else {
122		pwrdm_set_next_pwrst(mpu_pd, cx->mpu_state);
123		pwrdm_set_next_pwrst(core_pd, cx->core_state);
124	}
125
126	/*
127	 * Call idle CPU PM enter notifier chain so that
128	 * VFP context is saved.
129	 */
130	if (cx->mpu_state == PWRDM_POWER_OFF)
131		cpu_pm_enter();
132
133	/* Execute ARM wfi */
134	omap_sram_idle();
135
136	/*
137	 * Call idle CPU PM enter notifier chain to restore
138	 * VFP context.
139	 */
140	if (cx->mpu_state == PWRDM_POWER_OFF &&
141	    pwrdm_read_prev_pwrst(mpu_pd) == PWRDM_POWER_OFF)
142		cpu_pm_exit();
143
144	/* Re-allow idle for C1 */
145	if (cx->flags & OMAP_CPUIDLE_CX_NO_CLKDM_IDLE)
146		clkdm_allow_idle(mpu_pd->pwrdm_clkdms[0]);
147
148return_sleep_time:
149
150	return index;
151}
152
153/**
154 * next_valid_state - Find next valid C-state
155 * @dev: cpuidle device
156 * @drv: cpuidle driver
157 * @index: Index of currently selected c-state
158 *
159 * If the state corresponding to index is valid, index is returned back
160 * to the caller. Else, this function searches for a lower c-state which is
161 * still valid (as defined in omap3_power_states[]) and returns its index.
162 *
163 * A state is valid if the 'valid' field is enabled and
164 * if it satisfies the enable_off_mode condition.
165 */
166static int next_valid_state(struct cpuidle_device *dev,
167			    struct cpuidle_driver *drv, int index)
168{
169	struct omap3_idle_statedata *cx = &omap3_idle_data[index];
170	u32 mpu_deepest_state = PWRDM_POWER_RET;
171	u32 core_deepest_state = PWRDM_POWER_RET;
172	int idx;
173	int next_index = 0; /* C1 is the default value */
174
175	if (enable_off_mode) {
176		mpu_deepest_state = PWRDM_POWER_OFF;
177		/*
178		 * Erratum i583: valable for ES rev < Es1.2 on 3630.
179		 * CORE OFF mode is not supported in a stable form, restrict
180		 * instead the CORE state to RET.
181		 */
182		if (!IS_PM34XX_ERRATUM(PM_SDRC_WAKEUP_ERRATUM_i583))
183			core_deepest_state = PWRDM_POWER_OFF;
184	}
185
186	/* Check if current state is valid */
187	if ((cx->mpu_state >= mpu_deepest_state) &&
188	    (cx->core_state >= core_deepest_state))
189		return index;
190
191	/*
192	 * Drop to next valid state.
193	 * Start search from the next (lower) state.
194	 */
195	for (idx = index - 1; idx >= 0; idx--) {
196		cx = &omap3_idle_data[idx];
197		if ((cx->mpu_state >= mpu_deepest_state) &&
198		    (cx->core_state >= core_deepest_state)) {
199			next_index = idx;
200			break;
201		}
202	}
203
204	return next_index;
205}
206
207/**
208 * omap3_enter_idle_bm - Checks for any bus activity
209 * @dev: cpuidle device
210 * @drv: cpuidle driver
211 * @index: array index of target state to be programmed
212 *
213 * This function checks for any pending activity and then programs
214 * the device to the specified or a safer state.
215 */
216static int omap3_enter_idle_bm(struct cpuidle_device *dev,
217			       struct cpuidle_driver *drv,
218			       int index)
219{
220	int new_state_idx, ret;
221	u8 per_next_state, per_saved_state;
222	struct omap3_idle_statedata *cx;
223
224	/*
225	 * Use only C1 if CAM is active.
226	 * CAM does not have wakeup capability in OMAP3.
227	 */
228	if (pwrdm_read_pwrst(cam_pd) == PWRDM_POWER_ON)
229		new_state_idx = drv->safe_state_index;
230	else
231		new_state_idx = next_valid_state(dev, drv, index);
232
233	/*
234	 * FIXME: we currently manage device-specific idle states
235	 *        for PER and CORE in combination with CPU-specific
236	 *        idle states.  This is wrong, and device-specific
237	 *        idle management needs to be separated out into
238	 *        its own code.
239	 */
240
241	/* Program PER state */
242	cx = &omap3_idle_data[new_state_idx];
243
244	per_next_state = pwrdm_read_next_pwrst(per_pd);
245	per_saved_state = per_next_state;
246	if (per_next_state < cx->per_min_state) {
247		per_next_state = cx->per_min_state;
248		pwrdm_set_next_pwrst(per_pd, per_next_state);
249	}
250
251	ret = omap3_enter_idle(dev, drv, new_state_idx);
252
253	/* Restore original PER state if it was modified */
254	if (per_next_state != per_saved_state)
255		pwrdm_set_next_pwrst(per_pd, per_saved_state);
256
257	return ret;
258}
259
260static struct cpuidle_driver omap3_idle_driver = {
261	.name             = "omap3_idle",
262	.owner            = THIS_MODULE,
263	.states = {
264		{
265			.enter		  = omap3_enter_idle_bm,
266			.exit_latency	  = 2 + 2,
267			.target_residency = 5,
268			.name		  = "C1",
269			.desc		  = "MPU ON + CORE ON",
270		},
271		{
272			.enter		  = omap3_enter_idle_bm,
273			.exit_latency	  = 10 + 10,
274			.target_residency = 30,
275			.name		  = "C2",
276			.desc		  = "MPU ON + CORE ON",
277		},
278		{
279			.enter		  = omap3_enter_idle_bm,
280			.exit_latency	  = 50 + 50,
281			.target_residency = 300,
282			.name		  = "C3",
283			.desc		  = "MPU RET + CORE ON",
284		},
285		{
286			.enter		  = omap3_enter_idle_bm,
287			.exit_latency	  = 1500 + 1800,
288			.target_residency = 4000,
289			.name		  = "C4",
290			.desc		  = "MPU OFF + CORE ON",
291		},
292		{
293			.enter		  = omap3_enter_idle_bm,
294			.exit_latency	  = 2500 + 7500,
295			.target_residency = 12000,
296			.name		  = "C5",
297			.desc		  = "MPU RET + CORE RET",
298		},
299		{
300			.enter		  = omap3_enter_idle_bm,
301			.exit_latency	  = 3000 + 8500,
302			.target_residency = 15000,
303			.name		  = "C6",
304			.desc		  = "MPU OFF + CORE RET",
305		},
306		{
307			.enter		  = omap3_enter_idle_bm,
308			.exit_latency	  = 10000 + 30000,
309			.target_residency = 30000,
310			.name		  = "C7",
311			.desc		  = "MPU OFF + CORE OFF",
312		},
313	},
314	.state_count = ARRAY_SIZE(omap3_idle_data),
315	.safe_state_index = 0,
316};
317
318/* Public functions */
319
320/**
321 * omap3_idle_init - Init routine for OMAP3 idle
322 *
323 * Registers the OMAP3 specific cpuidle driver to the cpuidle
324 * framework with the valid set of states.
325 */
326int __init omap3_idle_init(void)
327{
328	mpu_pd = pwrdm_lookup("mpu_pwrdm");
329	core_pd = pwrdm_lookup("core_pwrdm");
330	per_pd = pwrdm_lookup("per_pwrdm");
331	cam_pd = pwrdm_lookup("cam_pwrdm");
332
333	if (!mpu_pd || !core_pd || !per_pd || !cam_pd)
334		return -ENODEV;
335
336	return cpuidle_register(&omap3_idle_driver, NULL);
337}
338