2 * Copyright (C) 2014 Imagination Technologies
3 * Author: Paul Burton <paul.burton@imgtec.com>
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.
11 #include <linux/cpu_pm.h>
12 #include <linux/cpuidle.h>
13 #include <linux/init.h>
16 #include <asm/pm-cps.h>
18 /* Enumeration of the various idle states this driver may enter */
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 */
27 static int cps_nc_enter(struct cpuidle_device
*dev
,
28 struct cpuidle_driver
*drv
, int index
)
30 enum cps_pm_state pm_state
;
34 * At least one core must remain powered up & clocked in order for the
35 * system to have any hope of functioning.
37 * TODO: don't treat core 0 specially, just prevent the final core
38 * TODO: remap interrupt affinity temporarily
40 if (!cpu_data
[dev
->cpu
].core
&& (index
> STATE_NC_WAIT
))
41 index
= STATE_NC_WAIT
;
43 /* Select the appropriate cps_pm_state */
46 pm_state
= CPS_PM_NC_WAIT
;
48 case STATE_CLOCK_GATED
:
49 pm_state
= CPS_PM_CLOCK_GATED
;
51 case STATE_POWER_GATED
:
52 pm_state
= CPS_PM_POWER_GATED
;
59 /* Notify listeners the CPU is about to power down */
60 if ((pm_state
== CPS_PM_POWER_GATED
) && cpu_pm_enter())
63 /* Enter that state */
64 err
= cps_pm_enter_state(pm_state
);
66 /* Notify listeners the CPU is back up */
67 if (pm_state
== CPS_PM_POWER_GATED
)
73 static struct cpuidle_driver cps_driver
= {
74 .name
= "cpc_cpuidle",
77 [STATE_WAIT
] = MIPS_CPUIDLE_WAIT_STATE
,
79 .enter
= cps_nc_enter
,
81 .target_residency
= 450,
82 .flags
= CPUIDLE_FLAG_TIME_VALID
,
84 .desc
= "non-coherent MIPS wait",
86 [STATE_CLOCK_GATED
] = {
87 .enter
= cps_nc_enter
,
89 .target_residency
= 700,
90 .flags
= CPUIDLE_FLAG_TIME_VALID
|
91 CPUIDLE_FLAG_TIMER_STOP
,
92 .name
= "clock-gated",
93 .desc
= "core clock gated",
95 [STATE_POWER_GATED
] = {
96 .enter
= cps_nc_enter
,
98 .target_residency
= 1000,
99 .flags
= CPUIDLE_FLAG_TIME_VALID
|
100 CPUIDLE_FLAG_TIMER_STOP
,
101 .name
= "power-gated",
102 .desc
= "core power gated",
105 .state_count
= STATE_COUNT
,
106 .safe_state_index
= 0,
109 static void __init
cps_cpuidle_unregister(void)
112 struct cpuidle_device
*device
;
114 for_each_possible_cpu(cpu
) {
115 device
= &per_cpu(cpuidle_dev
, cpu
);
116 cpuidle_unregister_device(device
);
119 cpuidle_unregister_driver(&cps_driver
);
122 static int __init
cps_cpuidle_init(void)
124 int err
, cpu
, core
, i
;
125 struct cpuidle_device
*device
;
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;
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) {
140 pr_cont("coherent wait\n");
143 pr_cont("non-coherent wait\n");
145 case STATE_CLOCK_GATED
:
146 pr_cont("clock gating\n");
152 * Set the coupled flag on the appropriate states if this system
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
;
159 err
= cpuidle_register_driver(&cps_driver
);
161 pr_err("Failed to register CPS cpuidle driver\n");
165 for_each_possible_cpu(cpu
) {
166 core
= cpu_data
[cpu
].core
;
167 device
= &per_cpu(cpuidle_dev
, cpu
);
169 #ifdef CONFIG_MIPS_MT
170 cpumask_copy(&device
->coupled_cpus
, &cpu_sibling_map
[cpu
]);
173 err
= cpuidle_register_device(device
);
175 pr_err("Failed to register CPU%d cpuidle device\n",
183 cps_cpuidle_unregister();
186 device_initcall(cps_cpuidle_init
);