Commit | Line | Data |
---|---|---|
d0508944 PB |
1 | /* |
2 | * Copyright (C) 2014 Imagination Technologies | |
3 | * Author: Paul Burton <paul.burton@imgtec.com> | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms of the GNU General Public License as published by the | |
7 | * Free Software Foundation; either version 2 of the License, or (at your | |
8 | * option) any later version. | |
9 | */ | |
10 | ||
11 | #include <linux/cpu_pm.h> | |
12 | #include <linux/cpuidle.h> | |
13 | #include <linux/init.h> | |
14 | ||
15 | #include <asm/idle.h> | |
16 | #include <asm/pm-cps.h> | |
17 | ||
18 | /* Enumeration of the various idle states this driver may enter */ | |
19 | enum cps_idle_state { | |
20 | STATE_WAIT = 0, /* MIPS wait instruction, coherent */ | |
21 | STATE_NC_WAIT, /* MIPS wait instruction, non-coherent */ | |
22 | STATE_CLOCK_GATED, /* Core clock gated */ | |
23 | STATE_POWER_GATED, /* Core power gated */ | |
24 | STATE_COUNT | |
25 | }; | |
26 | ||
27 | static int cps_nc_enter(struct cpuidle_device *dev, | |
28 | struct cpuidle_driver *drv, int index) | |
29 | { | |
30 | enum cps_pm_state pm_state; | |
31 | int err; | |
32 | ||
33 | /* | |
34 | * At least one core must remain powered up & clocked in order for the | |
35 | * system to have any hope of functioning. | |
36 | * | |
37 | * TODO: don't treat core 0 specially, just prevent the final core | |
38 | * TODO: remap interrupt affinity temporarily | |
39 | */ | |
40 | if (!cpu_data[dev->cpu].core && (index > STATE_NC_WAIT)) | |
41 | index = STATE_NC_WAIT; | |
42 | ||
43 | /* Select the appropriate cps_pm_state */ | |
44 | switch (index) { | |
45 | case STATE_NC_WAIT: | |
46 | pm_state = CPS_PM_NC_WAIT; | |
47 | break; | |
48 | case STATE_CLOCK_GATED: | |
49 | pm_state = CPS_PM_CLOCK_GATED; | |
50 | break; | |
51 | case STATE_POWER_GATED: | |
52 | pm_state = CPS_PM_POWER_GATED; | |
53 | break; | |
54 | default: | |
55 | BUG(); | |
56 | return -EINVAL; | |
57 | } | |
58 | ||
59 | /* Notify listeners the CPU is about to power down */ | |
60 | if ((pm_state == CPS_PM_POWER_GATED) && cpu_pm_enter()) | |
61 | return -EINTR; | |
62 | ||
63 | /* Enter that state */ | |
64 | err = cps_pm_enter_state(pm_state); | |
65 | ||
66 | /* Notify listeners the CPU is back up */ | |
67 | if (pm_state == CPS_PM_POWER_GATED) | |
68 | cpu_pm_exit(); | |
69 | ||
70 | return err ?: index; | |
71 | } | |
72 | ||
73 | static struct cpuidle_driver cps_driver = { | |
74 | .name = "cpc_cpuidle", | |
75 | .owner = THIS_MODULE, | |
76 | .states = { | |
77 | [STATE_WAIT] = MIPS_CPUIDLE_WAIT_STATE, | |
78 | [STATE_NC_WAIT] = { | |
79 | .enter = cps_nc_enter, | |
80 | .exit_latency = 200, | |
81 | .target_residency = 450, | |
82 | .flags = CPUIDLE_FLAG_TIME_VALID, | |
83 | .name = "nc-wait", | |
84 | .desc = "non-coherent MIPS wait", | |
85 | }, | |
86 | [STATE_CLOCK_GATED] = { | |
87 | .enter = cps_nc_enter, | |
88 | .exit_latency = 300, | |
89 | .target_residency = 700, | |
90 | .flags = CPUIDLE_FLAG_TIME_VALID | | |
91 | CPUIDLE_FLAG_TIMER_STOP, | |
92 | .name = "clock-gated", | |
93 | .desc = "core clock gated", | |
94 | }, | |
95 | [STATE_POWER_GATED] = { | |
96 | .enter = cps_nc_enter, | |
97 | .exit_latency = 600, | |
98 | .target_residency = 1000, | |
99 | .flags = CPUIDLE_FLAG_TIME_VALID | | |
100 | CPUIDLE_FLAG_TIMER_STOP, | |
101 | .name = "power-gated", | |
102 | .desc = "core power gated", | |
103 | }, | |
104 | }, | |
105 | .state_count = STATE_COUNT, | |
106 | .safe_state_index = 0, | |
107 | }; | |
108 | ||
109 | static void __init cps_cpuidle_unregister(void) | |
110 | { | |
111 | int cpu; | |
112 | struct cpuidle_device *device; | |
113 | ||
114 | for_each_possible_cpu(cpu) { | |
115 | device = &per_cpu(cpuidle_dev, cpu); | |
116 | cpuidle_unregister_device(device); | |
117 | } | |
118 | ||
119 | cpuidle_unregister_driver(&cps_driver); | |
120 | } | |
121 | ||
122 | static int __init cps_cpuidle_init(void) | |
123 | { | |
124 | int err, cpu, core, i; | |
125 | struct cpuidle_device *device; | |
126 | ||
127 | /* Detect supported states */ | |
128 | if (!cps_pm_support_state(CPS_PM_POWER_GATED)) | |
129 | cps_driver.state_count = STATE_CLOCK_GATED + 1; | |
130 | if (!cps_pm_support_state(CPS_PM_CLOCK_GATED)) | |
131 | cps_driver.state_count = STATE_NC_WAIT + 1; | |
132 | if (!cps_pm_support_state(CPS_PM_NC_WAIT)) | |
133 | cps_driver.state_count = STATE_WAIT + 1; | |
134 | ||
135 | /* Inform the user if some states are unavailable */ | |
136 | if (cps_driver.state_count < STATE_COUNT) { | |
137 | pr_info("cpuidle-cps: limited to "); | |
138 | switch (cps_driver.state_count - 1) { | |
139 | case STATE_WAIT: | |
140 | pr_cont("coherent wait\n"); | |
141 | break; | |
142 | case STATE_NC_WAIT: | |
143 | pr_cont("non-coherent wait\n"); | |
144 | break; | |
145 | case STATE_CLOCK_GATED: | |
146 | pr_cont("clock gating\n"); | |
147 | break; | |
148 | } | |
149 | } | |
150 | ||
151 | /* | |
152 | * Set the coupled flag on the appropriate states if this system | |
153 | * requires it. | |
154 | */ | |
155 | if (coupled_coherence) | |
156 | for (i = STATE_NC_WAIT; i < cps_driver.state_count; i++) | |
157 | cps_driver.states[i].flags |= CPUIDLE_FLAG_COUPLED; | |
158 | ||
159 | err = cpuidle_register_driver(&cps_driver); | |
160 | if (err) { | |
161 | pr_err("Failed to register CPS cpuidle driver\n"); | |
162 | return err; | |
163 | } | |
164 | ||
165 | for_each_possible_cpu(cpu) { | |
166 | core = cpu_data[cpu].core; | |
167 | device = &per_cpu(cpuidle_dev, cpu); | |
168 | device->cpu = cpu; | |
169 | #ifdef CONFIG_MIPS_MT | |
170 | cpumask_copy(&device->coupled_cpus, &cpu_sibling_map[cpu]); | |
171 | #endif | |
172 | ||
173 | err = cpuidle_register_device(device); | |
174 | if (err) { | |
175 | pr_err("Failed to register CPU%d cpuidle device\n", | |
176 | cpu); | |
177 | goto err_out; | |
178 | } | |
179 | } | |
180 | ||
181 | return 0; | |
182 | err_out: | |
183 | cps_cpuidle_unregister(); | |
184 | return err; | |
185 | } | |
186 | device_initcall(cps_cpuidle_init); |