Commit | Line | Data |
---|---|---|
2684ac05 LS |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | ||
3 | /* | |
4 | * Copyright 2021 Pengutronix, Lucas Stach <kernel@pengutronix.de> | |
5 | */ | |
6 | ||
06a9a229 | 7 | #include <linux/bitfield.h> |
2684ac05 | 8 | #include <linux/device.h> |
c553ca92 | 9 | #include <linux/interconnect.h> |
2684ac05 LS |
10 | #include <linux/module.h> |
11 | #include <linux/of_device.h> | |
12 | #include <linux/platform_device.h> | |
13 | #include <linux/pm_domain.h> | |
14 | #include <linux/pm_runtime.h> | |
15 | #include <linux/regmap.h> | |
16 | #include <linux/clk.h> | |
17 | ||
18 | #include <dt-bindings/power/imx8mm-power.h> | |
7f511d51 | 19 | #include <dt-bindings/power/imx8mn-power.h> |
07614fed | 20 | #include <dt-bindings/power/imx8mp-power.h> |
608d7c32 | 21 | #include <dt-bindings/power/imx8mq-power.h> |
2684ac05 LS |
22 | |
23 | #define BLK_SFT_RSTN 0x0 | |
24 | #define BLK_CLK_EN 0x4 | |
07614fed | 25 | #define BLK_MIPI_RESET_DIV 0x8 /* Mini/Nano/Plus DISPLAY_BLK_CTRL only */ |
2684ac05 LS |
26 | |
27 | struct imx8m_blk_ctrl_domain; | |
28 | ||
29 | struct imx8m_blk_ctrl { | |
30 | struct device *dev; | |
31 | struct notifier_block power_nb; | |
32 | struct device *bus_power_dev; | |
33 | struct regmap *regmap; | |
34 | struct imx8m_blk_ctrl_domain *domains; | |
35 | struct genpd_onecell_data onecell_data; | |
36 | }; | |
37 | ||
38 | struct imx8m_blk_ctrl_domain_data { | |
39 | const char *name; | |
40 | const char * const *clk_names; | |
c553ca92 | 41 | const char * const *path_names; |
2684ac05 | 42 | const char *gpc_name; |
816aec03 PF |
43 | int num_clks; |
44 | int num_paths; | |
2684ac05 LS |
45 | u32 rst_mask; |
46 | u32 clk_mask; | |
042b6779 AF |
47 | |
48 | /* | |
07614fed | 49 | * i.MX8M Mini, Nano and Plus have a third DISPLAY_BLK_CTRL register |
042b6779 AF |
50 | * which is used to control the reset for the MIPI Phy. |
51 | * Since it's only present in certain circumstances, | |
52 | * an if-statement should be used before setting and clearing this | |
53 | * register. | |
54 | */ | |
55 | u32 mipi_phy_rst_mask; | |
2684ac05 LS |
56 | }; |
57 | ||
e2aa165c | 58 | #define DOMAIN_MAX_CLKS 4 |
c553ca92 | 59 | #define DOMAIN_MAX_PATHS 4 |
2684ac05 LS |
60 | |
61 | struct imx8m_blk_ctrl_domain { | |
62 | struct generic_pm_domain genpd; | |
63 | const struct imx8m_blk_ctrl_domain_data *data; | |
64 | struct clk_bulk_data clks[DOMAIN_MAX_CLKS]; | |
c553ca92 | 65 | struct icc_bulk_data paths[DOMAIN_MAX_PATHS]; |
2684ac05 LS |
66 | struct device *power_dev; |
67 | struct imx8m_blk_ctrl *bc; | |
c553ca92 | 68 | int num_paths; |
2684ac05 LS |
69 | }; |
70 | ||
71 | struct imx8m_blk_ctrl_data { | |
72 | int max_reg; | |
73 | notifier_fn_t power_notifier_fn; | |
74 | const struct imx8m_blk_ctrl_domain_data *domains; | |
75 | int num_domains; | |
76 | }; | |
77 | ||
78 | static inline struct imx8m_blk_ctrl_domain * | |
79 | to_imx8m_blk_ctrl_domain(struct generic_pm_domain *genpd) | |
80 | { | |
81 | return container_of(genpd, struct imx8m_blk_ctrl_domain, genpd); | |
82 | } | |
83 | ||
84 | static int imx8m_blk_ctrl_power_on(struct generic_pm_domain *genpd) | |
85 | { | |
86 | struct imx8m_blk_ctrl_domain *domain = to_imx8m_blk_ctrl_domain(genpd); | |
87 | const struct imx8m_blk_ctrl_domain_data *data = domain->data; | |
88 | struct imx8m_blk_ctrl *bc = domain->bc; | |
89 | int ret; | |
90 | ||
91 | /* make sure bus domain is awake */ | |
92 | ret = pm_runtime_get_sync(bc->bus_power_dev); | |
93 | if (ret < 0) { | |
94 | pm_runtime_put_noidle(bc->bus_power_dev); | |
95 | dev_err(bc->dev, "failed to power up bus domain\n"); | |
96 | return ret; | |
97 | } | |
98 | ||
99 | /* put devices into reset */ | |
100 | regmap_clear_bits(bc->regmap, BLK_SFT_RSTN, data->rst_mask); | |
042b6779 AF |
101 | if (data->mipi_phy_rst_mask) |
102 | regmap_clear_bits(bc->regmap, BLK_MIPI_RESET_DIV, data->mipi_phy_rst_mask); | |
2684ac05 LS |
103 | |
104 | /* enable upstream and blk-ctrl clocks to allow reset to propagate */ | |
105 | ret = clk_bulk_prepare_enable(data->num_clks, domain->clks); | |
106 | if (ret) { | |
107 | dev_err(bc->dev, "failed to enable clocks\n"); | |
108 | goto bus_put; | |
109 | } | |
110 | regmap_set_bits(bc->regmap, BLK_CLK_EN, data->clk_mask); | |
111 | ||
112 | /* power up upstream GPC domain */ | |
113 | ret = pm_runtime_get_sync(domain->power_dev); | |
114 | if (ret < 0) { | |
115 | dev_err(bc->dev, "failed to power up peripheral domain\n"); | |
116 | goto clk_disable; | |
117 | } | |
118 | ||
119 | /* wait for reset to propagate */ | |
120 | udelay(5); | |
121 | ||
122 | /* release reset */ | |
123 | regmap_set_bits(bc->regmap, BLK_SFT_RSTN, data->rst_mask); | |
042b6779 AF |
124 | if (data->mipi_phy_rst_mask) |
125 | regmap_set_bits(bc->regmap, BLK_MIPI_RESET_DIV, data->mipi_phy_rst_mask); | |
2684ac05 | 126 | |
c553ca92 PF |
127 | ret = icc_bulk_set_bw(domain->num_paths, domain->paths); |
128 | if (ret) | |
129 | dev_err(bc->dev, "failed to set icc bw\n"); | |
130 | ||
2684ac05 LS |
131 | /* disable upstream clocks */ |
132 | clk_bulk_disable_unprepare(data->num_clks, domain->clks); | |
133 | ||
134 | return 0; | |
135 | ||
136 | clk_disable: | |
137 | clk_bulk_disable_unprepare(data->num_clks, domain->clks); | |
138 | bus_put: | |
139 | pm_runtime_put(bc->bus_power_dev); | |
140 | ||
141 | return ret; | |
142 | } | |
143 | ||
144 | static int imx8m_blk_ctrl_power_off(struct generic_pm_domain *genpd) | |
145 | { | |
146 | struct imx8m_blk_ctrl_domain *domain = to_imx8m_blk_ctrl_domain(genpd); | |
147 | const struct imx8m_blk_ctrl_domain_data *data = domain->data; | |
148 | struct imx8m_blk_ctrl *bc = domain->bc; | |
149 | ||
150 | /* put devices into reset and disable clocks */ | |
042b6779 AF |
151 | if (data->mipi_phy_rst_mask) |
152 | regmap_clear_bits(bc->regmap, BLK_MIPI_RESET_DIV, data->mipi_phy_rst_mask); | |
153 | ||
2684ac05 LS |
154 | regmap_clear_bits(bc->regmap, BLK_SFT_RSTN, data->rst_mask); |
155 | regmap_clear_bits(bc->regmap, BLK_CLK_EN, data->clk_mask); | |
156 | ||
157 | /* power down upstream GPC domain */ | |
158 | pm_runtime_put(domain->power_dev); | |
159 | ||
160 | /* allow bus domain to suspend */ | |
161 | pm_runtime_put(bc->bus_power_dev); | |
162 | ||
163 | return 0; | |
164 | } | |
165 | ||
2684ac05 LS |
166 | static struct lock_class_key blk_ctrl_genpd_lock_class; |
167 | ||
168 | static int imx8m_blk_ctrl_probe(struct platform_device *pdev) | |
169 | { | |
170 | const struct imx8m_blk_ctrl_data *bc_data; | |
171 | struct device *dev = &pdev->dev; | |
172 | struct imx8m_blk_ctrl *bc; | |
173 | void __iomem *base; | |
174 | int i, ret; | |
175 | ||
176 | struct regmap_config regmap_config = { | |
177 | .reg_bits = 32, | |
178 | .val_bits = 32, | |
179 | .reg_stride = 4, | |
180 | }; | |
181 | ||
182 | bc = devm_kzalloc(dev, sizeof(*bc), GFP_KERNEL); | |
183 | if (!bc) | |
184 | return -ENOMEM; | |
185 | ||
186 | bc->dev = dev; | |
187 | ||
188 | bc_data = of_device_get_match_data(dev); | |
189 | ||
190 | base = devm_platform_ioremap_resource(pdev, 0); | |
191 | if (IS_ERR(base)) | |
192 | return PTR_ERR(base); | |
193 | ||
194 | regmap_config.max_register = bc_data->max_reg; | |
195 | bc->regmap = devm_regmap_init_mmio(dev, base, ®map_config); | |
196 | if (IS_ERR(bc->regmap)) | |
197 | return dev_err_probe(dev, PTR_ERR(bc->regmap), | |
198 | "failed to init regmap\n"); | |
199 | ||
200 | bc->domains = devm_kcalloc(dev, bc_data->num_domains, | |
201 | sizeof(struct imx8m_blk_ctrl_domain), | |
202 | GFP_KERNEL); | |
203 | if (!bc->domains) | |
204 | return -ENOMEM; | |
205 | ||
206 | bc->onecell_data.num_domains = bc_data->num_domains; | |
2684ac05 LS |
207 | bc->onecell_data.domains = |
208 | devm_kcalloc(dev, bc_data->num_domains, | |
209 | sizeof(struct generic_pm_domain *), GFP_KERNEL); | |
210 | if (!bc->onecell_data.domains) | |
211 | return -ENOMEM; | |
212 | ||
31ef51a4 | 213 | bc->bus_power_dev = dev_pm_domain_attach_by_name(dev, "bus"); |
1a1da285 BG |
214 | if (IS_ERR(bc->bus_power_dev)) { |
215 | if (PTR_ERR(bc->bus_power_dev) == -ENODEV) | |
216 | return dev_err_probe(dev, -EPROBE_DEFER, | |
217 | "failed to attach power domain \"bus\"\n"); | |
218 | else | |
219 | return dev_err_probe(dev, PTR_ERR(bc->bus_power_dev), | |
220 | "failed to attach power domain \"bus\"\n"); | |
221 | } | |
2684ac05 LS |
222 | |
223 | for (i = 0; i < bc_data->num_domains; i++) { | |
224 | const struct imx8m_blk_ctrl_domain_data *data = &bc_data->domains[i]; | |
225 | struct imx8m_blk_ctrl_domain *domain = &bc->domains[i]; | |
226 | int j; | |
227 | ||
228 | domain->data = data; | |
c553ca92 | 229 | domain->num_paths = data->num_paths; |
2684ac05 LS |
230 | |
231 | for (j = 0; j < data->num_clks; j++) | |
232 | domain->clks[j].id = data->clk_names[j]; | |
233 | ||
c553ca92 PF |
234 | for (j = 0; j < data->num_paths; j++) { |
235 | domain->paths[j].name = data->path_names[j]; | |
236 | /* Fake value for now, just let ICC could configure NoC mode/priority */ | |
237 | domain->paths[j].avg_bw = 1; | |
238 | domain->paths[j].peak_bw = 1; | |
239 | } | |
240 | ||
241 | ret = devm_of_icc_bulk_get(dev, data->num_paths, domain->paths); | |
242 | if (ret) { | |
243 | if (ret != -EPROBE_DEFER) { | |
244 | dev_warn_once(dev, "Could not get interconnect paths, NoC will stay unconfigured!\n"); | |
245 | domain->num_paths = 0; | |
246 | } else { | |
247 | dev_err_probe(dev, ret, "failed to get noc entries\n"); | |
248 | goto cleanup_pds; | |
249 | } | |
250 | } | |
251 | ||
2684ac05 LS |
252 | ret = devm_clk_bulk_get(dev, data->num_clks, domain->clks); |
253 | if (ret) { | |
254 | dev_err_probe(dev, ret, "failed to get clock\n"); | |
255 | goto cleanup_pds; | |
256 | } | |
257 | ||
258 | domain->power_dev = | |
259 | dev_pm_domain_attach_by_name(dev, data->gpc_name); | |
260 | if (IS_ERR(domain->power_dev)) { | |
261 | dev_err_probe(dev, PTR_ERR(domain->power_dev), | |
1ec32a4f MV |
262 | "failed to attach power domain \"%s\"\n", |
263 | data->gpc_name); | |
2684ac05 LS |
264 | ret = PTR_ERR(domain->power_dev); |
265 | goto cleanup_pds; | |
266 | } | |
267 | ||
268 | domain->genpd.name = data->name; | |
269 | domain->genpd.power_on = imx8m_blk_ctrl_power_on; | |
270 | domain->genpd.power_off = imx8m_blk_ctrl_power_off; | |
271 | domain->bc = bc; | |
272 | ||
273 | ret = pm_genpd_init(&domain->genpd, NULL, true); | |
274 | if (ret) { | |
1ec32a4f MV |
275 | dev_err_probe(dev, ret, |
276 | "failed to init power domain \"%s\"\n", | |
277 | data->gpc_name); | |
2684ac05 LS |
278 | dev_pm_domain_detach(domain->power_dev, true); |
279 | goto cleanup_pds; | |
280 | } | |
281 | ||
282 | /* | |
283 | * We use runtime PM to trigger power on/off of the upstream GPC | |
284 | * domain, as a strict hierarchical parent/child power domain | |
285 | * setup doesn't allow us to meet the sequencing requirements. | |
286 | * This means we have nested locking of genpd locks, without the | |
287 | * nesting being visible at the genpd level, so we need a | |
288 | * separate lock class to make lockdep aware of the fact that | |
289 | * this are separate domain locks that can be nested without a | |
290 | * self-deadlock. | |
291 | */ | |
292 | lockdep_set_class(&domain->genpd.mlock, | |
293 | &blk_ctrl_genpd_lock_class); | |
294 | ||
295 | bc->onecell_data.domains[i] = &domain->genpd; | |
296 | } | |
297 | ||
298 | ret = of_genpd_add_provider_onecell(dev->of_node, &bc->onecell_data); | |
299 | if (ret) { | |
300 | dev_err_probe(dev, ret, "failed to add power domain provider\n"); | |
301 | goto cleanup_pds; | |
302 | } | |
303 | ||
304 | bc->power_nb.notifier_call = bc_data->power_notifier_fn; | |
305 | ret = dev_pm_genpd_add_notifier(bc->bus_power_dev, &bc->power_nb); | |
306 | if (ret) { | |
307 | dev_err_probe(dev, ret, "failed to add power notifier\n"); | |
308 | goto cleanup_provider; | |
309 | } | |
310 | ||
311 | dev_set_drvdata(dev, bc); | |
312 | ||
9cb6d1b3 MV |
313 | ret = devm_of_platform_populate(dev); |
314 | if (ret) | |
315 | goto cleanup_provider; | |
316 | ||
2684ac05 LS |
317 | return 0; |
318 | ||
319 | cleanup_provider: | |
320 | of_genpd_del_provider(dev->of_node); | |
321 | cleanup_pds: | |
322 | for (i--; i >= 0; i--) { | |
323 | pm_genpd_remove(&bc->domains[i].genpd); | |
324 | dev_pm_domain_detach(bc->domains[i].power_dev, true); | |
325 | } | |
326 | ||
327 | dev_pm_domain_detach(bc->bus_power_dev, true); | |
328 | ||
329 | return ret; | |
330 | } | |
331 | ||
332 | static int imx8m_blk_ctrl_remove(struct platform_device *pdev) | |
333 | { | |
334 | struct imx8m_blk_ctrl *bc = dev_get_drvdata(&pdev->dev); | |
335 | int i; | |
336 | ||
337 | of_genpd_del_provider(pdev->dev.of_node); | |
338 | ||
339 | for (i = 0; bc->onecell_data.num_domains; i++) { | |
340 | struct imx8m_blk_ctrl_domain *domain = &bc->domains[i]; | |
341 | ||
342 | pm_genpd_remove(&domain->genpd); | |
343 | dev_pm_domain_detach(domain->power_dev, true); | |
344 | } | |
345 | ||
346 | dev_pm_genpd_remove_notifier(bc->bus_power_dev); | |
347 | ||
348 | dev_pm_domain_detach(bc->bus_power_dev, true); | |
349 | ||
350 | return 0; | |
351 | } | |
352 | ||
353 | #ifdef CONFIG_PM_SLEEP | |
354 | static int imx8m_blk_ctrl_suspend(struct device *dev) | |
355 | { | |
356 | struct imx8m_blk_ctrl *bc = dev_get_drvdata(dev); | |
357 | int ret, i; | |
358 | ||
359 | /* | |
360 | * This may look strange, but is done so the generic PM_SLEEP code | |
361 | * can power down our domains and more importantly power them up again | |
362 | * after resume, without tripping over our usage of runtime PM to | |
363 | * control the upstream GPC domains. Things happen in the right order | |
364 | * in the system suspend/resume paths due to the device parent/child | |
365 | * hierarchy. | |
366 | */ | |
367 | ret = pm_runtime_get_sync(bc->bus_power_dev); | |
368 | if (ret < 0) { | |
369 | pm_runtime_put_noidle(bc->bus_power_dev); | |
370 | return ret; | |
371 | } | |
372 | ||
373 | for (i = 0; i < bc->onecell_data.num_domains; i++) { | |
374 | struct imx8m_blk_ctrl_domain *domain = &bc->domains[i]; | |
375 | ||
376 | ret = pm_runtime_get_sync(domain->power_dev); | |
377 | if (ret < 0) { | |
378 | pm_runtime_put_noidle(domain->power_dev); | |
379 | goto out_fail; | |
380 | } | |
381 | } | |
382 | ||
383 | return 0; | |
384 | ||
385 | out_fail: | |
386 | for (i--; i >= 0; i--) | |
387 | pm_runtime_put(bc->domains[i].power_dev); | |
388 | ||
389 | pm_runtime_put(bc->bus_power_dev); | |
390 | ||
391 | return ret; | |
392 | } | |
393 | ||
394 | static int imx8m_blk_ctrl_resume(struct device *dev) | |
395 | { | |
396 | struct imx8m_blk_ctrl *bc = dev_get_drvdata(dev); | |
397 | int i; | |
398 | ||
399 | for (i = 0; i < bc->onecell_data.num_domains; i++) | |
400 | pm_runtime_put(bc->domains[i].power_dev); | |
401 | ||
402 | pm_runtime_put(bc->bus_power_dev); | |
403 | ||
404 | return 0; | |
405 | } | |
406 | #endif | |
407 | ||
408 | static const struct dev_pm_ops imx8m_blk_ctrl_pm_ops = { | |
409 | SET_SYSTEM_SLEEP_PM_OPS(imx8m_blk_ctrl_suspend, imx8m_blk_ctrl_resume) | |
410 | }; | |
411 | ||
412 | static int imx8mm_vpu_power_notifier(struct notifier_block *nb, | |
413 | unsigned long action, void *data) | |
414 | { | |
415 | struct imx8m_blk_ctrl *bc = container_of(nb, struct imx8m_blk_ctrl, | |
416 | power_nb); | |
417 | ||
418 | if (action != GENPD_NOTIFY_ON && action != GENPD_NOTIFY_PRE_OFF) | |
419 | return NOTIFY_OK; | |
420 | ||
421 | /* | |
422 | * The ADB in the VPUMIX domain has no separate reset and clock | |
423 | * enable bits, but is ungated together with the VPU clocks. To | |
424 | * allow the handshake with the GPC to progress we put the VPUs | |
425 | * in reset and ungate the clocks. | |
426 | */ | |
427 | regmap_clear_bits(bc->regmap, BLK_SFT_RSTN, BIT(0) | BIT(1) | BIT(2)); | |
428 | regmap_set_bits(bc->regmap, BLK_CLK_EN, BIT(0) | BIT(1) | BIT(2)); | |
429 | ||
430 | if (action == GENPD_NOTIFY_ON) { | |
431 | /* | |
432 | * On power up we have no software backchannel to the GPC to | |
433 | * wait for the ADB handshake to happen, so we just delay for a | |
434 | * bit. On power down the GPC driver waits for the handshake. | |
435 | */ | |
436 | udelay(5); | |
437 | ||
438 | /* set "fuse" bits to enable the VPUs */ | |
439 | regmap_set_bits(bc->regmap, 0x8, 0xffffffff); | |
440 | regmap_set_bits(bc->regmap, 0xc, 0xffffffff); | |
441 | regmap_set_bits(bc->regmap, 0x10, 0xffffffff); | |
442 | regmap_set_bits(bc->regmap, 0x14, 0xffffffff); | |
443 | } | |
444 | ||
445 | return NOTIFY_OK; | |
446 | } | |
447 | ||
448 | static const struct imx8m_blk_ctrl_domain_data imx8mm_vpu_blk_ctl_domain_data[] = { | |
449 | [IMX8MM_VPUBLK_PD_G1] = { | |
450 | .name = "vpublk-g1", | |
451 | .clk_names = (const char *[]){ "g1", }, | |
452 | .num_clks = 1, | |
453 | .gpc_name = "g1", | |
454 | .rst_mask = BIT(1), | |
455 | .clk_mask = BIT(1), | |
456 | }, | |
457 | [IMX8MM_VPUBLK_PD_G2] = { | |
458 | .name = "vpublk-g2", | |
459 | .clk_names = (const char *[]){ "g2", }, | |
460 | .num_clks = 1, | |
461 | .gpc_name = "g2", | |
462 | .rst_mask = BIT(0), | |
463 | .clk_mask = BIT(0), | |
464 | }, | |
465 | [IMX8MM_VPUBLK_PD_H1] = { | |
466 | .name = "vpublk-h1", | |
467 | .clk_names = (const char *[]){ "h1", }, | |
468 | .num_clks = 1, | |
469 | .gpc_name = "h1", | |
470 | .rst_mask = BIT(2), | |
471 | .clk_mask = BIT(2), | |
472 | }, | |
473 | }; | |
474 | ||
475 | static const struct imx8m_blk_ctrl_data imx8mm_vpu_blk_ctl_dev_data = { | |
476 | .max_reg = 0x18, | |
477 | .power_notifier_fn = imx8mm_vpu_power_notifier, | |
478 | .domains = imx8mm_vpu_blk_ctl_domain_data, | |
479 | .num_domains = ARRAY_SIZE(imx8mm_vpu_blk_ctl_domain_data), | |
480 | }; | |
481 | ||
a1a5f15f PF |
482 | static const struct imx8m_blk_ctrl_domain_data imx8mp_vpu_blk_ctl_domain_data[] = { |
483 | [IMX8MP_VPUBLK_PD_G1] = { | |
484 | .name = "vpublk-g1", | |
485 | .clk_names = (const char *[]){ "g1", }, | |
486 | .num_clks = 1, | |
487 | .gpc_name = "g1", | |
488 | .rst_mask = BIT(1), | |
489 | .clk_mask = BIT(1), | |
490 | .path_names = (const char *[]){"g1"}, | |
491 | .num_paths = 1, | |
492 | }, | |
493 | [IMX8MP_VPUBLK_PD_G2] = { | |
494 | .name = "vpublk-g2", | |
495 | .clk_names = (const char *[]){ "g2", }, | |
496 | .num_clks = 1, | |
497 | .gpc_name = "g2", | |
498 | .rst_mask = BIT(0), | |
499 | .clk_mask = BIT(0), | |
500 | .path_names = (const char *[]){"g2"}, | |
501 | .num_paths = 1, | |
502 | }, | |
503 | [IMX8MP_VPUBLK_PD_VC8000E] = { | |
504 | .name = "vpublk-vc8000e", | |
505 | .clk_names = (const char *[]){ "vc8000e", }, | |
506 | .num_clks = 1, | |
507 | .gpc_name = "vc8000e", | |
508 | .rst_mask = BIT(2), | |
509 | .clk_mask = BIT(2), | |
510 | .path_names = (const char *[]){"vc8000e"}, | |
511 | .num_paths = 1, | |
512 | }, | |
513 | }; | |
514 | ||
515 | static const struct imx8m_blk_ctrl_data imx8mp_vpu_blk_ctl_dev_data = { | |
516 | .max_reg = 0x18, | |
517 | .power_notifier_fn = imx8mm_vpu_power_notifier, | |
518 | .domains = imx8mp_vpu_blk_ctl_domain_data, | |
519 | .num_domains = ARRAY_SIZE(imx8mp_vpu_blk_ctl_domain_data), | |
520 | }; | |
521 | ||
926e57c0 LS |
522 | static int imx8mm_disp_power_notifier(struct notifier_block *nb, |
523 | unsigned long action, void *data) | |
524 | { | |
525 | struct imx8m_blk_ctrl *bc = container_of(nb, struct imx8m_blk_ctrl, | |
526 | power_nb); | |
527 | ||
528 | if (action != GENPD_NOTIFY_ON && action != GENPD_NOTIFY_PRE_OFF) | |
529 | return NOTIFY_OK; | |
530 | ||
531 | /* Enable bus clock and deassert bus reset */ | |
532 | regmap_set_bits(bc->regmap, BLK_CLK_EN, BIT(12)); | |
533 | regmap_set_bits(bc->regmap, BLK_SFT_RSTN, BIT(6)); | |
534 | ||
535 | /* | |
536 | * On power up we have no software backchannel to the GPC to | |
537 | * wait for the ADB handshake to happen, so we just delay for a | |
538 | * bit. On power down the GPC driver waits for the handshake. | |
539 | */ | |
540 | if (action == GENPD_NOTIFY_ON) | |
541 | udelay(5); | |
542 | ||
543 | ||
544 | return NOTIFY_OK; | |
545 | } | |
546 | ||
547 | static const struct imx8m_blk_ctrl_domain_data imx8mm_disp_blk_ctl_domain_data[] = { | |
548 | [IMX8MM_DISPBLK_PD_CSI_BRIDGE] = { | |
549 | .name = "dispblk-csi-bridge", | |
550 | .clk_names = (const char *[]){ "csi-bridge-axi", "csi-bridge-apb", | |
551 | "csi-bridge-core", }, | |
552 | .num_clks = 3, | |
553 | .gpc_name = "csi-bridge", | |
554 | .rst_mask = BIT(0) | BIT(1) | BIT(2), | |
555 | .clk_mask = BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5), | |
556 | }, | |
557 | [IMX8MM_DISPBLK_PD_LCDIF] = { | |
558 | .name = "dispblk-lcdif", | |
559 | .clk_names = (const char *[]){ "lcdif-axi", "lcdif-apb", "lcdif-pix", }, | |
560 | .num_clks = 3, | |
561 | .gpc_name = "lcdif", | |
562 | .clk_mask = BIT(6) | BIT(7), | |
563 | }, | |
564 | [IMX8MM_DISPBLK_PD_MIPI_DSI] = { | |
565 | .name = "dispblk-mipi-dsi", | |
566 | .clk_names = (const char *[]){ "dsi-pclk", "dsi-ref", }, | |
567 | .num_clks = 2, | |
568 | .gpc_name = "mipi-dsi", | |
569 | .rst_mask = BIT(5), | |
570 | .clk_mask = BIT(8) | BIT(9), | |
042b6779 | 571 | .mipi_phy_rst_mask = BIT(17), |
926e57c0 LS |
572 | }, |
573 | [IMX8MM_DISPBLK_PD_MIPI_CSI] = { | |
574 | .name = "dispblk-mipi-csi", | |
575 | .clk_names = (const char *[]){ "csi-aclk", "csi-pclk" }, | |
576 | .num_clks = 2, | |
577 | .gpc_name = "mipi-csi", | |
578 | .rst_mask = BIT(3) | BIT(4), | |
579 | .clk_mask = BIT(10) | BIT(11), | |
042b6779 | 580 | .mipi_phy_rst_mask = BIT(16), |
926e57c0 LS |
581 | }, |
582 | }; | |
583 | ||
584 | static const struct imx8m_blk_ctrl_data imx8mm_disp_blk_ctl_dev_data = { | |
585 | .max_reg = 0x2c, | |
586 | .power_notifier_fn = imx8mm_disp_power_notifier, | |
587 | .domains = imx8mm_disp_blk_ctl_domain_data, | |
588 | .num_domains = ARRAY_SIZE(imx8mm_disp_blk_ctl_domain_data), | |
589 | }; | |
590 | ||
7f511d51 AF |
591 | |
592 | static int imx8mn_disp_power_notifier(struct notifier_block *nb, | |
593 | unsigned long action, void *data) | |
594 | { | |
595 | struct imx8m_blk_ctrl *bc = container_of(nb, struct imx8m_blk_ctrl, | |
596 | power_nb); | |
597 | ||
598 | if (action != GENPD_NOTIFY_ON && action != GENPD_NOTIFY_PRE_OFF) | |
599 | return NOTIFY_OK; | |
600 | ||
601 | /* Enable bus clock and deassert bus reset */ | |
602 | regmap_set_bits(bc->regmap, BLK_CLK_EN, BIT(8)); | |
603 | regmap_set_bits(bc->regmap, BLK_SFT_RSTN, BIT(8)); | |
604 | ||
605 | /* | |
606 | * On power up we have no software backchannel to the GPC to | |
607 | * wait for the ADB handshake to happen, so we just delay for a | |
608 | * bit. On power down the GPC driver waits for the handshake. | |
609 | */ | |
610 | if (action == GENPD_NOTIFY_ON) | |
611 | udelay(5); | |
612 | ||
613 | ||
614 | return NOTIFY_OK; | |
615 | } | |
616 | ||
617 | static const struct imx8m_blk_ctrl_domain_data imx8mn_disp_blk_ctl_domain_data[] = { | |
618 | [IMX8MN_DISPBLK_PD_MIPI_DSI] = { | |
619 | .name = "dispblk-mipi-dsi", | |
620 | .clk_names = (const char *[]){ "dsi-pclk", "dsi-ref", }, | |
621 | .num_clks = 2, | |
622 | .gpc_name = "mipi-dsi", | |
623 | .rst_mask = BIT(0) | BIT(1), | |
624 | .clk_mask = BIT(0) | BIT(1), | |
625 | .mipi_phy_rst_mask = BIT(17), | |
626 | }, | |
627 | [IMX8MN_DISPBLK_PD_MIPI_CSI] = { | |
628 | .name = "dispblk-mipi-csi", | |
629 | .clk_names = (const char *[]){ "csi-aclk", "csi-pclk" }, | |
630 | .num_clks = 2, | |
631 | .gpc_name = "mipi-csi", | |
632 | .rst_mask = BIT(2) | BIT(3), | |
633 | .clk_mask = BIT(2) | BIT(3), | |
634 | .mipi_phy_rst_mask = BIT(16), | |
635 | }, | |
636 | [IMX8MN_DISPBLK_PD_LCDIF] = { | |
637 | .name = "dispblk-lcdif", | |
638 | .clk_names = (const char *[]){ "lcdif-axi", "lcdif-apb", "lcdif-pix", }, | |
639 | .num_clks = 3, | |
640 | .gpc_name = "lcdif", | |
641 | .rst_mask = BIT(4) | BIT(5), | |
642 | .clk_mask = BIT(4) | BIT(5), | |
643 | }, | |
644 | [IMX8MN_DISPBLK_PD_ISI] = { | |
645 | .name = "dispblk-isi", | |
646 | .clk_names = (const char *[]){ "disp_axi", "disp_apb", "disp_axi_root", | |
647 | "disp_apb_root"}, | |
648 | .num_clks = 4, | |
649 | .gpc_name = "isi", | |
650 | .rst_mask = BIT(6) | BIT(7), | |
651 | .clk_mask = BIT(6) | BIT(7), | |
652 | }, | |
653 | }; | |
654 | ||
655 | static const struct imx8m_blk_ctrl_data imx8mn_disp_blk_ctl_dev_data = { | |
656 | .max_reg = 0x84, | |
657 | .power_notifier_fn = imx8mn_disp_power_notifier, | |
658 | .domains = imx8mn_disp_blk_ctl_domain_data, | |
659 | .num_domains = ARRAY_SIZE(imx8mn_disp_blk_ctl_domain_data), | |
660 | }; | |
661 | ||
06a9a229 LS |
662 | #define LCDIF_ARCACHE_CTRL 0x4c |
663 | #define LCDIF_1_RD_HURRY GENMASK(15, 13) | |
664 | #define LCDIF_0_RD_HURRY GENMASK(12, 10) | |
665 | ||
07614fed PE |
666 | static int imx8mp_media_power_notifier(struct notifier_block *nb, |
667 | unsigned long action, void *data) | |
668 | { | |
669 | struct imx8m_blk_ctrl *bc = container_of(nb, struct imx8m_blk_ctrl, | |
670 | power_nb); | |
671 | ||
672 | if (action != GENPD_NOTIFY_ON && action != GENPD_NOTIFY_PRE_OFF) | |
673 | return NOTIFY_OK; | |
674 | ||
675 | /* Enable bus clock and deassert bus reset */ | |
676 | regmap_set_bits(bc->regmap, BLK_CLK_EN, BIT(8)); | |
677 | regmap_set_bits(bc->regmap, BLK_SFT_RSTN, BIT(8)); | |
678 | ||
06a9a229 LS |
679 | if (action == GENPD_NOTIFY_ON) { |
680 | /* | |
681 | * On power up we have no software backchannel to the GPC to | |
682 | * wait for the ADB handshake to happen, so we just delay for a | |
683 | * bit. On power down the GPC driver waits for the handshake. | |
684 | */ | |
07614fed PE |
685 | udelay(5); |
686 | ||
06a9a229 LS |
687 | /* |
688 | * Set panic read hurry level for both LCDIF interfaces to | |
689 | * maximum priority to minimize chances of display FIFO | |
690 | * underflow. | |
691 | */ | |
692 | regmap_set_bits(bc->regmap, LCDIF_ARCACHE_CTRL, | |
693 | FIELD_PREP(LCDIF_1_RD_HURRY, 7) | | |
694 | FIELD_PREP(LCDIF_0_RD_HURRY, 7)); | |
695 | } | |
696 | ||
07614fed PE |
697 | return NOTIFY_OK; |
698 | } | |
699 | ||
700 | /* | |
701 | * From i.MX 8M Plus Applications Processor Reference Manual, Rev. 1, | |
702 | * section 13.2.2, 13.2.3 | |
703 | * isp-ahb and dwe are not in Figure 13-5. Media BLK_CTRL Clocks | |
704 | */ | |
705 | static const struct imx8m_blk_ctrl_domain_data imx8mp_media_blk_ctl_domain_data[] = { | |
706 | [IMX8MP_MEDIABLK_PD_MIPI_DSI_1] = { | |
707 | .name = "mediablk-mipi-dsi-1", | |
708 | .clk_names = (const char *[]){ "apb", "phy", }, | |
709 | .num_clks = 2, | |
710 | .gpc_name = "mipi-dsi1", | |
711 | .rst_mask = BIT(0) | BIT(1), | |
712 | .clk_mask = BIT(0) | BIT(1), | |
713 | .mipi_phy_rst_mask = BIT(17), | |
714 | }, | |
715 | [IMX8MP_MEDIABLK_PD_MIPI_CSI2_1] = { | |
716 | .name = "mediablk-mipi-csi2-1", | |
717 | .clk_names = (const char *[]){ "apb", "cam1" }, | |
718 | .num_clks = 2, | |
719 | .gpc_name = "mipi-csi1", | |
720 | .rst_mask = BIT(2) | BIT(3), | |
721 | .clk_mask = BIT(2) | BIT(3), | |
722 | .mipi_phy_rst_mask = BIT(16), | |
723 | }, | |
724 | [IMX8MP_MEDIABLK_PD_LCDIF_1] = { | |
725 | .name = "mediablk-lcdif-1", | |
726 | .clk_names = (const char *[]){ "disp1", "apb", "axi", }, | |
727 | .num_clks = 3, | |
728 | .gpc_name = "lcdif1", | |
729 | .rst_mask = BIT(4) | BIT(5) | BIT(23), | |
730 | .clk_mask = BIT(4) | BIT(5) | BIT(23), | |
c553ca92 PF |
731 | .path_names = (const char *[]){"lcdif-rd", "lcdif-wr"}, |
732 | .num_paths = 2, | |
07614fed PE |
733 | }, |
734 | [IMX8MP_MEDIABLK_PD_ISI] = { | |
735 | .name = "mediablk-isi", | |
736 | .clk_names = (const char *[]){ "axi", "apb" }, | |
737 | .num_clks = 2, | |
738 | .gpc_name = "isi", | |
739 | .rst_mask = BIT(6) | BIT(7), | |
740 | .clk_mask = BIT(6) | BIT(7), | |
c553ca92 PF |
741 | .path_names = (const char *[]){"isi0", "isi1", "isi2"}, |
742 | .num_paths = 3, | |
07614fed PE |
743 | }, |
744 | [IMX8MP_MEDIABLK_PD_MIPI_CSI2_2] = { | |
745 | .name = "mediablk-mipi-csi2-2", | |
746 | .clk_names = (const char *[]){ "apb", "cam2" }, | |
747 | .num_clks = 2, | |
748 | .gpc_name = "mipi-csi2", | |
749 | .rst_mask = BIT(9) | BIT(10), | |
750 | .clk_mask = BIT(9) | BIT(10), | |
751 | .mipi_phy_rst_mask = BIT(30), | |
752 | }, | |
753 | [IMX8MP_MEDIABLK_PD_LCDIF_2] = { | |
754 | .name = "mediablk-lcdif-2", | |
7c7eaeef | 755 | .clk_names = (const char *[]){ "disp2", "apb", "axi", }, |
07614fed PE |
756 | .num_clks = 3, |
757 | .gpc_name = "lcdif2", | |
758 | .rst_mask = BIT(11) | BIT(12) | BIT(24), | |
759 | .clk_mask = BIT(11) | BIT(12) | BIT(24), | |
c553ca92 PF |
760 | .path_names = (const char *[]){"lcdif-rd", "lcdif-wr"}, |
761 | .num_paths = 2, | |
07614fed PE |
762 | }, |
763 | [IMX8MP_MEDIABLK_PD_ISP] = { | |
764 | .name = "mediablk-isp", | |
765 | .clk_names = (const char *[]){ "isp", "axi", "apb" }, | |
766 | .num_clks = 3, | |
767 | .gpc_name = "isp", | |
768 | .rst_mask = BIT(16) | BIT(17) | BIT(18), | |
769 | .clk_mask = BIT(16) | BIT(17) | BIT(18), | |
c553ca92 PF |
770 | .path_names = (const char *[]){"isp0", "isp1"}, |
771 | .num_paths = 2, | |
07614fed PE |
772 | }, |
773 | [IMX8MP_MEDIABLK_PD_DWE] = { | |
774 | .name = "mediablk-dwe", | |
775 | .clk_names = (const char *[]){ "axi", "apb" }, | |
776 | .num_clks = 2, | |
777 | .gpc_name = "dwe", | |
778 | .rst_mask = BIT(19) | BIT(20) | BIT(21), | |
779 | .clk_mask = BIT(19) | BIT(20) | BIT(21), | |
c553ca92 PF |
780 | .path_names = (const char *[]){"dwe"}, |
781 | .num_paths = 1, | |
07614fed PE |
782 | }, |
783 | [IMX8MP_MEDIABLK_PD_MIPI_DSI_2] = { | |
784 | .name = "mediablk-mipi-dsi-2", | |
785 | .clk_names = (const char *[]){ "phy", }, | |
786 | .num_clks = 1, | |
787 | .gpc_name = "mipi-dsi2", | |
788 | .rst_mask = BIT(22), | |
789 | .clk_mask = BIT(22), | |
790 | .mipi_phy_rst_mask = BIT(29), | |
791 | }, | |
792 | }; | |
793 | ||
794 | static const struct imx8m_blk_ctrl_data imx8mp_media_blk_ctl_dev_data = { | |
795 | .max_reg = 0x138, | |
796 | .power_notifier_fn = imx8mp_media_power_notifier, | |
797 | .domains = imx8mp_media_blk_ctl_domain_data, | |
798 | .num_domains = ARRAY_SIZE(imx8mp_media_blk_ctl_domain_data), | |
799 | }; | |
800 | ||
608d7c32 LS |
801 | static int imx8mq_vpu_power_notifier(struct notifier_block *nb, |
802 | unsigned long action, void *data) | |
803 | { | |
804 | struct imx8m_blk_ctrl *bc = container_of(nb, struct imx8m_blk_ctrl, | |
805 | power_nb); | |
806 | ||
807 | if (action != GENPD_NOTIFY_ON && action != GENPD_NOTIFY_PRE_OFF) | |
808 | return NOTIFY_OK; | |
809 | ||
810 | /* | |
811 | * The ADB in the VPUMIX domain has no separate reset and clock | |
812 | * enable bits, but is ungated and reset together with the VPUs. The | |
813 | * reset and clock enable inputs to the ADB is a logical OR of the | |
814 | * VPU bits. In order to set the G2 fuse bits, the G2 clock must | |
815 | * also be enabled. | |
816 | */ | |
817 | regmap_set_bits(bc->regmap, BLK_SFT_RSTN, BIT(0) | BIT(1)); | |
818 | regmap_set_bits(bc->regmap, BLK_CLK_EN, BIT(0) | BIT(1)); | |
819 | ||
820 | if (action == GENPD_NOTIFY_ON) { | |
821 | /* | |
822 | * On power up we have no software backchannel to the GPC to | |
823 | * wait for the ADB handshake to happen, so we just delay for a | |
824 | * bit. On power down the GPC driver waits for the handshake. | |
825 | */ | |
826 | udelay(5); | |
827 | ||
828 | /* set "fuse" bits to enable the VPUs */ | |
829 | regmap_set_bits(bc->regmap, 0x8, 0xffffffff); | |
830 | regmap_set_bits(bc->regmap, 0xc, 0xffffffff); | |
831 | regmap_set_bits(bc->regmap, 0x10, 0xffffffff); | |
832 | } | |
833 | ||
834 | return NOTIFY_OK; | |
835 | } | |
836 | ||
837 | static const struct imx8m_blk_ctrl_domain_data imx8mq_vpu_blk_ctl_domain_data[] = { | |
838 | [IMX8MQ_VPUBLK_PD_G1] = { | |
839 | .name = "vpublk-g1", | |
840 | .clk_names = (const char *[]){ "g1", }, | |
841 | .num_clks = 1, | |
842 | .gpc_name = "g1", | |
843 | .rst_mask = BIT(1), | |
844 | .clk_mask = BIT(1), | |
845 | }, | |
846 | [IMX8MQ_VPUBLK_PD_G2] = { | |
847 | .name = "vpublk-g2", | |
848 | .clk_names = (const char *[]){ "g2", }, | |
849 | .num_clks = 1, | |
850 | .gpc_name = "g2", | |
851 | .rst_mask = BIT(0), | |
852 | .clk_mask = BIT(0), | |
853 | }, | |
854 | }; | |
855 | ||
856 | static const struct imx8m_blk_ctrl_data imx8mq_vpu_blk_ctl_dev_data = { | |
857 | .max_reg = 0x14, | |
858 | .power_notifier_fn = imx8mq_vpu_power_notifier, | |
859 | .domains = imx8mq_vpu_blk_ctl_domain_data, | |
860 | .num_domains = ARRAY_SIZE(imx8mq_vpu_blk_ctl_domain_data), | |
861 | }; | |
862 | ||
2684ac05 LS |
863 | static const struct of_device_id imx8m_blk_ctrl_of_match[] = { |
864 | { | |
865 | .compatible = "fsl,imx8mm-vpu-blk-ctrl", | |
866 | .data = &imx8mm_vpu_blk_ctl_dev_data | |
867 | }, { | |
926e57c0 LS |
868 | .compatible = "fsl,imx8mm-disp-blk-ctrl", |
869 | .data = &imx8mm_disp_blk_ctl_dev_data | |
7f511d51 AF |
870 | }, { |
871 | .compatible = "fsl,imx8mn-disp-blk-ctrl", | |
872 | .data = &imx8mn_disp_blk_ctl_dev_data | |
07614fed PE |
873 | }, { |
874 | .compatible = "fsl,imx8mp-media-blk-ctrl", | |
875 | .data = &imx8mp_media_blk_ctl_dev_data | |
608d7c32 LS |
876 | }, { |
877 | .compatible = "fsl,imx8mq-vpu-blk-ctrl", | |
878 | .data = &imx8mq_vpu_blk_ctl_dev_data | |
a1a5f15f PF |
879 | }, { |
880 | .compatible = "fsl,imx8mp-vpu-blk-ctrl", | |
881 | .data = &imx8mp_vpu_blk_ctl_dev_data | |
7f511d51 | 882 | }, { |
2684ac05 LS |
883 | /* Sentinel */ |
884 | } | |
885 | }; | |
886 | MODULE_DEVICE_TABLE(of, imx8m_blk_ctrl_of_match); | |
887 | ||
888 | static struct platform_driver imx8m_blk_ctrl_driver = { | |
889 | .probe = imx8m_blk_ctrl_probe, | |
890 | .remove = imx8m_blk_ctrl_remove, | |
891 | .driver = { | |
892 | .name = "imx8m-blk-ctrl", | |
893 | .pm = &imx8m_blk_ctrl_pm_ops, | |
894 | .of_match_table = imx8m_blk_ctrl_of_match, | |
895 | }, | |
896 | }; | |
897 | module_platform_driver(imx8m_blk_ctrl_driver); | |
78710a72 | 898 | MODULE_LICENSE("GPL"); |