xref: /linux/drivers/cpuidle/cpuidle-psci-domain.c (revision cdd38c5f1ce4398ec58fec95904b75824daab7b5)
1a5e0454cSUlf Hansson // SPDX-License-Identifier: GPL-2.0
2a5e0454cSUlf Hansson /*
3a5e0454cSUlf Hansson  * PM domains for CPUs via genpd - managed by cpuidle-psci.
4a5e0454cSUlf Hansson  *
5a5e0454cSUlf Hansson  * Copyright (C) 2019 Linaro Ltd.
6a5e0454cSUlf Hansson  * Author: Ulf Hansson <ulf.hansson@linaro.org>
7a5e0454cSUlf Hansson  *
8a5e0454cSUlf Hansson  */
9a5e0454cSUlf Hansson 
10a65a397fSUlf Hansson #define pr_fmt(fmt) "CPUidle PSCI: " fmt
11a65a397fSUlf Hansson 
12a5e0454cSUlf Hansson #include <linux/cpu.h>
13a5e0454cSUlf Hansson #include <linux/device.h>
14a5e0454cSUlf Hansson #include <linux/kernel.h>
15ee7c34caSUlf Hansson #include <linux/platform_device.h>
16a5e0454cSUlf Hansson #include <linux/pm_domain.h>
17a5e0454cSUlf Hansson #include <linux/pm_runtime.h>
18a65a397fSUlf Hansson #include <linux/psci.h>
19a65a397fSUlf Hansson #include <linux/slab.h>
20a65a397fSUlf Hansson #include <linux/string.h>
21a5e0454cSUlf Hansson 
22a5e0454cSUlf Hansson #include "cpuidle-psci.h"
23a5e0454cSUlf Hansson 
24a65a397fSUlf Hansson struct psci_pd_provider {
25a65a397fSUlf Hansson 	struct list_head link;
26a65a397fSUlf Hansson 	struct device_node *node;
27a65a397fSUlf Hansson };
28a65a397fSUlf Hansson 
29a65a397fSUlf Hansson static LIST_HEAD(psci_pd_providers);
3081f94ddfSUlf Hansson static bool psci_pd_allow_domain_state;
31a65a397fSUlf Hansson 
32a65a397fSUlf Hansson static int psci_pd_power_off(struct generic_pm_domain *pd)
33a65a397fSUlf Hansson {
34a65a397fSUlf Hansson 	struct genpd_power_state *state = &pd->states[pd->state_idx];
35a65a397fSUlf Hansson 	u32 *pd_state;
36a65a397fSUlf Hansson 
37a65a397fSUlf Hansson 	if (!state->data)
38a65a397fSUlf Hansson 		return 0;
39a65a397fSUlf Hansson 
4081f94ddfSUlf Hansson 	if (!psci_pd_allow_domain_state)
4181f94ddfSUlf Hansson 		return -EBUSY;
4281f94ddfSUlf Hansson 
43a65a397fSUlf Hansson 	/* OSI mode is enabled, set the corresponding domain state. */
44a65a397fSUlf Hansson 	pd_state = state->data;
45a65a397fSUlf Hansson 	psci_set_domain_state(*pd_state);
46a65a397fSUlf Hansson 
47a65a397fSUlf Hansson 	return 0;
48a65a397fSUlf Hansson }
49a65a397fSUlf Hansson 
50ee7c34caSUlf Hansson static int psci_pd_parse_state_nodes(struct genpd_power_state *states,
51a65a397fSUlf Hansson 				     int state_count)
52a65a397fSUlf Hansson {
53a65a397fSUlf Hansson 	int i, ret;
54a65a397fSUlf Hansson 	u32 psci_state, *psci_state_buf;
55a65a397fSUlf Hansson 
56a65a397fSUlf Hansson 	for (i = 0; i < state_count; i++) {
57a65a397fSUlf Hansson 		ret = psci_dt_parse_state_node(to_of_node(states[i].fwnode),
58a65a397fSUlf Hansson 					&psci_state);
59a65a397fSUlf Hansson 		if (ret)
60a65a397fSUlf Hansson 			goto free_state;
61a65a397fSUlf Hansson 
62a65a397fSUlf Hansson 		psci_state_buf = kmalloc(sizeof(u32), GFP_KERNEL);
63a65a397fSUlf Hansson 		if (!psci_state_buf) {
64a65a397fSUlf Hansson 			ret = -ENOMEM;
65a65a397fSUlf Hansson 			goto free_state;
66a65a397fSUlf Hansson 		}
67a65a397fSUlf Hansson 		*psci_state_buf = psci_state;
68a65a397fSUlf Hansson 		states[i].data = psci_state_buf;
69a65a397fSUlf Hansson 	}
70a65a397fSUlf Hansson 
71a65a397fSUlf Hansson 	return 0;
72a65a397fSUlf Hansson 
73a65a397fSUlf Hansson free_state:
74a65a397fSUlf Hansson 	i--;
75a65a397fSUlf Hansson 	for (; i >= 0; i--)
76a65a397fSUlf Hansson 		kfree(states[i].data);
77a65a397fSUlf Hansson 	return ret;
78a65a397fSUlf Hansson }
79a65a397fSUlf Hansson 
80ee7c34caSUlf Hansson static int psci_pd_parse_states(struct device_node *np,
81a65a397fSUlf Hansson 			struct genpd_power_state **states, int *state_count)
82a65a397fSUlf Hansson {
83a65a397fSUlf Hansson 	int ret;
84a65a397fSUlf Hansson 
85a65a397fSUlf Hansson 	/* Parse the domain idle states. */
86a65a397fSUlf Hansson 	ret = of_genpd_parse_idle_states(np, states, state_count);
87a65a397fSUlf Hansson 	if (ret)
88a65a397fSUlf Hansson 		return ret;
89a65a397fSUlf Hansson 
90a65a397fSUlf Hansson 	/* Fill out the PSCI specifics for each found state. */
91a65a397fSUlf Hansson 	ret = psci_pd_parse_state_nodes(*states, *state_count);
92a65a397fSUlf Hansson 	if (ret)
93a65a397fSUlf Hansson 		kfree(*states);
94a65a397fSUlf Hansson 
95a65a397fSUlf Hansson 	return ret;
96a65a397fSUlf Hansson }
97a65a397fSUlf Hansson 
98a65a397fSUlf Hansson static void psci_pd_free_states(struct genpd_power_state *states,
99a65a397fSUlf Hansson 				unsigned int state_count)
100a65a397fSUlf Hansson {
101a65a397fSUlf Hansson 	int i;
102a65a397fSUlf Hansson 
103a65a397fSUlf Hansson 	for (i = 0; i < state_count; i++)
104a65a397fSUlf Hansson 		kfree(states[i].data);
105a65a397fSUlf Hansson 	kfree(states);
106a65a397fSUlf Hansson }
107a65a397fSUlf Hansson 
10870c179b4SUlf Hansson static int psci_pd_init(struct device_node *np, bool use_osi)
109a65a397fSUlf Hansson {
110a65a397fSUlf Hansson 	struct generic_pm_domain *pd;
111a65a397fSUlf Hansson 	struct psci_pd_provider *pd_provider;
112a65a397fSUlf Hansson 	struct dev_power_governor *pd_gov;
113a65a397fSUlf Hansson 	struct genpd_power_state *states = NULL;
114a65a397fSUlf Hansson 	int ret = -ENOMEM, state_count = 0;
115a65a397fSUlf Hansson 
116a65a397fSUlf Hansson 	pd = kzalloc(sizeof(*pd), GFP_KERNEL);
117a65a397fSUlf Hansson 	if (!pd)
118a65a397fSUlf Hansson 		goto out;
119a65a397fSUlf Hansson 
120a65a397fSUlf Hansson 	pd_provider = kzalloc(sizeof(*pd_provider), GFP_KERNEL);
121a65a397fSUlf Hansson 	if (!pd_provider)
122a65a397fSUlf Hansson 		goto free_pd;
123a65a397fSUlf Hansson 
124a65a397fSUlf Hansson 	pd->name = kasprintf(GFP_KERNEL, "%pOF", np);
125a65a397fSUlf Hansson 	if (!pd->name)
126a65a397fSUlf Hansson 		goto free_pd_prov;
127a65a397fSUlf Hansson 
128a65a397fSUlf Hansson 	/*
129a65a397fSUlf Hansson 	 * Parse the domain idle states and let genpd manage the state selection
130a65a397fSUlf Hansson 	 * for those being compatible with "domain-idle-state".
131a65a397fSUlf Hansson 	 */
132a65a397fSUlf Hansson 	ret = psci_pd_parse_states(np, &states, &state_count);
133a65a397fSUlf Hansson 	if (ret)
134a65a397fSUlf Hansson 		goto free_name;
135a65a397fSUlf Hansson 
136a65a397fSUlf Hansson 	pd->free_states = psci_pd_free_states;
137a65a397fSUlf Hansson 	pd->name = kbasename(pd->name);
138a65a397fSUlf Hansson 	pd->states = states;
139a65a397fSUlf Hansson 	pd->state_count = state_count;
140a65a397fSUlf Hansson 	pd->flags |= GENPD_FLAG_IRQ_SAFE | GENPD_FLAG_CPU_DOMAIN;
141a65a397fSUlf Hansson 
14270c179b4SUlf Hansson 	/* Allow power off when OSI has been successfully enabled. */
14370c179b4SUlf Hansson 	if (use_osi)
14470c179b4SUlf Hansson 		pd->power_off = psci_pd_power_off;
14570c179b4SUlf Hansson 	else
14670c179b4SUlf Hansson 		pd->flags |= GENPD_FLAG_ALWAYS_ON;
14770c179b4SUlf Hansson 
148a65a397fSUlf Hansson 	/* Use governor for CPU PM domains if it has some states to manage. */
149a65a397fSUlf Hansson 	pd_gov = state_count > 0 ? &pm_domain_cpu_gov : NULL;
150a65a397fSUlf Hansson 
151a65a397fSUlf Hansson 	ret = pm_genpd_init(pd, pd_gov, false);
152a65a397fSUlf Hansson 	if (ret) {
153a65a397fSUlf Hansson 		psci_pd_free_states(states, state_count);
154a65a397fSUlf Hansson 		goto free_name;
155a65a397fSUlf Hansson 	}
156a65a397fSUlf Hansson 
157a65a397fSUlf Hansson 	ret = of_genpd_add_provider_simple(np, pd);
158a65a397fSUlf Hansson 	if (ret)
159a65a397fSUlf Hansson 		goto remove_pd;
160a65a397fSUlf Hansson 
161a65a397fSUlf Hansson 	pd_provider->node = of_node_get(np);
162a65a397fSUlf Hansson 	list_add(&pd_provider->link, &psci_pd_providers);
163a65a397fSUlf Hansson 
164a65a397fSUlf Hansson 	pr_debug("init PM domain %s\n", pd->name);
165a65a397fSUlf Hansson 	return 0;
166a65a397fSUlf Hansson 
167a65a397fSUlf Hansson remove_pd:
168a65a397fSUlf Hansson 	pm_genpd_remove(pd);
169a65a397fSUlf Hansson free_name:
170a65a397fSUlf Hansson 	kfree(pd->name);
171a65a397fSUlf Hansson free_pd_prov:
172a65a397fSUlf Hansson 	kfree(pd_provider);
173a65a397fSUlf Hansson free_pd:
174a65a397fSUlf Hansson 	kfree(pd);
175a65a397fSUlf Hansson out:
176a65a397fSUlf Hansson 	pr_err("failed to init PM domain ret=%d %pOF\n", ret, np);
177a65a397fSUlf Hansson 	return ret;
178a65a397fSUlf Hansson }
179a65a397fSUlf Hansson 
180ee7c34caSUlf Hansson static void psci_pd_remove(void)
181a65a397fSUlf Hansson {
182a65a397fSUlf Hansson 	struct psci_pd_provider *pd_provider, *it;
183a65a397fSUlf Hansson 	struct generic_pm_domain *genpd;
184a65a397fSUlf Hansson 
185a65a397fSUlf Hansson 	list_for_each_entry_safe(pd_provider, it, &psci_pd_providers, link) {
186a65a397fSUlf Hansson 		of_genpd_del_provider(pd_provider->node);
187a65a397fSUlf Hansson 
188a65a397fSUlf Hansson 		genpd = of_genpd_remove_last(pd_provider->node);
189a65a397fSUlf Hansson 		if (!IS_ERR(genpd))
190a65a397fSUlf Hansson 			kfree(genpd);
191a65a397fSUlf Hansson 
192a65a397fSUlf Hansson 		of_node_put(pd_provider->node);
193a65a397fSUlf Hansson 		list_del(&pd_provider->link);
194a65a397fSUlf Hansson 		kfree(pd_provider);
195a65a397fSUlf Hansson 	}
196a65a397fSUlf Hansson }
197a65a397fSUlf Hansson 
19870c179b4SUlf Hansson static int psci_pd_init_topology(struct device_node *np)
199a65a397fSUlf Hansson {
200a65a397fSUlf Hansson 	struct device_node *node;
201a65a397fSUlf Hansson 	struct of_phandle_args child, parent;
202a65a397fSUlf Hansson 	int ret;
203a65a397fSUlf Hansson 
204a65a397fSUlf Hansson 	for_each_child_of_node(np, node) {
205a65a397fSUlf Hansson 		if (of_parse_phandle_with_args(node, "power-domains",
206a65a397fSUlf Hansson 					"#power-domain-cells", 0, &parent))
207a65a397fSUlf Hansson 			continue;
208a65a397fSUlf Hansson 
209a65a397fSUlf Hansson 		child.np = node;
210a65a397fSUlf Hansson 		child.args_count = 0;
21170c179b4SUlf Hansson 		ret = of_genpd_add_subdomain(&parent, &child);
212a65a397fSUlf Hansson 		of_node_put(parent.np);
213a65a397fSUlf Hansson 		if (ret) {
214a65a397fSUlf Hansson 			of_node_put(node);
215a65a397fSUlf Hansson 			return ret;
216a65a397fSUlf Hansson 		}
217a65a397fSUlf Hansson 	}
218a65a397fSUlf Hansson 
219a65a397fSUlf Hansson 	return 0;
220a65a397fSUlf Hansson }
221a65a397fSUlf Hansson 
22270c179b4SUlf Hansson static bool psci_pd_try_set_osi_mode(void)
223a65a397fSUlf Hansson {
22470c179b4SUlf Hansson 	int ret;
22570c179b4SUlf Hansson 
22670c179b4SUlf Hansson 	if (!psci_has_osi_support())
22770c179b4SUlf Hansson 		return false;
22870c179b4SUlf Hansson 
22970c179b4SUlf Hansson 	ret = psci_set_osi_mode(true);
23070c179b4SUlf Hansson 	if (ret) {
23170c179b4SUlf Hansson 		pr_warn("failed to enable OSI mode: %d\n", ret);
23270c179b4SUlf Hansson 		return false;
233a65a397fSUlf Hansson 	}
234a65a397fSUlf Hansson 
23570c179b4SUlf Hansson 	return true;
236a65a397fSUlf Hansson }
237a65a397fSUlf Hansson 
23881f94ddfSUlf Hansson static void psci_cpuidle_domain_sync_state(struct device *dev)
23981f94ddfSUlf Hansson {
24081f94ddfSUlf Hansson 	/*
24181f94ddfSUlf Hansson 	 * All devices have now been attached/probed to the PM domain topology,
24281f94ddfSUlf Hansson 	 * hence it's fine to allow domain states to be picked.
24381f94ddfSUlf Hansson 	 */
24481f94ddfSUlf Hansson 	psci_pd_allow_domain_state = true;
24581f94ddfSUlf Hansson }
24681f94ddfSUlf Hansson 
247ee7c34caSUlf Hansson static const struct of_device_id psci_of_match[] = {
248a65a397fSUlf Hansson 	{ .compatible = "arm,psci-1.0" },
249a65a397fSUlf Hansson 	{}
250a65a397fSUlf Hansson };
251a65a397fSUlf Hansson 
252ee7c34caSUlf Hansson static int psci_cpuidle_domain_probe(struct platform_device *pdev)
253a65a397fSUlf Hansson {
254ee7c34caSUlf Hansson 	struct device_node *np = pdev->dev.of_node;
255a65a397fSUlf Hansson 	struct device_node *node;
25670c179b4SUlf Hansson 	bool use_osi;
257a65a397fSUlf Hansson 	int ret = 0, pd_count = 0;
258a65a397fSUlf Hansson 
259a65a397fSUlf Hansson 	if (!np)
260a65a397fSUlf Hansson 		return -ENODEV;
261a65a397fSUlf Hansson 
26270c179b4SUlf Hansson 	/* If OSI mode is supported, let's try to enable it. */
26370c179b4SUlf Hansson 	use_osi = psci_pd_try_set_osi_mode();
264a65a397fSUlf Hansson 
265a65a397fSUlf Hansson 	/*
266a65a397fSUlf Hansson 	 * Parse child nodes for the "#power-domain-cells" property and
267a65a397fSUlf Hansson 	 * initialize a genpd/genpd-of-provider pair when it's found.
268a65a397fSUlf Hansson 	 */
269a65a397fSUlf Hansson 	for_each_child_of_node(np, node) {
270a65a397fSUlf Hansson 		if (!of_find_property(node, "#power-domain-cells", NULL))
271a65a397fSUlf Hansson 			continue;
272a65a397fSUlf Hansson 
27370c179b4SUlf Hansson 		ret = psci_pd_init(node, use_osi);
274a65a397fSUlf Hansson 		if (ret)
275a65a397fSUlf Hansson 			goto put_node;
276a65a397fSUlf Hansson 
277a65a397fSUlf Hansson 		pd_count++;
278a65a397fSUlf Hansson 	}
279a65a397fSUlf Hansson 
280a65a397fSUlf Hansson 	/* Bail out if not using the hierarchical CPU topology. */
281a65a397fSUlf Hansson 	if (!pd_count)
28270c179b4SUlf Hansson 		goto no_pd;
283a65a397fSUlf Hansson 
284a65a397fSUlf Hansson 	/* Link genpd masters/subdomains to model the CPU topology. */
28570c179b4SUlf Hansson 	ret = psci_pd_init_topology(np);
286a65a397fSUlf Hansson 	if (ret)
287a65a397fSUlf Hansson 		goto remove_pd;
288a65a397fSUlf Hansson 
289a65a397fSUlf Hansson 	pr_info("Initialized CPU PM domain topology\n");
290ee7c34caSUlf Hansson 	return 0;
291a65a397fSUlf Hansson 
292a65a397fSUlf Hansson put_node:
293a65a397fSUlf Hansson 	of_node_put(node);
294a65a397fSUlf Hansson remove_pd:
295a65a397fSUlf Hansson 	psci_pd_remove();
296a65a397fSUlf Hansson 	pr_err("failed to create CPU PM domains ret=%d\n", ret);
29770c179b4SUlf Hansson no_pd:
29870c179b4SUlf Hansson 	if (use_osi)
29970c179b4SUlf Hansson 		psci_set_osi_mode(false);
300a65a397fSUlf Hansson 	return ret;
301a65a397fSUlf Hansson }
302ee7c34caSUlf Hansson 
303ee7c34caSUlf Hansson static struct platform_driver psci_cpuidle_domain_driver = {
304ee7c34caSUlf Hansson 	.probe  = psci_cpuidle_domain_probe,
305ee7c34caSUlf Hansson 	.driver = {
306ee7c34caSUlf Hansson 		.name = "psci-cpuidle-domain",
307ee7c34caSUlf Hansson 		.of_match_table = psci_of_match,
30881f94ddfSUlf Hansson 		.sync_state = psci_cpuidle_domain_sync_state,
309ee7c34caSUlf Hansson 	},
310ee7c34caSUlf Hansson };
311ee7c34caSUlf Hansson 
312ee7c34caSUlf Hansson static int __init psci_idle_init_domains(void)
313ee7c34caSUlf Hansson {
314ee7c34caSUlf Hansson 	return platform_driver_register(&psci_cpuidle_domain_driver);
315ee7c34caSUlf Hansson }
316a65a397fSUlf Hansson subsys_initcall(psci_idle_init_domains);
317a65a397fSUlf Hansson 
318166bf835SUlf Hansson struct device *psci_dt_attach_cpu(int cpu)
319a5e0454cSUlf Hansson {
320a5e0454cSUlf Hansson 	struct device *dev;
321a5e0454cSUlf Hansson 
322a5e0454cSUlf Hansson 	dev = dev_pm_domain_attach_by_name(get_cpu_device(cpu), "psci");
323a5e0454cSUlf Hansson 	if (IS_ERR_OR_NULL(dev))
324a5e0454cSUlf Hansson 		return dev;
325a5e0454cSUlf Hansson 
326a5e0454cSUlf Hansson 	pm_runtime_irq_safe(dev);
327a5e0454cSUlf Hansson 	if (cpu_online(cpu))
328a5e0454cSUlf Hansson 		pm_runtime_get_sync(dev);
329a5e0454cSUlf Hansson 
330*670c90deSUlf Hansson 	dev_pm_syscore_device(dev, true);
331*670c90deSUlf Hansson 
332a5e0454cSUlf Hansson 	return dev;
333a5e0454cSUlf Hansson }
334166bf835SUlf Hansson 
335166bf835SUlf Hansson void psci_dt_detach_cpu(struct device *dev)
336166bf835SUlf Hansson {
337166bf835SUlf Hansson 	if (IS_ERR_OR_NULL(dev))
338166bf835SUlf Hansson 		return;
339166bf835SUlf Hansson 
340166bf835SUlf Hansson 	dev_pm_domain_detach(dev, false);
341166bf835SUlf Hansson }
342