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