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

source code of linux/drivers/cpuidle/cpuidle-cps.c