1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * PM domains for CPUs via genpd - managed by cpuidle-psci. |
4 | * |
5 | * Copyright (C) 2019 Linaro Ltd. |
6 | * Author: Ulf Hansson <ulf.hansson@linaro.org> |
7 | * |
8 | */ |
9 | |
10 | #define pr_fmt(fmt) "CPUidle PSCI: " fmt |
11 | |
12 | #include <linux/cpu.h> |
13 | #include <linux/device.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/pm_domain.h> |
17 | #include <linux/pm_runtime.h> |
18 | #include <linux/psci.h> |
19 | #include <linux/slab.h> |
20 | #include <linux/string.h> |
21 | |
22 | #include "cpuidle-psci.h" |
23 | |
24 | struct psci_pd_provider { |
25 | struct list_head link; |
26 | struct device_node *node; |
27 | }; |
28 | |
29 | static LIST_HEAD(psci_pd_providers); |
30 | static bool psci_pd_allow_domain_state; |
31 | |
32 | static int psci_pd_power_off(struct generic_pm_domain *pd) |
33 | { |
34 | struct genpd_power_state *state = &pd->states[pd->state_idx]; |
35 | u32 *pd_state; |
36 | |
37 | if (!state->data) |
38 | return 0; |
39 | |
40 | if (!psci_pd_allow_domain_state) |
41 | return -EBUSY; |
42 | |
43 | /* OSI mode is enabled, set the corresponding domain state. */ |
44 | pd_state = state->data; |
45 | psci_set_domain_state(state: *pd_state); |
46 | |
47 | return 0; |
48 | } |
49 | |
50 | static int psci_pd_init(struct device_node *np, bool use_osi) |
51 | { |
52 | struct generic_pm_domain *pd; |
53 | struct psci_pd_provider *pd_provider; |
54 | struct dev_power_governor *pd_gov; |
55 | int ret = -ENOMEM; |
56 | |
57 | pd = dt_idle_pd_alloc(np, psci_dt_parse_state_node); |
58 | if (!pd) |
59 | goto out; |
60 | |
61 | pd_provider = kzalloc(size: sizeof(*pd_provider), GFP_KERNEL); |
62 | if (!pd_provider) |
63 | goto free_pd; |
64 | |
65 | pd->flags |= GENPD_FLAG_IRQ_SAFE | GENPD_FLAG_CPU_DOMAIN; |
66 | |
67 | /* |
68 | * Allow power off when OSI has been successfully enabled. |
69 | * PREEMPT_RT is not yet ready to enter domain idle states. |
70 | */ |
71 | if (use_osi && !IS_ENABLED(CONFIG_PREEMPT_RT)) |
72 | pd->power_off = psci_pd_power_off; |
73 | else |
74 | pd->flags |= GENPD_FLAG_ALWAYS_ON; |
75 | |
76 | /* Use governor for CPU PM domains if it has some states to manage. */ |
77 | pd_gov = pd->states ? &pm_domain_cpu_gov : NULL; |
78 | |
79 | ret = pm_genpd_init(genpd: pd, gov: pd_gov, is_off: false); |
80 | if (ret) |
81 | goto free_pd_prov; |
82 | |
83 | ret = of_genpd_add_provider_simple(np, genpd: pd); |
84 | if (ret) |
85 | goto remove_pd; |
86 | |
87 | pd_provider->node = of_node_get(node: np); |
88 | list_add(new: &pd_provider->link, head: &psci_pd_providers); |
89 | |
90 | pr_debug("init PM domain %s\n" , pd->name); |
91 | return 0; |
92 | |
93 | remove_pd: |
94 | pm_genpd_remove(genpd: pd); |
95 | free_pd_prov: |
96 | kfree(objp: pd_provider); |
97 | free_pd: |
98 | dt_idle_pd_free(pd); |
99 | out: |
100 | pr_err("failed to init PM domain ret=%d %pOF\n" , ret, np); |
101 | return ret; |
102 | } |
103 | |
104 | static void psci_pd_remove(void) |
105 | { |
106 | struct psci_pd_provider *pd_provider, *it; |
107 | struct generic_pm_domain *genpd; |
108 | |
109 | list_for_each_entry_safe_reverse(pd_provider, it, |
110 | &psci_pd_providers, link) { |
111 | of_genpd_del_provider(np: pd_provider->node); |
112 | |
113 | genpd = of_genpd_remove_last(np: pd_provider->node); |
114 | if (!IS_ERR(ptr: genpd)) |
115 | kfree(objp: genpd); |
116 | |
117 | of_node_put(node: pd_provider->node); |
118 | list_del(entry: &pd_provider->link); |
119 | kfree(objp: pd_provider); |
120 | } |
121 | } |
122 | |
123 | static void psci_cpuidle_domain_sync_state(struct device *dev) |
124 | { |
125 | /* |
126 | * All devices have now been attached/probed to the PM domain topology, |
127 | * hence it's fine to allow domain states to be picked. |
128 | */ |
129 | psci_pd_allow_domain_state = true; |
130 | } |
131 | |
132 | static const struct of_device_id psci_of_match[] = { |
133 | { .compatible = "arm,psci-1.0" }, |
134 | {} |
135 | }; |
136 | |
137 | static int psci_cpuidle_domain_probe(struct platform_device *pdev) |
138 | { |
139 | struct device_node *np = pdev->dev.of_node; |
140 | struct device_node *node; |
141 | bool use_osi = psci_has_osi_support(); |
142 | int ret = 0, pd_count = 0; |
143 | |
144 | if (!np) |
145 | return -ENODEV; |
146 | |
147 | /* |
148 | * Parse child nodes for the "#power-domain-cells" property and |
149 | * initialize a genpd/genpd-of-provider pair when it's found. |
150 | */ |
151 | for_each_child_of_node(np, node) { |
152 | if (!of_property_present(np: node, propname: "#power-domain-cells" )) |
153 | continue; |
154 | |
155 | ret = psci_pd_init(np: node, use_osi); |
156 | if (ret) { |
157 | of_node_put(node); |
158 | goto exit; |
159 | } |
160 | |
161 | pd_count++; |
162 | } |
163 | |
164 | /* Bail out if not using the hierarchical CPU topology. */ |
165 | if (!pd_count) |
166 | return 0; |
167 | |
168 | /* Link genpd masters/subdomains to model the CPU topology. */ |
169 | ret = dt_idle_pd_init_topology(np); |
170 | if (ret) |
171 | goto remove_pd; |
172 | |
173 | /* let's try to enable OSI. */ |
174 | ret = psci_set_osi_mode(enable: use_osi); |
175 | if (ret) |
176 | goto remove_pd; |
177 | |
178 | pr_info("Initialized CPU PM domain topology using %s mode\n" , |
179 | use_osi ? "OSI" : "PC" ); |
180 | return 0; |
181 | |
182 | remove_pd: |
183 | dt_idle_pd_remove_topology(np); |
184 | psci_pd_remove(); |
185 | exit: |
186 | pr_err("failed to create CPU PM domains ret=%d\n" , ret); |
187 | return ret; |
188 | } |
189 | |
190 | static struct platform_driver psci_cpuidle_domain_driver = { |
191 | .probe = psci_cpuidle_domain_probe, |
192 | .driver = { |
193 | .name = "psci-cpuidle-domain" , |
194 | .of_match_table = psci_of_match, |
195 | .sync_state = psci_cpuidle_domain_sync_state, |
196 | }, |
197 | }; |
198 | |
199 | static int __init psci_idle_init_domains(void) |
200 | { |
201 | return platform_driver_register(&psci_cpuidle_domain_driver); |
202 | } |
203 | subsys_initcall(psci_idle_init_domains); |
204 | |