xref: /openbmc/linux/arch/arm/kernel/cpuidle.c (revision ec7a7aa9)
1fcaf2036SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2e1689795SRobert Lee /*
3e1689795SRobert Lee  * Copyright 2012 Linaro Ltd.
4e1689795SRobert Lee  */
5e1689795SRobert Lee 
6e1689795SRobert Lee #include <linux/cpuidle.h>
7449e056cSDaniel Lezcano #include <linux/of.h>
8eeebc3bbSDaniel Lezcano #include <asm/cpuidle.h>
9e1689795SRobert Lee 
10449e056cSDaniel Lezcano extern struct of_cpuidle_method __cpuidle_method_of_table[];
11449e056cSDaniel Lezcano 
12449e056cSDaniel Lezcano static const struct of_cpuidle_method __cpuidle_method_of_table_sentinel
1333def849SJoe Perches 	__used __section("__cpuidle_method_of_table_end");
14449e056cSDaniel Lezcano 
157619751fSKees Cook static struct cpuidle_ops cpuidle_ops[NR_CPUS] __ro_after_init;
16449e056cSDaniel Lezcano 
179a309d6fSDaniel Lezcano /**
189a309d6fSDaniel Lezcano  * arm_cpuidle_simple_enter() - a wrapper to cpu_do_idle()
199a309d6fSDaniel Lezcano  * @dev: not used
209a309d6fSDaniel Lezcano  * @drv: not used
219a309d6fSDaniel Lezcano  * @index: not used
229a309d6fSDaniel Lezcano  *
239a309d6fSDaniel Lezcano  * A trivial wrapper to allow the cpu_do_idle function to be assigned as a
249a309d6fSDaniel Lezcano  * cpuidle callback by matching the function signature.
259a309d6fSDaniel Lezcano  *
269a309d6fSDaniel Lezcano  * Returns the index passed as parameter
279a309d6fSDaniel Lezcano  */
arm_cpuidle_simple_enter(struct cpuidle_device * dev,struct cpuidle_driver * drv,int index)28*26388a7cSPeter Zijlstra __cpuidle int arm_cpuidle_simple_enter(struct cpuidle_device *dev, struct
29*26388a7cSPeter Zijlstra 				       cpuidle_driver *drv, int index)
30e1689795SRobert Lee {
31e1689795SRobert Lee 	cpu_do_idle();
32e1689795SRobert Lee 
33e1689795SRobert Lee 	return index;
34e1689795SRobert Lee }
35449e056cSDaniel Lezcano 
369a309d6fSDaniel Lezcano /**
379a309d6fSDaniel Lezcano  * arm_cpuidle_suspend() - function to enter low power idle states
389a309d6fSDaniel Lezcano  * @index: an integer used as an identifier for the low level PM callbacks
399a309d6fSDaniel Lezcano  *
409a309d6fSDaniel Lezcano  * This function calls the underlying arch specific low level PM code as
419a309d6fSDaniel Lezcano  * registered at the init time.
429a309d6fSDaniel Lezcano  *
43c3fbbf93SJisheng Zhang  * Returns the result of the suspend callback.
449a309d6fSDaniel Lezcano  */
arm_cpuidle_suspend(int index)45449e056cSDaniel Lezcano int arm_cpuidle_suspend(int index)
46449e056cSDaniel Lezcano {
47449e056cSDaniel Lezcano 	int cpu = smp_processor_id();
48449e056cSDaniel Lezcano 
49c3fbbf93SJisheng Zhang 	return cpuidle_ops[cpu].suspend(index);
50449e056cSDaniel Lezcano }
51449e056cSDaniel Lezcano 
529a309d6fSDaniel Lezcano /**
539a309d6fSDaniel Lezcano  * arm_cpuidle_get_ops() - find a registered cpuidle_ops by name
549a309d6fSDaniel Lezcano  * @method: the method name
559a309d6fSDaniel Lezcano  *
569a309d6fSDaniel Lezcano  * Search in the __cpuidle_method_of_table array the cpuidle ops matching the
579a309d6fSDaniel Lezcano  * method name.
589a309d6fSDaniel Lezcano  *
599a309d6fSDaniel Lezcano  * Returns a struct cpuidle_ops pointer, NULL if not found.
609a309d6fSDaniel Lezcano  */
arm_cpuidle_get_ops(const char * method)614cfd5520SJisheng Zhang static const struct cpuidle_ops *__init arm_cpuidle_get_ops(const char *method)
62449e056cSDaniel Lezcano {
63449e056cSDaniel Lezcano 	struct of_cpuidle_method *m = __cpuidle_method_of_table;
64449e056cSDaniel Lezcano 
65449e056cSDaniel Lezcano 	for (; m->method; m++)
66449e056cSDaniel Lezcano 		if (!strcmp(m->method, method))
67449e056cSDaniel Lezcano 			return m->ops;
68449e056cSDaniel Lezcano 
69449e056cSDaniel Lezcano 	return NULL;
70449e056cSDaniel Lezcano }
71449e056cSDaniel Lezcano 
729a309d6fSDaniel Lezcano /**
739a309d6fSDaniel Lezcano  * arm_cpuidle_read_ops() - Initialize the cpuidle ops with the device tree
749a309d6fSDaniel Lezcano  * @dn: a pointer to a struct device node corresponding to a cpu node
759a309d6fSDaniel Lezcano  * @cpu: the cpu identifier
769a309d6fSDaniel Lezcano  *
779a309d6fSDaniel Lezcano  * Get the method name defined in the 'enable-method' property, retrieve the
789a309d6fSDaniel Lezcano  * associated cpuidle_ops and do a struct copy. This copy is needed because all
794cfd5520SJisheng Zhang  * cpuidle_ops are tagged __initconst and will be unloaded after the init
809a309d6fSDaniel Lezcano  * process.
819a309d6fSDaniel Lezcano  *
829a309d6fSDaniel Lezcano  * Return 0 on sucess, -ENOENT if no 'enable-method' is defined, -EOPNOTSUPP if
83c3fbbf93SJisheng Zhang  * no cpuidle_ops is registered for the 'enable-method', or if either init or
84c3fbbf93SJisheng Zhang  * suspend callback isn't defined.
859a309d6fSDaniel Lezcano  */
arm_cpuidle_read_ops(struct device_node * dn,int cpu)86449e056cSDaniel Lezcano static int __init arm_cpuidle_read_ops(struct device_node *dn, int cpu)
87449e056cSDaniel Lezcano {
88449e056cSDaniel Lezcano 	const char *enable_method;
894cfd5520SJisheng Zhang 	const struct cpuidle_ops *ops;
90449e056cSDaniel Lezcano 
91449e056cSDaniel Lezcano 	enable_method = of_get_property(dn, "enable-method", NULL);
92449e056cSDaniel Lezcano 	if (!enable_method)
93449e056cSDaniel Lezcano 		return -ENOENT;
94449e056cSDaniel Lezcano 
95449e056cSDaniel Lezcano 	ops = arm_cpuidle_get_ops(enable_method);
96449e056cSDaniel Lezcano 	if (!ops) {
97a8e65e06SRob Herring 		pr_warn("%pOF: unsupported enable-method property: %s\n",
98a8e65e06SRob Herring 			dn, enable_method);
99449e056cSDaniel Lezcano 		return -EOPNOTSUPP;
100449e056cSDaniel Lezcano 	}
101449e056cSDaniel Lezcano 
102c3fbbf93SJisheng Zhang 	if (!ops->init || !ops->suspend) {
103c3fbbf93SJisheng Zhang 		pr_warn("cpuidle_ops '%s': no init or suspend callback\n",
104f222a769SJisheng Zhang 			enable_method);
105f222a769SJisheng Zhang 		return -EOPNOTSUPP;
106f222a769SJisheng Zhang 	}
107f222a769SJisheng Zhang 
108449e056cSDaniel Lezcano 	cpuidle_ops[cpu] = *ops; /* structure copy */
109449e056cSDaniel Lezcano 
110449e056cSDaniel Lezcano 	pr_notice("cpuidle: enable-method property '%s'"
111449e056cSDaniel Lezcano 		  " found operations\n", enable_method);
112449e056cSDaniel Lezcano 
113449e056cSDaniel Lezcano 	return 0;
114449e056cSDaniel Lezcano }
115449e056cSDaniel Lezcano 
1169a309d6fSDaniel Lezcano /**
1179a309d6fSDaniel Lezcano  * arm_cpuidle_init() - Initialize cpuidle_ops for a specific cpu
1189a309d6fSDaniel Lezcano  * @cpu: the cpu to be initialized
1199a309d6fSDaniel Lezcano  *
1209a309d6fSDaniel Lezcano  * Initialize the cpuidle ops with the device for the cpu and then call
1219a309d6fSDaniel Lezcano  * the cpu's idle initialization callback. This may fail if the underlying HW
1229a309d6fSDaniel Lezcano  * is not operational.
1239a309d6fSDaniel Lezcano  *
1249a309d6fSDaniel Lezcano  * Returns:
1259a309d6fSDaniel Lezcano  *  0 on success,
1269a309d6fSDaniel Lezcano  *  -ENODEV if it fails to find the cpu node in the device tree,
127f222a769SJisheng Zhang  *  -EOPNOTSUPP if it does not find a registered and valid cpuidle_ops for
128f222a769SJisheng Zhang  *  this cpu,
1299a309d6fSDaniel Lezcano  *  -ENOENT if it fails to find an 'enable-method' property,
1309a309d6fSDaniel Lezcano  *  -ENXIO if the HW reports a failure or a misconfiguration,
1319a309d6fSDaniel Lezcano  *  -ENOMEM if the HW report an memory allocation failure
1329a309d6fSDaniel Lezcano  */
arm_cpuidle_init(int cpu)133449e056cSDaniel Lezcano int __init arm_cpuidle_init(int cpu)
134449e056cSDaniel Lezcano {
135449e056cSDaniel Lezcano 	struct device_node *cpu_node = of_cpu_device_node_get(cpu);
136449e056cSDaniel Lezcano 	int ret;
137449e056cSDaniel Lezcano 
138449e056cSDaniel Lezcano 	if (!cpu_node)
139449e056cSDaniel Lezcano 		return -ENODEV;
140449e056cSDaniel Lezcano 
141449e056cSDaniel Lezcano 	ret = arm_cpuidle_read_ops(cpu_node, cpu);
142f222a769SJisheng Zhang 	if (!ret)
143449e056cSDaniel Lezcano 		ret = cpuidle_ops[cpu].init(cpu_node, cpu);
144449e056cSDaniel Lezcano 
145449e056cSDaniel Lezcano 	of_node_put(cpu_node);
146449e056cSDaniel Lezcano 
147449e056cSDaniel Lezcano 	return ret;
148449e056cSDaniel Lezcano }
149