xref: /linux/drivers/cpuidle/cpuidle-ux500.c (revision d3f2950f2adeea3da0317e952914b59adaa4cdb3)
1*d3f2950fSDaniel Lezcano /*
2*d3f2950fSDaniel Lezcano  * Copyright (c) 2012 Linaro : Daniel Lezcano <daniel.lezcano@linaro.org> (IBM)
3*d3f2950fSDaniel Lezcano  *
4*d3f2950fSDaniel Lezcano  * Based on the work of Rickard Andersson <rickard.andersson@stericsson.com>
5*d3f2950fSDaniel Lezcano  * and Jonas Aaberg <jonas.aberg@stericsson.com>.
6*d3f2950fSDaniel Lezcano  *
7*d3f2950fSDaniel Lezcano  * This program is free software; you can redistribute it and/or modify
8*d3f2950fSDaniel Lezcano  * it under the terms of the GNU General Public License version 2 as
9*d3f2950fSDaniel Lezcano  * published by the Free Software Foundation.
10*d3f2950fSDaniel Lezcano  */
11*d3f2950fSDaniel Lezcano 
12*d3f2950fSDaniel Lezcano #include <linux/module.h>
13*d3f2950fSDaniel Lezcano #include <linux/cpuidle.h>
14*d3f2950fSDaniel Lezcano #include <linux/spinlock.h>
15*d3f2950fSDaniel Lezcano #include <linux/atomic.h>
16*d3f2950fSDaniel Lezcano #include <linux/smp.h>
17*d3f2950fSDaniel Lezcano #include <linux/mfd/dbx500-prcmu.h>
18*d3f2950fSDaniel Lezcano #include <linux/platform_data/arm-ux500-pm.h>
19*d3f2950fSDaniel Lezcano #include <linux/platform_device.h>
20*d3f2950fSDaniel Lezcano 
21*d3f2950fSDaniel Lezcano #include <asm/cpuidle.h>
22*d3f2950fSDaniel Lezcano #include <asm/proc-fns.h>
23*d3f2950fSDaniel Lezcano 
24*d3f2950fSDaniel Lezcano static atomic_t master = ATOMIC_INIT(0);
25*d3f2950fSDaniel Lezcano static DEFINE_SPINLOCK(master_lock);
26*d3f2950fSDaniel Lezcano 
27*d3f2950fSDaniel Lezcano static inline int ux500_enter_idle(struct cpuidle_device *dev,
28*d3f2950fSDaniel Lezcano 				   struct cpuidle_driver *drv, int index)
29*d3f2950fSDaniel Lezcano {
30*d3f2950fSDaniel Lezcano 	int this_cpu = smp_processor_id();
31*d3f2950fSDaniel Lezcano 	bool recouple = false;
32*d3f2950fSDaniel Lezcano 
33*d3f2950fSDaniel Lezcano 	if (atomic_inc_return(&master) == num_online_cpus()) {
34*d3f2950fSDaniel Lezcano 
35*d3f2950fSDaniel Lezcano 		/* With this lock, we prevent the other cpu to exit and enter
36*d3f2950fSDaniel Lezcano 		 * this function again and become the master */
37*d3f2950fSDaniel Lezcano 		if (!spin_trylock(&master_lock))
38*d3f2950fSDaniel Lezcano 			goto wfi;
39*d3f2950fSDaniel Lezcano 
40*d3f2950fSDaniel Lezcano 		/* decouple the gic from the A9 cores */
41*d3f2950fSDaniel Lezcano 		if (prcmu_gic_decouple()) {
42*d3f2950fSDaniel Lezcano 			spin_unlock(&master_lock);
43*d3f2950fSDaniel Lezcano 			goto out;
44*d3f2950fSDaniel Lezcano 		}
45*d3f2950fSDaniel Lezcano 
46*d3f2950fSDaniel Lezcano 		/* If an error occur, we will have to recouple the gic
47*d3f2950fSDaniel Lezcano 		 * manually */
48*d3f2950fSDaniel Lezcano 		recouple = true;
49*d3f2950fSDaniel Lezcano 
50*d3f2950fSDaniel Lezcano 		/* At this state, as the gic is decoupled, if the other
51*d3f2950fSDaniel Lezcano 		 * cpu is in WFI, we have the guarantee it won't be wake
52*d3f2950fSDaniel Lezcano 		 * up, so we can safely go to retention */
53*d3f2950fSDaniel Lezcano 		if (!prcmu_is_cpu_in_wfi(this_cpu ? 0 : 1))
54*d3f2950fSDaniel Lezcano 			goto out;
55*d3f2950fSDaniel Lezcano 
56*d3f2950fSDaniel Lezcano 		/* The prcmu will be in charge of watching the interrupts
57*d3f2950fSDaniel Lezcano 		 * and wake up the cpus */
58*d3f2950fSDaniel Lezcano 		if (prcmu_copy_gic_settings())
59*d3f2950fSDaniel Lezcano 			goto out;
60*d3f2950fSDaniel Lezcano 
61*d3f2950fSDaniel Lezcano 		/* Check in the meantime an interrupt did
62*d3f2950fSDaniel Lezcano 		 * not occur on the gic ... */
63*d3f2950fSDaniel Lezcano 		if (prcmu_gic_pending_irq())
64*d3f2950fSDaniel Lezcano 			goto out;
65*d3f2950fSDaniel Lezcano 
66*d3f2950fSDaniel Lezcano 		/* ... and the prcmu */
67*d3f2950fSDaniel Lezcano 		if (prcmu_pending_irq())
68*d3f2950fSDaniel Lezcano 			goto out;
69*d3f2950fSDaniel Lezcano 
70*d3f2950fSDaniel Lezcano 		/* Go to the retention state, the prcmu will wait for the
71*d3f2950fSDaniel Lezcano 		 * cpu to go WFI and this is what happens after exiting this
72*d3f2950fSDaniel Lezcano 		 * 'master' critical section */
73*d3f2950fSDaniel Lezcano 		if (prcmu_set_power_state(PRCMU_AP_IDLE, true, true))
74*d3f2950fSDaniel Lezcano 			goto out;
75*d3f2950fSDaniel Lezcano 
76*d3f2950fSDaniel Lezcano 		/* When we switch to retention, the prcmu is in charge
77*d3f2950fSDaniel Lezcano 		 * of recoupling the gic automatically */
78*d3f2950fSDaniel Lezcano 		recouple = false;
79*d3f2950fSDaniel Lezcano 
80*d3f2950fSDaniel Lezcano 		spin_unlock(&master_lock);
81*d3f2950fSDaniel Lezcano 	}
82*d3f2950fSDaniel Lezcano wfi:
83*d3f2950fSDaniel Lezcano 	cpu_do_idle();
84*d3f2950fSDaniel Lezcano out:
85*d3f2950fSDaniel Lezcano 	atomic_dec(&master);
86*d3f2950fSDaniel Lezcano 
87*d3f2950fSDaniel Lezcano 	if (recouple) {
88*d3f2950fSDaniel Lezcano 		prcmu_gic_recouple();
89*d3f2950fSDaniel Lezcano 		spin_unlock(&master_lock);
90*d3f2950fSDaniel Lezcano 	}
91*d3f2950fSDaniel Lezcano 
92*d3f2950fSDaniel Lezcano 	return index;
93*d3f2950fSDaniel Lezcano }
94*d3f2950fSDaniel Lezcano 
95*d3f2950fSDaniel Lezcano static struct cpuidle_driver ux500_idle_driver = {
96*d3f2950fSDaniel Lezcano 	.name = "ux500_idle",
97*d3f2950fSDaniel Lezcano 	.owner = THIS_MODULE,
98*d3f2950fSDaniel Lezcano 	.states = {
99*d3f2950fSDaniel Lezcano 		ARM_CPUIDLE_WFI_STATE,
100*d3f2950fSDaniel Lezcano 		{
101*d3f2950fSDaniel Lezcano 			.enter		  = ux500_enter_idle,
102*d3f2950fSDaniel Lezcano 			.exit_latency	  = 70,
103*d3f2950fSDaniel Lezcano 			.target_residency = 260,
104*d3f2950fSDaniel Lezcano 			.flags		  = CPUIDLE_FLAG_TIME_VALID |
105*d3f2950fSDaniel Lezcano 			                    CPUIDLE_FLAG_TIMER_STOP,
106*d3f2950fSDaniel Lezcano 			.name		  = "ApIdle",
107*d3f2950fSDaniel Lezcano 			.desc		  = "ARM Retention",
108*d3f2950fSDaniel Lezcano 		},
109*d3f2950fSDaniel Lezcano 	},
110*d3f2950fSDaniel Lezcano 	.safe_state_index = 0,
111*d3f2950fSDaniel Lezcano 	.state_count = 2,
112*d3f2950fSDaniel Lezcano };
113*d3f2950fSDaniel Lezcano 
114*d3f2950fSDaniel Lezcano static int __init dbx500_cpuidle_probe(struct platform_device *pdev)
115*d3f2950fSDaniel Lezcano {
116*d3f2950fSDaniel Lezcano 	/* Configure wake up reasons */
117*d3f2950fSDaniel Lezcano 	prcmu_enable_wakeups(PRCMU_WAKEUP(ARM) | PRCMU_WAKEUP(RTC) |
118*d3f2950fSDaniel Lezcano 			     PRCMU_WAKEUP(ABB));
119*d3f2950fSDaniel Lezcano 
120*d3f2950fSDaniel Lezcano 	return cpuidle_register(&ux500_idle_driver, NULL);
121*d3f2950fSDaniel Lezcano }
122*d3f2950fSDaniel Lezcano 
123*d3f2950fSDaniel Lezcano static struct platform_driver dbx500_cpuidle_plat_driver = {
124*d3f2950fSDaniel Lezcano 	.driver = {
125*d3f2950fSDaniel Lezcano 		.name = "cpuidle-dbx500",
126*d3f2950fSDaniel Lezcano 		.owner = THIS_MODULE,
127*d3f2950fSDaniel Lezcano 	},
128*d3f2950fSDaniel Lezcano 	.probe = dbx500_cpuidle_probe,
129*d3f2950fSDaniel Lezcano };
130*d3f2950fSDaniel Lezcano 
131*d3f2950fSDaniel Lezcano module_platform_driver(dbx500_cpuidle_plat_driver);
132