Commit | Line | Data |
---|---|---|
4f86d3a8 LB |
1 | /* |
2 | * driver.c - driver support | |
3 | * | |
4 | * (C) 2006-2007 Venkatesh Pallipadi <venkatesh.pallipadi@intel.com> | |
5 | * Shaohua Li <shaohua.li@intel.com> | |
6 | * Adam Belay <abelay@novell.com> | |
7 | * | |
8 | * This code is licenced under the GPL. | |
9 | */ | |
10 | ||
11 | #include <linux/mutex.h> | |
12 | #include <linux/module.h> | |
13 | #include <linux/cpuidle.h> | |
a06df062 DL |
14 | #include <linux/cpumask.h> |
15 | #include <linux/clockchips.h> | |
4f86d3a8 LB |
16 | |
17 | #include "cpuidle.h" | |
18 | ||
4f86d3a8 LB |
19 | DEFINE_SPINLOCK(cpuidle_driver_lock); |
20 | ||
82467a5a | 21 | #ifdef CONFIG_CPU_IDLE_MULTIPLE_DRIVERS |
bf4d1b5d | 22 | |
82467a5a DL |
23 | static DEFINE_PER_CPU(struct cpuidle_driver *, cpuidle_drivers); |
24 | ||
25 | static struct cpuidle_driver *__cpuidle_get_cpu_driver(int cpu) | |
13dd52f1 | 26 | { |
82467a5a | 27 | return per_cpu(cpuidle_drivers, cpu); |
a06df062 DL |
28 | } |
29 | ||
82467a5a | 30 | static inline void __cpuidle_unset_driver(struct cpuidle_driver *drv) |
a06df062 | 31 | { |
82467a5a | 32 | int cpu; |
a06df062 | 33 | |
82467a5a | 34 | for_each_cpu(cpu, drv->cpumask) { |
a06df062 | 35 | |
82467a5a | 36 | if (drv != __cpuidle_get_cpu_driver(cpu)) |
a06df062 DL |
37 | continue; |
38 | ||
82467a5a | 39 | per_cpu(cpuidle_drivers, cpu) = NULL; |
a06df062 | 40 | } |
13dd52f1 DL |
41 | } |
42 | ||
82467a5a | 43 | static inline int __cpuidle_set_driver(struct cpuidle_driver *drv) |
4f86d3a8 | 44 | { |
82467a5a | 45 | int cpu; |
62027aea | 46 | |
82467a5a | 47 | for_each_cpu(cpu, drv->cpumask) { |
ed953472 | 48 | |
82467a5a DL |
49 | if (__cpuidle_get_cpu_driver(cpu)) { |
50 | __cpuidle_unset_driver(drv); | |
51 | return -EBUSY; | |
52 | } | |
ed953472 | 53 | |
82467a5a DL |
54 | per_cpu(cpuidle_drivers, cpu) = drv; |
55 | } | |
42f67f2a | 56 | |
13dd52f1 DL |
57 | return 0; |
58 | } | |
59 | ||
82467a5a | 60 | #else |
13dd52f1 | 61 | |
82467a5a | 62 | static struct cpuidle_driver *cpuidle_curr_driver; |
a06df062 | 63 | |
82467a5a DL |
64 | static inline struct cpuidle_driver *__cpuidle_get_cpu_driver(int cpu) |
65 | { | |
66 | return cpuidle_curr_driver; | |
bf4d1b5d DL |
67 | } |
68 | ||
82467a5a DL |
69 | static inline int __cpuidle_set_driver(struct cpuidle_driver *drv) |
70 | { | |
71 | if (cpuidle_curr_driver) | |
72 | return -EBUSY; | |
bf4d1b5d | 73 | |
82467a5a | 74 | cpuidle_curr_driver = drv; |
bf4d1b5d | 75 | |
82467a5a | 76 | return 0; |
bf4d1b5d DL |
77 | } |
78 | ||
82467a5a | 79 | static inline void __cpuidle_unset_driver(struct cpuidle_driver *drv) |
bf4d1b5d | 80 | { |
82467a5a DL |
81 | if (drv == cpuidle_curr_driver) |
82 | cpuidle_curr_driver = NULL; | |
bf4d1b5d DL |
83 | } |
84 | ||
82467a5a DL |
85 | #endif |
86 | ||
87 | static void cpuidle_setup_broadcast_timer(void *arg) | |
bf4d1b5d | 88 | { |
82467a5a DL |
89 | int cpu = smp_processor_id(); |
90 | clockevents_notify((long)(arg), &cpu); | |
bf4d1b5d DL |
91 | } |
92 | ||
82467a5a | 93 | static int __cpuidle_driver_init(struct cpuidle_driver *drv) |
bf4d1b5d | 94 | { |
82467a5a | 95 | int i; |
bf4d1b5d | 96 | |
82467a5a | 97 | drv->refcnt = 0; |
bf4d1b5d | 98 | |
82467a5a DL |
99 | if (!drv->cpumask) |
100 | drv->cpumask = (struct cpumask *)cpu_possible_mask; | |
bf4d1b5d | 101 | |
82467a5a | 102 | for (i = drv->state_count - 1; i >= 0 ; i--) { |
bf4d1b5d | 103 | |
82467a5a DL |
104 | if (!(drv->states[i].flags & CPUIDLE_FLAG_TIMER_STOP)) |
105 | continue; | |
106 | ||
107 | drv->bctimer = 1; | |
108 | break; | |
109 | } | |
110 | ||
111 | return 0; | |
bf4d1b5d DL |
112 | } |
113 | ||
82467a5a | 114 | static int __cpuidle_register_driver(struct cpuidle_driver *drv) |
bf4d1b5d DL |
115 | { |
116 | int ret; | |
117 | ||
82467a5a DL |
118 | if (!drv || !drv->state_count) |
119 | return -EINVAL; | |
bf4d1b5d | 120 | |
82467a5a DL |
121 | if (cpuidle_disabled()) |
122 | return -ENODEV; | |
bf4d1b5d | 123 | |
82467a5a DL |
124 | ret = __cpuidle_driver_init(drv); |
125 | if (ret) | |
126 | return ret; | |
ed953472 | 127 | |
82467a5a DL |
128 | ret = __cpuidle_set_driver(drv); |
129 | if (ret) | |
130 | return ret; | |
13dd52f1 | 131 | |
82467a5a DL |
132 | if (drv->bctimer) |
133 | on_each_cpu_mask(drv->cpumask, cpuidle_setup_broadcast_timer, | |
134 | (void *)CLOCK_EVT_NOTIFY_BROADCAST_ON, 1); | |
4f86d3a8 | 135 | |
82467a5a | 136 | return 0; |
4f86d3a8 | 137 | } |
4f86d3a8 | 138 | |
752138df | 139 | /** |
bf4d1b5d DL |
140 | * cpuidle_unregister_driver - unregisters a driver |
141 | * @drv: the driver | |
752138df | 142 | */ |
82467a5a | 143 | static void __cpuidle_unregister_driver(struct cpuidle_driver *drv) |
bf4d1b5d | 144 | { |
82467a5a DL |
145 | if (WARN_ON(drv->refcnt > 0)) |
146 | return; | |
bf4d1b5d | 147 | |
82467a5a DL |
148 | if (drv->bctimer) { |
149 | drv->bctimer = 0; | |
150 | on_each_cpu_mask(drv->cpumask, cpuidle_setup_broadcast_timer, | |
151 | (void *)CLOCK_EVT_NOTIFY_BROADCAST_OFF, 1); | |
152 | } | |
bf4d1b5d | 153 | |
82467a5a | 154 | __cpuidle_unset_driver(drv); |
752138df | 155 | } |
bf4d1b5d DL |
156 | |
157 | /** | |
158 | * cpuidle_register_driver - registers a driver | |
159 | * @drv: the driver | |
160 | */ | |
161 | int cpuidle_register_driver(struct cpuidle_driver *drv) | |
162 | { | |
82467a5a | 163 | int ret; |
bf4d1b5d | 164 | |
bf4d1b5d | 165 | spin_lock(&cpuidle_driver_lock); |
82467a5a | 166 | ret = __cpuidle_register_driver(drv); |
bf4d1b5d | 167 | spin_unlock(&cpuidle_driver_lock); |
bf4d1b5d DL |
168 | |
169 | return ret; | |
170 | } | |
171 | EXPORT_SYMBOL_GPL(cpuidle_register_driver); | |
752138df | 172 | |
4f86d3a8 LB |
173 | /** |
174 | * cpuidle_unregister_driver - unregisters a driver | |
175 | * @drv: the driver | |
176 | */ | |
177 | void cpuidle_unregister_driver(struct cpuidle_driver *drv) | |
178 | { | |
4f86d3a8 | 179 | spin_lock(&cpuidle_driver_lock); |
82467a5a | 180 | __cpuidle_unregister_driver(drv); |
4f86d3a8 LB |
181 | spin_unlock(&cpuidle_driver_lock); |
182 | } | |
4f86d3a8 | 183 | EXPORT_SYMBOL_GPL(cpuidle_unregister_driver); |
bf4d1b5d DL |
184 | |
185 | /** | |
186 | * cpuidle_get_driver - return the current driver | |
187 | */ | |
188 | struct cpuidle_driver *cpuidle_get_driver(void) | |
189 | { | |
190 | struct cpuidle_driver *drv; | |
191 | int cpu; | |
192 | ||
193 | cpu = get_cpu(); | |
194 | drv = __cpuidle_get_cpu_driver(cpu); | |
195 | put_cpu(); | |
196 | ||
197 | return drv; | |
198 | } | |
199 | EXPORT_SYMBOL_GPL(cpuidle_get_driver); | |
200 | ||
201 | /** | |
202 | * cpuidle_get_cpu_driver - return the driver tied with a cpu | |
203 | */ | |
204 | struct cpuidle_driver *cpuidle_get_cpu_driver(struct cpuidle_device *dev) | |
205 | { | |
bf4d1b5d DL |
206 | if (!dev) |
207 | return NULL; | |
208 | ||
ac34d7c8 | 209 | return __cpuidle_get_cpu_driver(dev->cpu); |
bf4d1b5d DL |
210 | } |
211 | EXPORT_SYMBOL_GPL(cpuidle_get_cpu_driver); | |
6e797a07 RW |
212 | |
213 | struct cpuidle_driver *cpuidle_driver_ref(void) | |
214 | { | |
215 | struct cpuidle_driver *drv; | |
216 | ||
217 | spin_lock(&cpuidle_driver_lock); | |
218 | ||
13dd52f1 | 219 | drv = cpuidle_get_driver(); |
42f67f2a | 220 | drv->refcnt++; |
6e797a07 RW |
221 | |
222 | spin_unlock(&cpuidle_driver_lock); | |
223 | return drv; | |
224 | } | |
225 | ||
226 | void cpuidle_driver_unref(void) | |
227 | { | |
13dd52f1 | 228 | struct cpuidle_driver *drv = cpuidle_get_driver(); |
42f67f2a | 229 | |
6e797a07 RW |
230 | spin_lock(&cpuidle_driver_lock); |
231 | ||
42f67f2a DL |
232 | if (drv && !WARN_ON(drv->refcnt <= 0)) |
233 | drv->refcnt--; | |
6e797a07 RW |
234 | |
235 | spin_unlock(&cpuidle_driver_lock); | |
236 | } |