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