Commit | Line | Data |
---|---|---|
75fcb5ca NA |
1 | /* |
2 | * Copyright (c) 2017 BayLibre, SAS | |
3 | * Author: Neil Armstrong <narmstrong@baylibre.com> | |
4 | * | |
5 | * SPDX-License-Identifier: GPL-2.0+ | |
6 | */ | |
7 | ||
75fcb5ca NA |
8 | #include <linux/platform_device.h> |
9 | #include <linux/pm_domain.h> | |
10 | #include <linux/bitfield.h> | |
11 | #include <linux/regmap.h> | |
12 | #include <linux/mfd/syscon.h> | |
584ed6d4 | 13 | #include <linux/of.h> |
75fcb5ca NA |
14 | #include <linux/reset.h> |
15 | #include <linux/clk.h> | |
d9da1785 | 16 | #include <linux/module.h> |
75fcb5ca NA |
17 | |
18 | /* AO Offsets */ | |
19 | ||
20 | #define AO_RTI_GEN_PWR_SLEEP0 (0x3a << 2) | |
21 | ||
22 | #define GEN_PWR_VPU_HDMI BIT(8) | |
23 | #define GEN_PWR_VPU_HDMI_ISO BIT(9) | |
24 | ||
25 | /* HHI Offsets */ | |
26 | ||
27 | #define HHI_MEM_PD_REG0 (0x40 << 2) | |
28 | #define HHI_VPU_MEM_PD_REG0 (0x41 << 2) | |
29 | #define HHI_VPU_MEM_PD_REG1 (0x42 << 2) | |
bb1dca3a | 30 | #define HHI_VPU_MEM_PD_REG2 (0x4d << 2) |
75fcb5ca NA |
31 | |
32 | struct meson_gx_pwrc_vpu { | |
33 | struct generic_pm_domain genpd; | |
34 | struct regmap *regmap_ao; | |
35 | struct regmap *regmap_hhi; | |
36 | struct reset_control *rstc; | |
37 | struct clk *vpu_clk; | |
38 | struct clk *vapb_clk; | |
75fcb5ca NA |
39 | }; |
40 | ||
41 | static inline | |
42 | struct meson_gx_pwrc_vpu *genpd_to_pd(struct generic_pm_domain *d) | |
43 | { | |
44 | return container_of(d, struct meson_gx_pwrc_vpu, genpd); | |
45 | } | |
46 | ||
47 | static int meson_gx_pwrc_vpu_power_off(struct generic_pm_domain *genpd) | |
48 | { | |
49 | struct meson_gx_pwrc_vpu *pd = genpd_to_pd(genpd); | |
50 | int i; | |
51 | ||
52 | regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, | |
53 | GEN_PWR_VPU_HDMI_ISO, GEN_PWR_VPU_HDMI_ISO); | |
54 | udelay(20); | |
55 | ||
56 | /* Power Down Memories */ | |
57 | for (i = 0; i < 32; i += 2) { | |
58 | regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG0, | |
2fe3b4bb | 59 | 0x3 << i, 0x3 << i); |
75fcb5ca NA |
60 | udelay(5); |
61 | } | |
62 | for (i = 0; i < 32; i += 2) { | |
63 | regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG1, | |
2fe3b4bb | 64 | 0x3 << i, 0x3 << i); |
75fcb5ca NA |
65 | udelay(5); |
66 | } | |
67 | for (i = 8; i < 16; i++) { | |
68 | regmap_update_bits(pd->regmap_hhi, HHI_MEM_PD_REG0, | |
69 | BIT(i), BIT(i)); | |
70 | udelay(5); | |
71 | } | |
72 | udelay(20); | |
73 | ||
74 | regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, | |
75 | GEN_PWR_VPU_HDMI, GEN_PWR_VPU_HDMI); | |
76 | ||
77 | msleep(20); | |
78 | ||
79 | clk_disable_unprepare(pd->vpu_clk); | |
80 | clk_disable_unprepare(pd->vapb_clk); | |
81 | ||
75fcb5ca NA |
82 | return 0; |
83 | } | |
84 | ||
bb1dca3a NA |
85 | static int meson_g12a_pwrc_vpu_power_off(struct generic_pm_domain *genpd) |
86 | { | |
87 | struct meson_gx_pwrc_vpu *pd = genpd_to_pd(genpd); | |
88 | int i; | |
89 | ||
90 | regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, | |
91 | GEN_PWR_VPU_HDMI_ISO, GEN_PWR_VPU_HDMI_ISO); | |
92 | udelay(20); | |
93 | ||
94 | /* Power Down Memories */ | |
95 | for (i = 0; i < 32; i += 2) { | |
96 | regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG0, | |
97 | 0x3 << i, 0x3 << i); | |
98 | udelay(5); | |
99 | } | |
100 | for (i = 0; i < 32; i += 2) { | |
101 | regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG1, | |
102 | 0x3 << i, 0x3 << i); | |
103 | udelay(5); | |
104 | } | |
105 | for (i = 0; i < 32; i += 2) { | |
106 | regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG2, | |
107 | 0x3 << i, 0x3 << i); | |
108 | udelay(5); | |
109 | } | |
110 | for (i = 8; i < 16; i++) { | |
111 | regmap_update_bits(pd->regmap_hhi, HHI_MEM_PD_REG0, | |
112 | BIT(i), BIT(i)); | |
113 | udelay(5); | |
114 | } | |
115 | udelay(20); | |
116 | ||
117 | regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, | |
118 | GEN_PWR_VPU_HDMI, GEN_PWR_VPU_HDMI); | |
119 | ||
120 | msleep(20); | |
121 | ||
122 | clk_disable_unprepare(pd->vpu_clk); | |
123 | clk_disable_unprepare(pd->vapb_clk); | |
124 | ||
125 | return 0; | |
126 | } | |
127 | ||
75fcb5ca NA |
128 | static int meson_gx_pwrc_vpu_setup_clk(struct meson_gx_pwrc_vpu *pd) |
129 | { | |
130 | int ret; | |
131 | ||
132 | ret = clk_prepare_enable(pd->vpu_clk); | |
133 | if (ret) | |
134 | return ret; | |
135 | ||
339cd0ea NA |
136 | ret = clk_prepare_enable(pd->vapb_clk); |
137 | if (ret) | |
138 | clk_disable_unprepare(pd->vpu_clk); | |
139 | ||
140 | return ret; | |
75fcb5ca NA |
141 | } |
142 | ||
143 | static int meson_gx_pwrc_vpu_power_on(struct generic_pm_domain *genpd) | |
144 | { | |
145 | struct meson_gx_pwrc_vpu *pd = genpd_to_pd(genpd); | |
146 | int ret; | |
147 | int i; | |
148 | ||
149 | regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, | |
150 | GEN_PWR_VPU_HDMI, 0); | |
151 | udelay(20); | |
152 | ||
153 | /* Power Up Memories */ | |
154 | for (i = 0; i < 32; i += 2) { | |
155 | regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG0, | |
2fe3b4bb | 156 | 0x3 << i, 0); |
75fcb5ca NA |
157 | udelay(5); |
158 | } | |
159 | ||
160 | for (i = 0; i < 32; i += 2) { | |
161 | regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG1, | |
2fe3b4bb | 162 | 0x3 << i, 0); |
75fcb5ca NA |
163 | udelay(5); |
164 | } | |
165 | ||
166 | for (i = 8; i < 16; i++) { | |
167 | regmap_update_bits(pd->regmap_hhi, HHI_MEM_PD_REG0, | |
168 | BIT(i), 0); | |
169 | udelay(5); | |
170 | } | |
171 | udelay(20); | |
172 | ||
173 | ret = reset_control_assert(pd->rstc); | |
174 | if (ret) | |
175 | return ret; | |
176 | ||
177 | regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, | |
178 | GEN_PWR_VPU_HDMI_ISO, 0); | |
179 | ||
180 | ret = reset_control_deassert(pd->rstc); | |
181 | if (ret) | |
182 | return ret; | |
183 | ||
184 | ret = meson_gx_pwrc_vpu_setup_clk(pd); | |
185 | if (ret) | |
186 | return ret; | |
187 | ||
75fcb5ca NA |
188 | return 0; |
189 | } | |
190 | ||
bb1dca3a NA |
191 | static int meson_g12a_pwrc_vpu_power_on(struct generic_pm_domain *genpd) |
192 | { | |
193 | struct meson_gx_pwrc_vpu *pd = genpd_to_pd(genpd); | |
194 | int ret; | |
195 | int i; | |
196 | ||
197 | regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, | |
198 | GEN_PWR_VPU_HDMI, 0); | |
199 | udelay(20); | |
200 | ||
201 | /* Power Up Memories */ | |
202 | for (i = 0; i < 32; i += 2) { | |
203 | regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG0, | |
204 | 0x3 << i, 0); | |
205 | udelay(5); | |
206 | } | |
207 | ||
208 | for (i = 0; i < 32; i += 2) { | |
209 | regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG1, | |
210 | 0x3 << i, 0); | |
211 | udelay(5); | |
212 | } | |
213 | ||
214 | for (i = 0; i < 32; i += 2) { | |
215 | regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG2, | |
216 | 0x3 << i, 0); | |
217 | udelay(5); | |
218 | } | |
219 | ||
220 | for (i = 8; i < 16; i++) { | |
221 | regmap_update_bits(pd->regmap_hhi, HHI_MEM_PD_REG0, | |
222 | BIT(i), 0); | |
223 | udelay(5); | |
224 | } | |
225 | udelay(20); | |
226 | ||
227 | ret = reset_control_assert(pd->rstc); | |
228 | if (ret) | |
229 | return ret; | |
230 | ||
231 | regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, | |
232 | GEN_PWR_VPU_HDMI_ISO, 0); | |
233 | ||
234 | ret = reset_control_deassert(pd->rstc); | |
235 | if (ret) | |
236 | return ret; | |
237 | ||
238 | ret = meson_gx_pwrc_vpu_setup_clk(pd); | |
239 | if (ret) | |
240 | return ret; | |
241 | ||
242 | return 0; | |
243 | } | |
244 | ||
75fcb5ca NA |
245 | static bool meson_gx_pwrc_vpu_get_power(struct meson_gx_pwrc_vpu *pd) |
246 | { | |
247 | u32 reg; | |
248 | ||
249 | regmap_read(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, ®); | |
250 | ||
251 | return (reg & GEN_PWR_VPU_HDMI); | |
252 | } | |
253 | ||
254 | static struct meson_gx_pwrc_vpu vpu_hdmi_pd = { | |
255 | .genpd = { | |
256 | .name = "vpu_hdmi", | |
257 | .power_off = meson_gx_pwrc_vpu_power_off, | |
258 | .power_on = meson_gx_pwrc_vpu_power_on, | |
259 | }, | |
260 | }; | |
261 | ||
bb1dca3a NA |
262 | static struct meson_gx_pwrc_vpu vpu_hdmi_pd_g12a = { |
263 | .genpd = { | |
264 | .name = "vpu_hdmi", | |
265 | .power_off = meson_g12a_pwrc_vpu_power_off, | |
266 | .power_on = meson_g12a_pwrc_vpu_power_on, | |
267 | }, | |
268 | }; | |
269 | ||
75fcb5ca NA |
270 | static int meson_gx_pwrc_vpu_probe(struct platform_device *pdev) |
271 | { | |
bb1dca3a | 272 | const struct meson_gx_pwrc_vpu *vpu_pd_match; |
75fcb5ca | 273 | struct regmap *regmap_ao, *regmap_hhi; |
bb1dca3a | 274 | struct meson_gx_pwrc_vpu *vpu_pd; |
9491cff1 | 275 | struct device_node *parent_np; |
75fcb5ca NA |
276 | struct reset_control *rstc; |
277 | struct clk *vpu_clk; | |
278 | struct clk *vapb_clk; | |
339cd0ea NA |
279 | bool powered_off; |
280 | int ret; | |
75fcb5ca | 281 | |
bb1dca3a NA |
282 | vpu_pd_match = of_device_get_match_data(&pdev->dev); |
283 | if (!vpu_pd_match) { | |
284 | dev_err(&pdev->dev, "failed to get match data\n"); | |
285 | return -ENODEV; | |
286 | } | |
287 | ||
288 | vpu_pd = devm_kzalloc(&pdev->dev, sizeof(*vpu_pd), GFP_KERNEL); | |
289 | if (!vpu_pd) | |
290 | return -ENOMEM; | |
291 | ||
292 | memcpy(vpu_pd, vpu_pd_match, sizeof(*vpu_pd)); | |
293 | ||
9491cff1 LH |
294 | parent_np = of_get_parent(pdev->dev.of_node); |
295 | regmap_ao = syscon_node_to_regmap(parent_np); | |
296 | of_node_put(parent_np); | |
75fcb5ca NA |
297 | if (IS_ERR(regmap_ao)) { |
298 | dev_err(&pdev->dev, "failed to get regmap\n"); | |
299 | return PTR_ERR(regmap_ao); | |
300 | } | |
301 | ||
302 | regmap_hhi = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, | |
303 | "amlogic,hhi-sysctrl"); | |
304 | if (IS_ERR(regmap_hhi)) { | |
305 | dev_err(&pdev->dev, "failed to get HHI regmap\n"); | |
306 | return PTR_ERR(regmap_hhi); | |
307 | } | |
308 | ||
1116e43e | 309 | rstc = devm_reset_control_array_get_exclusive(&pdev->dev); |
78e812f0 YX |
310 | if (IS_ERR(rstc)) |
311 | return dev_err_probe(&pdev->dev, PTR_ERR(rstc), | |
312 | "failed to get reset lines\n"); | |
75fcb5ca NA |
313 | |
314 | vpu_clk = devm_clk_get(&pdev->dev, "vpu"); | |
315 | if (IS_ERR(vpu_clk)) { | |
316 | dev_err(&pdev->dev, "vpu clock request failed\n"); | |
317 | return PTR_ERR(vpu_clk); | |
318 | } | |
319 | ||
320 | vapb_clk = devm_clk_get(&pdev->dev, "vapb"); | |
321 | if (IS_ERR(vapb_clk)) { | |
322 | dev_err(&pdev->dev, "vapb clock request failed\n"); | |
323 | return PTR_ERR(vapb_clk); | |
324 | } | |
325 | ||
bb1dca3a NA |
326 | vpu_pd->regmap_ao = regmap_ao; |
327 | vpu_pd->regmap_hhi = regmap_hhi; | |
328 | vpu_pd->rstc = rstc; | |
329 | vpu_pd->vpu_clk = vpu_clk; | |
330 | vpu_pd->vapb_clk = vapb_clk; | |
331 | ||
332 | platform_set_drvdata(pdev, vpu_pd); | |
75fcb5ca | 333 | |
bb1dca3a | 334 | powered_off = meson_gx_pwrc_vpu_get_power(vpu_pd); |
339cd0ea NA |
335 | |
336 | /* If already powered, sync the clock states */ | |
337 | if (!powered_off) { | |
bb1dca3a | 338 | ret = meson_gx_pwrc_vpu_setup_clk(vpu_pd); |
339cd0ea NA |
339 | if (ret) |
340 | return ret; | |
341 | } | |
342 | ||
5aabf118 KH |
343 | vpu_pd->genpd.flags = GENPD_FLAG_ALWAYS_ON; |
344 | pm_genpd_init(&vpu_pd->genpd, NULL, powered_off); | |
75fcb5ca NA |
345 | |
346 | return of_genpd_add_provider_simple(pdev->dev.of_node, | |
bb1dca3a | 347 | &vpu_pd->genpd); |
75fcb5ca NA |
348 | } |
349 | ||
350 | static void meson_gx_pwrc_vpu_shutdown(struct platform_device *pdev) | |
351 | { | |
bb1dca3a | 352 | struct meson_gx_pwrc_vpu *vpu_pd = platform_get_drvdata(pdev); |
87f88732 HK |
353 | bool powered_off; |
354 | ||
bb1dca3a | 355 | powered_off = meson_gx_pwrc_vpu_get_power(vpu_pd); |
87f88732 | 356 | if (!powered_off) |
bb1dca3a | 357 | vpu_pd->genpd.power_off(&vpu_pd->genpd); |
75fcb5ca NA |
358 | } |
359 | ||
360 | static const struct of_device_id meson_gx_pwrc_vpu_match_table[] = { | |
bb1dca3a NA |
361 | { .compatible = "amlogic,meson-gx-pwrc-vpu", .data = &vpu_hdmi_pd }, |
362 | { | |
363 | .compatible = "amlogic,meson-g12a-pwrc-vpu", | |
364 | .data = &vpu_hdmi_pd_g12a | |
365 | }, | |
75fcb5ca NA |
366 | { /* sentinel */ } |
367 | }; | |
d9da1785 | 368 | MODULE_DEVICE_TABLE(of, meson_gx_pwrc_vpu_match_table); |
75fcb5ca NA |
369 | |
370 | static struct platform_driver meson_gx_pwrc_vpu_driver = { | |
371 | .probe = meson_gx_pwrc_vpu_probe, | |
372 | .shutdown = meson_gx_pwrc_vpu_shutdown, | |
373 | .driver = { | |
374 | .name = "meson_gx_pwrc_vpu", | |
375 | .of_match_table = meson_gx_pwrc_vpu_match_table, | |
376 | }, | |
377 | }; | |
d9da1785 KH |
378 | module_platform_driver(meson_gx_pwrc_vpu_driver); |
379 | MODULE_LICENSE("GPL v2"); |