diff options
author | Paul Burton <paul.burton@imgtec.com> | 2014-04-14 11:25:29 -0400 |
---|---|---|
committer | Paul Burton <paul.burton@imgtec.com> | 2014-05-28 11:20:36 -0400 |
commit | d050894435cdc78807e714a0148527542a583e87 (patch) | |
tree | 39f95ee57dbe42e78945365e2b166161a24cc804 /drivers/cpuidle | |
parent | f08dbf8a61462aa122b9b5077849a3f4bd84702a (diff) |
cpuidle: cpuidle-cps: add MIPS CPS cpuidle driver
This patch adds a cpuidle driver for systems based around the MIPS
Coherent Processing System (CPS) architecture. It supports four idle
states:
- The standard MIPS wait instruction.
- The non-coherent wait, clock gated & power gated states exposed by
the recently added pm-cps layer.
The pm-cps layer is used to enter all the deep idle states. Since cores
in the clock or power gated states cannot service interrupts, the
gic_send_ipi_single function is modified to send a power up command for
the appropriate core to the CPC in cases where the target CPU has marked
itself potentially incoherent.
Signed-off-by: Paul Burton <paul.burton@imgtec.com>
Diffstat (limited to 'drivers/cpuidle')
-rw-r--r-- | drivers/cpuidle/Kconfig | 5 | ||||
-rw-r--r-- | drivers/cpuidle/Kconfig.mips | 17 | ||||
-rw-r--r-- | drivers/cpuidle/Makefile | 4 | ||||
-rw-r--r-- | drivers/cpuidle/cpuidle-cps.c | 186 |
4 files changed, 212 insertions, 0 deletions
diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index f04e25f6c98d..1b96fb91d32c 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig | |||
@@ -35,6 +35,11 @@ depends on ARM | |||
35 | source "drivers/cpuidle/Kconfig.arm" | 35 | source "drivers/cpuidle/Kconfig.arm" |
36 | endmenu | 36 | endmenu |
37 | 37 | ||
38 | menu "MIPS CPU Idle Drivers" | ||
39 | depends on MIPS | ||
40 | source "drivers/cpuidle/Kconfig.mips" | ||
41 | endmenu | ||
42 | |||
38 | menu "POWERPC CPU Idle Drivers" | 43 | menu "POWERPC CPU Idle Drivers" |
39 | depends on PPC | 44 | depends on PPC |
40 | source "drivers/cpuidle/Kconfig.powerpc" | 45 | source "drivers/cpuidle/Kconfig.powerpc" |
diff --git a/drivers/cpuidle/Kconfig.mips b/drivers/cpuidle/Kconfig.mips new file mode 100644 index 000000000000..0e70ee28a5ca --- /dev/null +++ b/drivers/cpuidle/Kconfig.mips | |||
@@ -0,0 +1,17 @@ | |||
1 | # | ||
2 | # MIPS CPU Idle Drivers | ||
3 | # | ||
4 | config MIPS_CPS_CPUIDLE | ||
5 | bool "CPU Idle driver for MIPS CPS platforms" | ||
6 | depends on CPU_IDLE | ||
7 | depends on SYS_SUPPORTS_MIPS_CPS | ||
8 | select ARCH_NEEDS_CPU_IDLE_COUPLED if MIPS_MT | ||
9 | select GENERIC_CLOCKEVENTS_BROADCAST if SMP | ||
10 | select MIPS_CPS_PM | ||
11 | default y | ||
12 | help | ||
13 | Select this option to enable processor idle state management | ||
14 | through cpuidle for systems built around the MIPS Coherent | ||
15 | Processing System (CPS) architecture. In order to make use of | ||
16 | the deepest idle states you will need to ensure that you are | ||
17 | also using the CONFIG_MIPS_CPS SMP implementation. | ||
diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index f71ae1b373c5..a7fc96bcf319 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile | |||
@@ -15,6 +15,10 @@ obj-$(CONFIG_ARM_U8500_CPUIDLE) += cpuidle-ux500.o | |||
15 | obj-$(CONFIG_ARM_AT91_CPUIDLE) += cpuidle-at91.o | 15 | obj-$(CONFIG_ARM_AT91_CPUIDLE) += cpuidle-at91.o |
16 | 16 | ||
17 | ############################################################################### | 17 | ############################################################################### |
18 | # MIPS drivers | ||
19 | obj-$(CONFIG_MIPS_CPS_CPUIDLE) += cpuidle-cps.o | ||
20 | |||
21 | ############################################################################### | ||
18 | # POWERPC drivers | 22 | # POWERPC drivers |
19 | obj-$(CONFIG_PSERIES_CPUIDLE) += cpuidle-pseries.o | 23 | obj-$(CONFIG_PSERIES_CPUIDLE) += cpuidle-pseries.o |
20 | obj-$(CONFIG_POWERNV_CPUIDLE) += cpuidle-powernv.o | 24 | obj-$(CONFIG_POWERNV_CPUIDLE) += cpuidle-powernv.o |
diff --git a/drivers/cpuidle/cpuidle-cps.c b/drivers/cpuidle/cpuidle-cps.c new file mode 100644 index 000000000000..fc7b62720deb --- /dev/null +++ b/drivers/cpuidle/cpuidle-cps.c | |||
@@ -0,0 +1,186 @@ | |||
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); | ||