diff options
Diffstat (limited to 'drivers/soc/imx/gpc.c')
-rw-r--r-- | drivers/soc/imx/gpc.c | 489 |
1 files changed, 489 insertions, 0 deletions
diff --git a/drivers/soc/imx/gpc.c b/drivers/soc/imx/gpc.c new file mode 100644 index 000000000000..47e7aa963dbb --- /dev/null +++ b/drivers/soc/imx/gpc.c | |||
@@ -0,0 +1,489 @@ | |||
1 | /* | ||
2 | * Copyright 2015-2017 Pengutronix, Lucas Stach <kernel@pengutronix.de> | ||
3 | * Copyright 2011-2013 Freescale Semiconductor, Inc. | ||
4 | * | ||
5 | * The code contained herein is licensed under the GNU General Public | ||
6 | * License. You may obtain a copy of the GNU General Public License | ||
7 | * Version 2 or later at the following locations: | ||
8 | * | ||
9 | * http://www.opensource.org/licenses/gpl-license.html | ||
10 | * http://www.gnu.org/copyleft/gpl.html | ||
11 | */ | ||
12 | |||
13 | #include <linux/clk.h> | ||
14 | #include <linux/delay.h> | ||
15 | #include <linux/io.h> | ||
16 | #include <linux/of_device.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/pm_domain.h> | ||
19 | #include <linux/regmap.h> | ||
20 | #include <linux/regulator/consumer.h> | ||
21 | |||
22 | #define GPC_CNTR 0x000 | ||
23 | |||
24 | #define GPC_PGC_CTRL_OFFS 0x0 | ||
25 | #define GPC_PGC_PUPSCR_OFFS 0x4 | ||
26 | #define GPC_PGC_PDNSCR_OFFS 0x8 | ||
27 | #define GPC_PGC_SW2ISO_SHIFT 0x8 | ||
28 | #define GPC_PGC_SW_SHIFT 0x0 | ||
29 | |||
30 | #define GPC_PGC_GPU_PDN 0x260 | ||
31 | #define GPC_PGC_GPU_PUPSCR 0x264 | ||
32 | #define GPC_PGC_GPU_PDNSCR 0x268 | ||
33 | |||
34 | #define GPU_VPU_PUP_REQ BIT(1) | ||
35 | #define GPU_VPU_PDN_REQ BIT(0) | ||
36 | |||
37 | #define GPC_CLK_MAX 6 | ||
38 | |||
39 | #define PGC_DOMAIN_FLAG_NO_PD BIT(0) | ||
40 | |||
41 | struct imx_pm_domain { | ||
42 | struct generic_pm_domain base; | ||
43 | struct regmap *regmap; | ||
44 | struct regulator *supply; | ||
45 | struct clk *clk[GPC_CLK_MAX]; | ||
46 | int num_clks; | ||
47 | unsigned int reg_offs; | ||
48 | signed char cntr_pdn_bit; | ||
49 | unsigned int ipg_rate_mhz; | ||
50 | unsigned int flags; | ||
51 | }; | ||
52 | |||
53 | static inline struct imx_pm_domain * | ||
54 | to_imx_pm_domain(struct generic_pm_domain *genpd) | ||
55 | { | ||
56 | return container_of(genpd, struct imx_pm_domain, base); | ||
57 | } | ||
58 | |||
59 | static int imx6_pm_domain_power_off(struct generic_pm_domain *genpd) | ||
60 | { | ||
61 | struct imx_pm_domain *pd = to_imx_pm_domain(genpd); | ||
62 | int iso, iso2sw; | ||
63 | u32 val; | ||
64 | |||
65 | if (pd->flags & PGC_DOMAIN_FLAG_NO_PD) | ||
66 | return -EBUSY; | ||
67 | |||
68 | /* Read ISO and ISO2SW power down delays */ | ||
69 | regmap_read(pd->regmap, pd->reg_offs + GPC_PGC_PUPSCR_OFFS, &val); | ||
70 | iso = val & 0x3f; | ||
71 | iso2sw = (val >> 8) & 0x3f; | ||
72 | |||
73 | /* Gate off domain when powered down */ | ||
74 | regmap_update_bits(pd->regmap, pd->reg_offs + GPC_PGC_CTRL_OFFS, | ||
75 | 0x1, 0x1); | ||
76 | |||
77 | /* Request GPC to power down domain */ | ||
78 | val = BIT(pd->cntr_pdn_bit); | ||
79 | regmap_update_bits(pd->regmap, GPC_CNTR, val, val); | ||
80 | |||
81 | /* Wait ISO + ISO2SW IPG clock cycles */ | ||
82 | udelay(DIV_ROUND_UP(iso + iso2sw, pd->ipg_rate_mhz)); | ||
83 | |||
84 | if (pd->supply) | ||
85 | regulator_disable(pd->supply); | ||
86 | |||
87 | return 0; | ||
88 | } | ||
89 | |||
90 | static int imx6_pm_domain_power_on(struct generic_pm_domain *genpd) | ||
91 | { | ||
92 | struct imx_pm_domain *pd = to_imx_pm_domain(genpd); | ||
93 | int i, ret, sw, sw2iso; | ||
94 | u32 val; | ||
95 | |||
96 | if (pd->supply) { | ||
97 | ret = regulator_enable(pd->supply); | ||
98 | if (ret) { | ||
99 | pr_err("%s: failed to enable regulator: %d\n", | ||
100 | __func__, ret); | ||
101 | return ret; | ||
102 | } | ||
103 | } | ||
104 | |||
105 | /* Enable reset clocks for all devices in the domain */ | ||
106 | for (i = 0; i < pd->num_clks; i++) | ||
107 | clk_prepare_enable(pd->clk[i]); | ||
108 | |||
109 | /* Gate off domain when powered down */ | ||
110 | regmap_update_bits(pd->regmap, pd->reg_offs + GPC_PGC_CTRL_OFFS, | ||
111 | 0x1, 0x1); | ||
112 | |||
113 | /* Read ISO and ISO2SW power up delays */ | ||
114 | regmap_read(pd->regmap, pd->reg_offs + GPC_PGC_PUPSCR_OFFS, &val); | ||
115 | sw = val & 0x3f; | ||
116 | sw2iso = (val >> 8) & 0x3f; | ||
117 | |||
118 | /* Request GPC to power up domain */ | ||
119 | val = BIT(pd->cntr_pdn_bit + 1); | ||
120 | regmap_update_bits(pd->regmap, GPC_CNTR, val, val); | ||
121 | |||
122 | /* Wait ISO + ISO2SW IPG clock cycles */ | ||
123 | udelay(DIV_ROUND_UP(sw + sw2iso, pd->ipg_rate_mhz)); | ||
124 | |||
125 | /* Disable reset clocks for all devices in the domain */ | ||
126 | for (i = 0; i < pd->num_clks; i++) | ||
127 | clk_disable_unprepare(pd->clk[i]); | ||
128 | |||
129 | return 0; | ||
130 | } | ||
131 | |||
132 | static int imx_pgc_get_clocks(struct device *dev, struct imx_pm_domain *domain) | ||
133 | { | ||
134 | int i, ret; | ||
135 | |||
136 | for (i = 0; ; i++) { | ||
137 | struct clk *clk = of_clk_get(dev->of_node, i); | ||
138 | if (IS_ERR(clk)) | ||
139 | break; | ||
140 | if (i >= GPC_CLK_MAX) { | ||
141 | dev_err(dev, "more than %d clocks\n", GPC_CLK_MAX); | ||
142 | ret = -EINVAL; | ||
143 | goto clk_err; | ||
144 | } | ||
145 | domain->clk[i] = clk; | ||
146 | } | ||
147 | domain->num_clks = i; | ||
148 | |||
149 | return 0; | ||
150 | |||
151 | clk_err: | ||
152 | while (i--) | ||
153 | clk_put(domain->clk[i]); | ||
154 | |||
155 | return ret; | ||
156 | } | ||
157 | |||
158 | static void imx_pgc_put_clocks(struct imx_pm_domain *domain) | ||
159 | { | ||
160 | int i; | ||
161 | |||
162 | for (i = domain->num_clks - 1; i >= 0; i--) | ||
163 | clk_put(domain->clk[i]); | ||
164 | } | ||
165 | |||
166 | static int imx_pgc_parse_dt(struct device *dev, struct imx_pm_domain *domain) | ||
167 | { | ||
168 | /* try to get the domain supply regulator */ | ||
169 | domain->supply = devm_regulator_get_optional(dev, "power"); | ||
170 | if (IS_ERR(domain->supply)) { | ||
171 | if (PTR_ERR(domain->supply) == -ENODEV) | ||
172 | domain->supply = NULL; | ||
173 | else | ||
174 | return PTR_ERR(domain->supply); | ||
175 | } | ||
176 | |||
177 | /* try to get all clocks needed for reset propagation */ | ||
178 | return imx_pgc_get_clocks(dev, domain); | ||
179 | } | ||
180 | |||
181 | static int imx_pgc_power_domain_probe(struct platform_device *pdev) | ||
182 | { | ||
183 | struct imx_pm_domain *domain = pdev->dev.platform_data; | ||
184 | struct device *dev = &pdev->dev; | ||
185 | int ret; | ||
186 | |||
187 | /* if this PD is associated with a DT node try to parse it */ | ||
188 | if (dev->of_node) { | ||
189 | ret = imx_pgc_parse_dt(dev, domain); | ||
190 | if (ret) | ||
191 | return ret; | ||
192 | } | ||
193 | |||
194 | /* initially power on the domain */ | ||
195 | if (domain->base.power_on) | ||
196 | domain->base.power_on(&domain->base); | ||
197 | |||
198 | if (IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS)) { | ||
199 | pm_genpd_init(&domain->base, NULL, false); | ||
200 | ret = of_genpd_add_provider_simple(dev->of_node, &domain->base); | ||
201 | if (ret) | ||
202 | goto genpd_err; | ||
203 | } | ||
204 | |||
205 | device_link_add(dev, dev->parent, DL_FLAG_AUTOREMOVE); | ||
206 | |||
207 | return 0; | ||
208 | |||
209 | genpd_err: | ||
210 | pm_genpd_remove(&domain->base); | ||
211 | imx_pgc_put_clocks(domain); | ||
212 | |||
213 | return ret; | ||
214 | } | ||
215 | |||
216 | static int imx_pgc_power_domain_remove(struct platform_device *pdev) | ||
217 | { | ||
218 | struct imx_pm_domain *domain = pdev->dev.platform_data; | ||
219 | |||
220 | if (IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS)) { | ||
221 | of_genpd_del_provider(pdev->dev.of_node); | ||
222 | pm_genpd_remove(&domain->base); | ||
223 | imx_pgc_put_clocks(domain); | ||
224 | } | ||
225 | |||
226 | return 0; | ||
227 | } | ||
228 | |||
229 | static const struct platform_device_id imx_pgc_power_domain_id[] = { | ||
230 | { "imx-pgc-power-domain"}, | ||
231 | { }, | ||
232 | }; | ||
233 | |||
234 | static struct platform_driver imx_pgc_power_domain_driver = { | ||
235 | .driver = { | ||
236 | .name = "imx-pgc-pd", | ||
237 | }, | ||
238 | .probe = imx_pgc_power_domain_probe, | ||
239 | .remove = imx_pgc_power_domain_remove, | ||
240 | .id_table = imx_pgc_power_domain_id, | ||
241 | }; | ||
242 | builtin_platform_driver(imx_pgc_power_domain_driver) | ||
243 | |||
244 | #define GPC_PGC_DOMAIN_ARM 0 | ||
245 | #define GPC_PGC_DOMAIN_PU 1 | ||
246 | #define GPC_PGC_DOMAIN_DISPLAY 2 | ||
247 | |||
248 | static struct genpd_power_state imx6_pm_domain_pu_state = { | ||
249 | .power_off_latency_ns = 25000, | ||
250 | .power_on_latency_ns = 2000000, | ||
251 | }; | ||
252 | |||
253 | static struct imx_pm_domain imx_gpc_domains[] = { | ||
254 | { | ||
255 | .base = { | ||
256 | .name = "ARM", | ||
257 | }, | ||
258 | }, { | ||
259 | .base = { | ||
260 | .name = "PU", | ||
261 | .power_off = imx6_pm_domain_power_off, | ||
262 | .power_on = imx6_pm_domain_power_on, | ||
263 | .states = &imx6_pm_domain_pu_state, | ||
264 | .state_count = 1, | ||
265 | }, | ||
266 | .reg_offs = 0x260, | ||
267 | .cntr_pdn_bit = 0, | ||
268 | }, { | ||
269 | .base = { | ||
270 | .name = "DISPLAY", | ||
271 | .power_off = imx6_pm_domain_power_off, | ||
272 | .power_on = imx6_pm_domain_power_on, | ||
273 | }, | ||
274 | .reg_offs = 0x240, | ||
275 | .cntr_pdn_bit = 4, | ||
276 | } | ||
277 | }; | ||
278 | |||
279 | struct imx_gpc_dt_data { | ||
280 | int num_domains; | ||
281 | bool err009619_present; | ||
282 | }; | ||
283 | |||
284 | static const struct imx_gpc_dt_data imx6q_dt_data = { | ||
285 | .num_domains = 2, | ||
286 | .err009619_present = false, | ||
287 | }; | ||
288 | |||
289 | static const struct imx_gpc_dt_data imx6qp_dt_data = { | ||
290 | .num_domains = 2, | ||
291 | .err009619_present = true, | ||
292 | }; | ||
293 | |||
294 | static const struct imx_gpc_dt_data imx6sl_dt_data = { | ||
295 | .num_domains = 3, | ||
296 | .err009619_present = false, | ||
297 | }; | ||
298 | |||
299 | static const struct of_device_id imx_gpc_dt_ids[] = { | ||
300 | { .compatible = "fsl,imx6q-gpc", .data = &imx6q_dt_data }, | ||
301 | { .compatible = "fsl,imx6qp-gpc", .data = &imx6qp_dt_data }, | ||
302 | { .compatible = "fsl,imx6sl-gpc", .data = &imx6sl_dt_data }, | ||
303 | { } | ||
304 | }; | ||
305 | |||
306 | static const struct regmap_config imx_gpc_regmap_config = { | ||
307 | .reg_bits = 32, | ||
308 | .val_bits = 32, | ||
309 | .reg_stride = 4, | ||
310 | .max_register = 0x2ac, | ||
311 | }; | ||
312 | |||
313 | static struct generic_pm_domain *imx_gpc_onecell_domains[] = { | ||
314 | &imx_gpc_domains[0].base, | ||
315 | &imx_gpc_domains[1].base, | ||
316 | }; | ||
317 | |||
318 | static struct genpd_onecell_data imx_gpc_onecell_data = { | ||
319 | .domains = imx_gpc_onecell_domains, | ||
320 | .num_domains = 2, | ||
321 | }; | ||
322 | |||
323 | static int imx_gpc_old_dt_init(struct device *dev, struct regmap *regmap, | ||
324 | unsigned int num_domains) | ||
325 | { | ||
326 | struct imx_pm_domain *domain; | ||
327 | int i, ret; | ||
328 | |||
329 | for (i = 0; i < num_domains; i++) { | ||
330 | domain = &imx_gpc_domains[i]; | ||
331 | domain->regmap = regmap; | ||
332 | domain->ipg_rate_mhz = 66; | ||
333 | |||
334 | if (i == 1) { | ||
335 | domain->supply = devm_regulator_get(dev, "pu"); | ||
336 | if (IS_ERR(domain->supply)) | ||
337 | return PTR_ERR(domain->supply);; | ||
338 | |||
339 | ret = imx_pgc_get_clocks(dev, domain); | ||
340 | if (ret) | ||
341 | goto clk_err; | ||
342 | |||
343 | domain->base.power_on(&domain->base); | ||
344 | } | ||
345 | } | ||
346 | |||
347 | for (i = 0; i < num_domains; i++) | ||
348 | pm_genpd_init(&imx_gpc_domains[i].base, NULL, false); | ||
349 | |||
350 | if (IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS)) { | ||
351 | ret = of_genpd_add_provider_onecell(dev->of_node, | ||
352 | &imx_gpc_onecell_data); | ||
353 | if (ret) | ||
354 | goto genpd_err; | ||
355 | } | ||
356 | |||
357 | return 0; | ||
358 | |||
359 | genpd_err: | ||
360 | for (i = 0; i < num_domains; i++) | ||
361 | pm_genpd_remove(&imx_gpc_domains[i].base); | ||
362 | imx_pgc_put_clocks(&imx_gpc_domains[GPC_PGC_DOMAIN_PU]); | ||
363 | clk_err: | ||
364 | return ret; | ||
365 | } | ||
366 | |||
367 | static int imx_gpc_probe(struct platform_device *pdev) | ||
368 | { | ||
369 | const struct of_device_id *of_id = | ||
370 | of_match_device(imx_gpc_dt_ids, &pdev->dev); | ||
371 | const struct imx_gpc_dt_data *of_id_data = of_id->data; | ||
372 | struct device_node *pgc_node; | ||
373 | struct regmap *regmap; | ||
374 | struct resource *res; | ||
375 | void __iomem *base; | ||
376 | int ret; | ||
377 | |||
378 | pgc_node = of_get_child_by_name(pdev->dev.of_node, "pgc"); | ||
379 | |||
380 | /* bail out if DT too old and doesn't provide the necessary info */ | ||
381 | if (!of_property_read_bool(pdev->dev.of_node, "#power-domain-cells") && | ||
382 | !pgc_node) | ||
383 | return 0; | ||
384 | |||
385 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
386 | base = devm_ioremap_resource(&pdev->dev, res); | ||
387 | if (IS_ERR(base)) | ||
388 | return PTR_ERR(base); | ||
389 | |||
390 | regmap = devm_regmap_init_mmio_clk(&pdev->dev, NULL, base, | ||
391 | &imx_gpc_regmap_config); | ||
392 | if (IS_ERR(regmap)) { | ||
393 | ret = PTR_ERR(regmap); | ||
394 | dev_err(&pdev->dev, "failed to init regmap: %d\n", | ||
395 | ret); | ||
396 | return ret; | ||
397 | } | ||
398 | |||
399 | /* Disable PU power down in normal operation if ERR009619 is present */ | ||
400 | if (of_id_data->err009619_present) | ||
401 | imx_gpc_domains[GPC_PGC_DOMAIN_PU].flags |= | ||
402 | PGC_DOMAIN_FLAG_NO_PD; | ||
403 | |||
404 | if (!pgc_node) { | ||
405 | ret = imx_gpc_old_dt_init(&pdev->dev, regmap, | ||
406 | of_id_data->num_domains); | ||
407 | if (ret) | ||
408 | return ret; | ||
409 | } else { | ||
410 | struct imx_pm_domain *domain; | ||
411 | struct platform_device *pd_pdev; | ||
412 | struct device_node *np; | ||
413 | struct clk *ipg_clk; | ||
414 | unsigned int ipg_rate_mhz; | ||
415 | int domain_index; | ||
416 | |||
417 | ipg_clk = devm_clk_get(&pdev->dev, "ipg"); | ||
418 | if (IS_ERR(ipg_clk)) | ||
419 | return PTR_ERR(ipg_clk); | ||
420 | ipg_rate_mhz = clk_get_rate(ipg_clk) / 1000000; | ||
421 | |||
422 | for_each_child_of_node(pgc_node, np) { | ||
423 | ret = of_property_read_u32(np, "reg", &domain_index); | ||
424 | if (ret) { | ||
425 | of_node_put(np); | ||
426 | return ret; | ||
427 | } | ||
428 | if (domain_index >= of_id_data->num_domains) | ||
429 | continue; | ||
430 | |||
431 | domain = &imx_gpc_domains[domain_index]; | ||
432 | domain->regmap = regmap; | ||
433 | domain->ipg_rate_mhz = ipg_rate_mhz; | ||
434 | |||
435 | pd_pdev = platform_device_alloc("imx-pgc-power-domain", | ||
436 | domain_index); | ||
437 | if (!pd_pdev) { | ||
438 | of_node_put(np); | ||
439 | return -ENOMEM; | ||
440 | } | ||
441 | pd_pdev->dev.platform_data = domain; | ||
442 | pd_pdev->dev.parent = &pdev->dev; | ||
443 | pd_pdev->dev.of_node = np; | ||
444 | |||
445 | ret = platform_device_add(pd_pdev); | ||
446 | if (ret) { | ||
447 | platform_device_put(pd_pdev); | ||
448 | of_node_put(np); | ||
449 | return ret; | ||
450 | } | ||
451 | } | ||
452 | } | ||
453 | |||
454 | return 0; | ||
455 | } | ||
456 | |||
457 | static int imx_gpc_remove(struct platform_device *pdev) | ||
458 | { | ||
459 | int ret; | ||
460 | |||
461 | /* | ||
462 | * If the old DT binding is used the toplevel driver needs to | ||
463 | * de-register the power domains | ||
464 | */ | ||
465 | if (!of_get_child_by_name(pdev->dev.of_node, "pgc")) { | ||
466 | of_genpd_del_provider(pdev->dev.of_node); | ||
467 | |||
468 | ret = pm_genpd_remove(&imx_gpc_domains[GPC_PGC_DOMAIN_PU].base); | ||
469 | if (ret) | ||
470 | return ret; | ||
471 | imx_pgc_put_clocks(&imx_gpc_domains[GPC_PGC_DOMAIN_PU]); | ||
472 | |||
473 | ret = pm_genpd_remove(&imx_gpc_domains[GPC_PGC_DOMAIN_ARM].base); | ||
474 | if (ret) | ||
475 | return ret; | ||
476 | } | ||
477 | |||
478 | return 0; | ||
479 | } | ||
480 | |||
481 | static struct platform_driver imx_gpc_driver = { | ||
482 | .driver = { | ||
483 | .name = "imx-gpc", | ||
484 | .of_match_table = imx_gpc_dt_ids, | ||
485 | }, | ||
486 | .probe = imx_gpc_probe, | ||
487 | .remove = imx_gpc_remove, | ||
488 | }; | ||
489 | builtin_platform_driver(imx_gpc_driver) | ||