Commit | Line | Data |
---|---|---|
6df9d38f HM |
1 | // SPDX-License-Identifier: GPL-2.0-only OR MIT |
2 | /* | |
3 | * Apple SoC PMGR device power state driver | |
4 | * | |
5 | * Copyright The Asahi Linux Contributors | |
6 | */ | |
7 | ||
8 | #include <linux/bitops.h> | |
9 | #include <linux/bitfield.h> | |
10 | #include <linux/err.h> | |
11 | #include <linux/of.h> | |
12 | #include <linux/of_address.h> | |
13 | #include <linux/platform_device.h> | |
14 | #include <linux/pm_domain.h> | |
15 | #include <linux/regmap.h> | |
16 | #include <linux/mfd/syscon.h> | |
17 | #include <linux/reset-controller.h> | |
18 | #include <linux/module.h> | |
19 | ||
20 | #define APPLE_PMGR_RESET BIT(31) | |
21 | #define APPLE_PMGR_AUTO_ENABLE BIT(28) | |
22 | #define APPLE_PMGR_PS_AUTO GENMASK(27, 24) | |
cc1fe1e5 | 23 | #define APPLE_PMGR_PS_MIN GENMASK(19, 16) |
6df9d38f HM |
24 | #define APPLE_PMGR_PARENT_OFF BIT(11) |
25 | #define APPLE_PMGR_DEV_DISABLE BIT(10) | |
26 | #define APPLE_PMGR_WAS_CLKGATED BIT(9) | |
27 | #define APPLE_PMGR_WAS_PWRGATED BIT(8) | |
28 | #define APPLE_PMGR_PS_ACTUAL GENMASK(7, 4) | |
29 | #define APPLE_PMGR_PS_TARGET GENMASK(3, 0) | |
30 | ||
31 | #define APPLE_PMGR_FLAGS (APPLE_PMGR_WAS_CLKGATED | APPLE_PMGR_WAS_PWRGATED) | |
32 | ||
33 | #define APPLE_PMGR_PS_ACTIVE 0xf | |
34 | #define APPLE_PMGR_PS_CLKGATE 0x4 | |
35 | #define APPLE_PMGR_PS_PWRGATE 0x0 | |
36 | ||
37 | #define APPLE_PMGR_PS_SET_TIMEOUT 100 | |
38 | #define APPLE_PMGR_RESET_TIME 1 | |
39 | ||
40 | struct apple_pmgr_ps { | |
41 | struct device *dev; | |
42 | struct generic_pm_domain genpd; | |
43 | struct reset_controller_dev rcdev; | |
44 | struct regmap *regmap; | |
45 | u32 offset; | |
cc1fe1e5 | 46 | u32 min_state; |
6df9d38f HM |
47 | }; |
48 | ||
49 | #define genpd_to_apple_pmgr_ps(_genpd) container_of(_genpd, struct apple_pmgr_ps, genpd) | |
50 | #define rcdev_to_apple_pmgr_ps(_rcdev) container_of(_rcdev, struct apple_pmgr_ps, rcdev) | |
51 | ||
52 | static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool auto_enable) | |
53 | { | |
54 | int ret; | |
55 | struct apple_pmgr_ps *ps = genpd_to_apple_pmgr_ps(genpd); | |
56 | u32 reg; | |
57 | ||
58 | ret = regmap_read(ps->regmap, ps->offset, ®); | |
59 | if (ret < 0) | |
60 | return ret; | |
61 | ||
62 | /* Resets are synchronous, and only work if the device is powered and clocked. */ | |
63 | if (reg & APPLE_PMGR_RESET && pstate != APPLE_PMGR_PS_ACTIVE) | |
64 | dev_err(ps->dev, "PS %s: powering off with RESET active\n", | |
65 | genpd->name); | |
66 | ||
67 | reg &= ~(APPLE_PMGR_AUTO_ENABLE | APPLE_PMGR_FLAGS | APPLE_PMGR_PS_TARGET); | |
68 | reg |= FIELD_PREP(APPLE_PMGR_PS_TARGET, pstate); | |
69 | ||
70 | dev_dbg(ps->dev, "PS %s: pwrstate = 0x%x: 0x%x\n", genpd->name, pstate, reg); | |
71 | ||
72 | regmap_write(ps->regmap, ps->offset, reg); | |
73 | ||
74 | ret = regmap_read_poll_timeout_atomic( | |
75 | ps->regmap, ps->offset, reg, | |
76 | (FIELD_GET(APPLE_PMGR_PS_ACTUAL, reg) == pstate), 1, | |
77 | APPLE_PMGR_PS_SET_TIMEOUT); | |
78 | if (ret < 0) | |
79 | dev_err(ps->dev, "PS %s: Failed to reach power state 0x%x (now: 0x%x)\n", | |
80 | genpd->name, pstate, reg); | |
81 | ||
82 | if (auto_enable) { | |
83 | /* Not all devices implement this; this is a no-op where not implemented. */ | |
84 | reg &= ~APPLE_PMGR_FLAGS; | |
85 | reg |= APPLE_PMGR_AUTO_ENABLE; | |
86 | regmap_write(ps->regmap, ps->offset, reg); | |
87 | } | |
88 | ||
89 | return ret; | |
90 | } | |
91 | ||
92 | static bool apple_pmgr_ps_is_active(struct apple_pmgr_ps *ps) | |
93 | { | |
94 | u32 reg = 0; | |
95 | ||
96 | regmap_read(ps->regmap, ps->offset, ®); | |
97 | /* | |
98 | * We consider domains as active if they are actually on, or if they have auto-PM | |
99 | * enabled and the intended target is on. | |
100 | */ | |
101 | return (FIELD_GET(APPLE_PMGR_PS_ACTUAL, reg) == APPLE_PMGR_PS_ACTIVE || | |
102 | (FIELD_GET(APPLE_PMGR_PS_TARGET, reg) == APPLE_PMGR_PS_ACTIVE && | |
103 | reg & APPLE_PMGR_AUTO_ENABLE)); | |
104 | } | |
105 | ||
106 | static int apple_pmgr_ps_power_on(struct generic_pm_domain *genpd) | |
107 | { | |
108 | return apple_pmgr_ps_set(genpd, APPLE_PMGR_PS_ACTIVE, true); | |
109 | } | |
110 | ||
111 | static int apple_pmgr_ps_power_off(struct generic_pm_domain *genpd) | |
112 | { | |
113 | return apple_pmgr_ps_set(genpd, APPLE_PMGR_PS_PWRGATE, false); | |
114 | } | |
115 | ||
116 | static int apple_pmgr_reset_assert(struct reset_controller_dev *rcdev, unsigned long id) | |
117 | { | |
118 | struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev); | |
c289d5bc | 119 | unsigned long flags; |
6df9d38f | 120 | |
c289d5bc | 121 | spin_lock_irqsave(&ps->genpd.slock, flags); |
6df9d38f HM |
122 | |
123 | if (ps->genpd.status == GENPD_STATE_OFF) | |
124 | dev_err(ps->dev, "PS 0x%x: asserting RESET while powered down\n", ps->offset); | |
125 | ||
126 | dev_dbg(ps->dev, "PS 0x%x: assert reset\n", ps->offset); | |
127 | /* Quiesce device before asserting reset */ | |
128 | regmap_update_bits(ps->regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_DEV_DISABLE, | |
129 | APPLE_PMGR_DEV_DISABLE); | |
130 | regmap_update_bits(ps->regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_RESET, | |
131 | APPLE_PMGR_RESET); | |
132 | ||
c289d5bc | 133 | spin_unlock_irqrestore(&ps->genpd.slock, flags); |
6df9d38f HM |
134 | |
135 | return 0; | |
136 | } | |
137 | ||
138 | static int apple_pmgr_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id) | |
139 | { | |
140 | struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev); | |
c289d5bc | 141 | unsigned long flags; |
6df9d38f | 142 | |
c289d5bc | 143 | spin_lock_irqsave(&ps->genpd.slock, flags); |
6df9d38f HM |
144 | |
145 | dev_dbg(ps->dev, "PS 0x%x: deassert reset\n", ps->offset); | |
146 | regmap_update_bits(ps->regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_RESET, 0); | |
147 | regmap_update_bits(ps->regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_DEV_DISABLE, 0); | |
148 | ||
149 | if (ps->genpd.status == GENPD_STATE_OFF) | |
150 | dev_err(ps->dev, "PS 0x%x: RESET was deasserted while powered down\n", ps->offset); | |
151 | ||
c289d5bc | 152 | spin_unlock_irqrestore(&ps->genpd.slock, flags); |
6df9d38f HM |
153 | |
154 | return 0; | |
155 | } | |
156 | ||
157 | static int apple_pmgr_reset_reset(struct reset_controller_dev *rcdev, unsigned long id) | |
158 | { | |
159 | int ret; | |
160 | ||
161 | ret = apple_pmgr_reset_assert(rcdev, id); | |
162 | if (ret) | |
163 | return ret; | |
164 | ||
165 | usleep_range(APPLE_PMGR_RESET_TIME, 2 * APPLE_PMGR_RESET_TIME); | |
166 | ||
167 | return apple_pmgr_reset_deassert(rcdev, id); | |
168 | } | |
169 | ||
170 | static int apple_pmgr_reset_status(struct reset_controller_dev *rcdev, unsigned long id) | |
171 | { | |
172 | struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev); | |
173 | u32 reg = 0; | |
174 | ||
175 | regmap_read(ps->regmap, ps->offset, ®); | |
176 | ||
177 | return !!(reg & APPLE_PMGR_RESET); | |
178 | } | |
179 | ||
180 | const struct reset_control_ops apple_pmgr_reset_ops = { | |
181 | .assert = apple_pmgr_reset_assert, | |
182 | .deassert = apple_pmgr_reset_deassert, | |
183 | .reset = apple_pmgr_reset_reset, | |
184 | .status = apple_pmgr_reset_status, | |
185 | }; | |
186 | ||
187 | static int apple_pmgr_reset_xlate(struct reset_controller_dev *rcdev, | |
188 | const struct of_phandle_args *reset_spec) | |
189 | { | |
190 | return 0; | |
191 | } | |
192 | ||
193 | static int apple_pmgr_ps_probe(struct platform_device *pdev) | |
194 | { | |
195 | struct device *dev = &pdev->dev; | |
196 | struct device_node *node = dev->of_node; | |
197 | struct apple_pmgr_ps *ps; | |
198 | struct regmap *regmap; | |
199 | struct of_phandle_iterator it; | |
200 | int ret; | |
201 | const char *name; | |
202 | bool active; | |
203 | ||
204 | regmap = syscon_node_to_regmap(node->parent); | |
205 | if (IS_ERR(regmap)) | |
206 | return PTR_ERR(regmap); | |
207 | ||
208 | ps = devm_kzalloc(dev, sizeof(*ps), GFP_KERNEL); | |
209 | if (!ps) | |
210 | return -ENOMEM; | |
211 | ||
212 | ps->dev = dev; | |
213 | ps->regmap = regmap; | |
214 | ||
215 | ret = of_property_read_string(node, "label", &name); | |
216 | if (ret < 0) { | |
217 | dev_err(dev, "missing label property\n"); | |
218 | return ret; | |
219 | } | |
220 | ||
221 | ret = of_property_read_u32(node, "reg", &ps->offset); | |
222 | if (ret < 0) { | |
223 | dev_err(dev, "missing reg property\n"); | |
224 | return ret; | |
225 | } | |
226 | ||
c289d5bc | 227 | ps->genpd.flags |= GENPD_FLAG_IRQ_SAFE; |
6df9d38f HM |
228 | ps->genpd.name = name; |
229 | ps->genpd.power_on = apple_pmgr_ps_power_on; | |
230 | ps->genpd.power_off = apple_pmgr_ps_power_off; | |
231 | ||
cc1fe1e5 HM |
232 | ret = of_property_read_u32(node, "apple,min-state", &ps->min_state); |
233 | if (ret == 0 && ps->min_state <= APPLE_PMGR_PS_ACTIVE) | |
234 | regmap_update_bits(regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_PS_MIN, | |
235 | FIELD_PREP(APPLE_PMGR_PS_MIN, ps->min_state)); | |
236 | ||
6df9d38f HM |
237 | active = apple_pmgr_ps_is_active(ps); |
238 | if (of_property_read_bool(node, "apple,always-on")) { | |
239 | ps->genpd.flags |= GENPD_FLAG_ALWAYS_ON; | |
240 | if (!active) { | |
241 | dev_warn(dev, "always-on domain %s is not on at boot\n", name); | |
242 | /* Turn it on so pm_genpd_init does not fail */ | |
243 | active = apple_pmgr_ps_power_on(&ps->genpd) == 0; | |
244 | } | |
245 | } | |
246 | ||
247 | /* Turn on auto-PM if the domain is already on */ | |
248 | if (active) | |
249 | regmap_update_bits(regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_AUTO_ENABLE, | |
250 | APPLE_PMGR_AUTO_ENABLE); | |
251 | ||
252 | ret = pm_genpd_init(&ps->genpd, NULL, !active); | |
253 | if (ret < 0) { | |
254 | dev_err(dev, "pm_genpd_init failed\n"); | |
255 | return ret; | |
256 | } | |
257 | ||
258 | ret = of_genpd_add_provider_simple(node, &ps->genpd); | |
259 | if (ret < 0) { | |
260 | dev_err(dev, "of_genpd_add_provider_simple failed\n"); | |
261 | return ret; | |
262 | } | |
263 | ||
264 | of_for_each_phandle(&it, ret, node, "power-domains", "#power-domain-cells", -1) { | |
265 | struct of_phandle_args parent, child; | |
266 | ||
267 | parent.np = it.node; | |
268 | parent.args_count = of_phandle_iterator_args(&it, parent.args, MAX_PHANDLE_ARGS); | |
269 | child.np = node; | |
270 | child.args_count = 0; | |
271 | ret = of_genpd_add_subdomain(&parent, &child); | |
272 | ||
273 | if (ret == -EPROBE_DEFER) { | |
274 | of_node_put(parent.np); | |
275 | goto err_remove; | |
276 | } else if (ret < 0) { | |
277 | dev_err(dev, "failed to add to parent domain: %d (%s -> %s)\n", | |
278 | ret, it.node->name, node->name); | |
279 | of_node_put(parent.np); | |
280 | goto err_remove; | |
281 | } | |
282 | } | |
283 | ||
284 | /* | |
285 | * Do not participate in regular PM; parent power domains are handled via the | |
286 | * genpd hierarchy. | |
287 | */ | |
288 | pm_genpd_remove_device(dev); | |
289 | ||
290 | ps->rcdev.owner = THIS_MODULE; | |
291 | ps->rcdev.nr_resets = 1; | |
292 | ps->rcdev.ops = &apple_pmgr_reset_ops; | |
293 | ps->rcdev.of_node = dev->of_node; | |
294 | ps->rcdev.of_reset_n_cells = 0; | |
295 | ps->rcdev.of_xlate = apple_pmgr_reset_xlate; | |
296 | ||
297 | ret = devm_reset_controller_register(dev, &ps->rcdev); | |
298 | if (ret < 0) | |
299 | goto err_remove; | |
300 | ||
301 | return 0; | |
302 | err_remove: | |
303 | of_genpd_del_provider(node); | |
304 | pm_genpd_remove(&ps->genpd); | |
305 | return ret; | |
306 | } | |
307 | ||
308 | static const struct of_device_id apple_pmgr_ps_of_match[] = { | |
309 | { .compatible = "apple,pmgr-pwrstate" }, | |
310 | {} | |
311 | }; | |
312 | ||
313 | MODULE_DEVICE_TABLE(of, apple_pmgr_ps_of_match); | |
314 | ||
315 | static struct platform_driver apple_pmgr_ps_driver = { | |
316 | .probe = apple_pmgr_ps_probe, | |
317 | .driver = { | |
318 | .name = "apple-pmgr-pwrstate", | |
319 | .of_match_table = apple_pmgr_ps_of_match, | |
320 | }, | |
321 | }; | |
322 | ||
323 | MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); | |
324 | MODULE_DESCRIPTION("PMGR power state driver for Apple SoCs"); | |
325 | MODULE_LICENSE("GPL v2"); | |
326 | ||
327 | module_platform_driver(apple_pmgr_ps_driver); |