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 | ||
8 | #include <linux/of_address.h> | |
9 | #include <linux/platform_device.h> | |
10 | #include <linux/pm_domain.h> | |
11 | #include <linux/bitfield.h> | |
12 | #include <linux/regmap.h> | |
13 | #include <linux/mfd/syscon.h> | |
14 | #include <linux/reset.h> | |
15 | #include <linux/clk.h> | |
16 | ||
17 | /* AO Offsets */ | |
18 | ||
19 | #define AO_RTI_GEN_PWR_SLEEP0 (0x3a << 2) | |
20 | ||
21 | #define GEN_PWR_VPU_HDMI BIT(8) | |
22 | #define GEN_PWR_VPU_HDMI_ISO BIT(9) | |
23 | ||
24 | /* HHI Offsets */ | |
25 | ||
26 | #define HHI_MEM_PD_REG0 (0x40 << 2) | |
27 | #define HHI_VPU_MEM_PD_REG0 (0x41 << 2) | |
28 | #define HHI_VPU_MEM_PD_REG1 (0x42 << 2) | |
29 | ||
30 | struct meson_gx_pwrc_vpu { | |
31 | struct generic_pm_domain genpd; | |
32 | struct regmap *regmap_ao; | |
33 | struct regmap *regmap_hhi; | |
34 | struct reset_control *rstc; | |
35 | struct clk *vpu_clk; | |
36 | struct clk *vapb_clk; | |
75fcb5ca NA |
37 | }; |
38 | ||
39 | static inline | |
40 | struct meson_gx_pwrc_vpu *genpd_to_pd(struct generic_pm_domain *d) | |
41 | { | |
42 | return container_of(d, struct meson_gx_pwrc_vpu, genpd); | |
43 | } | |
44 | ||
45 | static int meson_gx_pwrc_vpu_power_off(struct generic_pm_domain *genpd) | |
46 | { | |
47 | struct meson_gx_pwrc_vpu *pd = genpd_to_pd(genpd); | |
48 | int i; | |
49 | ||
50 | regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, | |
51 | GEN_PWR_VPU_HDMI_ISO, GEN_PWR_VPU_HDMI_ISO); | |
52 | udelay(20); | |
53 | ||
54 | /* Power Down Memories */ | |
55 | for (i = 0; i < 32; i += 2) { | |
56 | regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG0, | |
57 | 0x2 << i, 0x3 << i); | |
58 | udelay(5); | |
59 | } | |
60 | for (i = 0; i < 32; i += 2) { | |
61 | regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG1, | |
62 | 0x2 << i, 0x3 << i); | |
63 | udelay(5); | |
64 | } | |
65 | for (i = 8; i < 16; i++) { | |
66 | regmap_update_bits(pd->regmap_hhi, HHI_MEM_PD_REG0, | |
67 | BIT(i), BIT(i)); | |
68 | udelay(5); | |
69 | } | |
70 | udelay(20); | |
71 | ||
72 | regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, | |
73 | GEN_PWR_VPU_HDMI, GEN_PWR_VPU_HDMI); | |
74 | ||
75 | msleep(20); | |
76 | ||
77 | clk_disable_unprepare(pd->vpu_clk); | |
78 | clk_disable_unprepare(pd->vapb_clk); | |
79 | ||
75fcb5ca NA |
80 | return 0; |
81 | } | |
82 | ||
83 | static int meson_gx_pwrc_vpu_setup_clk(struct meson_gx_pwrc_vpu *pd) | |
84 | { | |
85 | int ret; | |
86 | ||
87 | ret = clk_prepare_enable(pd->vpu_clk); | |
88 | if (ret) | |
89 | return ret; | |
90 | ||
339cd0ea NA |
91 | ret = clk_prepare_enable(pd->vapb_clk); |
92 | if (ret) | |
93 | clk_disable_unprepare(pd->vpu_clk); | |
94 | ||
95 | return ret; | |
75fcb5ca NA |
96 | } |
97 | ||
98 | static int meson_gx_pwrc_vpu_power_on(struct generic_pm_domain *genpd) | |
99 | { | |
100 | struct meson_gx_pwrc_vpu *pd = genpd_to_pd(genpd); | |
101 | int ret; | |
102 | int i; | |
103 | ||
104 | regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, | |
105 | GEN_PWR_VPU_HDMI, 0); | |
106 | udelay(20); | |
107 | ||
108 | /* Power Up Memories */ | |
109 | for (i = 0; i < 32; i += 2) { | |
110 | regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG0, | |
111 | 0x2 << i, 0); | |
112 | udelay(5); | |
113 | } | |
114 | ||
115 | for (i = 0; i < 32; i += 2) { | |
116 | regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG1, | |
117 | 0x2 << i, 0); | |
118 | udelay(5); | |
119 | } | |
120 | ||
121 | for (i = 8; i < 16; i++) { | |
122 | regmap_update_bits(pd->regmap_hhi, HHI_MEM_PD_REG0, | |
123 | BIT(i), 0); | |
124 | udelay(5); | |
125 | } | |
126 | udelay(20); | |
127 | ||
128 | ret = reset_control_assert(pd->rstc); | |
129 | if (ret) | |
130 | return ret; | |
131 | ||
132 | regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, | |
133 | GEN_PWR_VPU_HDMI_ISO, 0); | |
134 | ||
135 | ret = reset_control_deassert(pd->rstc); | |
136 | if (ret) | |
137 | return ret; | |
138 | ||
139 | ret = meson_gx_pwrc_vpu_setup_clk(pd); | |
140 | if (ret) | |
141 | return ret; | |
142 | ||
75fcb5ca NA |
143 | return 0; |
144 | } | |
145 | ||
146 | static bool meson_gx_pwrc_vpu_get_power(struct meson_gx_pwrc_vpu *pd) | |
147 | { | |
148 | u32 reg; | |
149 | ||
150 | regmap_read(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, ®); | |
151 | ||
152 | return (reg & GEN_PWR_VPU_HDMI); | |
153 | } | |
154 | ||
155 | static struct meson_gx_pwrc_vpu vpu_hdmi_pd = { | |
156 | .genpd = { | |
157 | .name = "vpu_hdmi", | |
158 | .power_off = meson_gx_pwrc_vpu_power_off, | |
159 | .power_on = meson_gx_pwrc_vpu_power_on, | |
160 | }, | |
161 | }; | |
162 | ||
163 | static int meson_gx_pwrc_vpu_probe(struct platform_device *pdev) | |
164 | { | |
165 | struct regmap *regmap_ao, *regmap_hhi; | |
166 | struct reset_control *rstc; | |
167 | struct clk *vpu_clk; | |
168 | struct clk *vapb_clk; | |
339cd0ea NA |
169 | bool powered_off; |
170 | int ret; | |
75fcb5ca NA |
171 | |
172 | regmap_ao = syscon_node_to_regmap(of_get_parent(pdev->dev.of_node)); | |
173 | if (IS_ERR(regmap_ao)) { | |
174 | dev_err(&pdev->dev, "failed to get regmap\n"); | |
175 | return PTR_ERR(regmap_ao); | |
176 | } | |
177 | ||
178 | regmap_hhi = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, | |
179 | "amlogic,hhi-sysctrl"); | |
180 | if (IS_ERR(regmap_hhi)) { | |
181 | dev_err(&pdev->dev, "failed to get HHI regmap\n"); | |
182 | return PTR_ERR(regmap_hhi); | |
183 | } | |
184 | ||
185 | rstc = devm_reset_control_array_get(&pdev->dev, false, false); | |
186 | if (IS_ERR(rstc)) { | |
3a2ad7bd HK |
187 | if (PTR_ERR(rstc) != -EPROBE_DEFER) |
188 | dev_err(&pdev->dev, "failed to get reset lines\n"); | |
75fcb5ca NA |
189 | return PTR_ERR(rstc); |
190 | } | |
191 | ||
192 | vpu_clk = devm_clk_get(&pdev->dev, "vpu"); | |
193 | if (IS_ERR(vpu_clk)) { | |
194 | dev_err(&pdev->dev, "vpu clock request failed\n"); | |
195 | return PTR_ERR(vpu_clk); | |
196 | } | |
197 | ||
198 | vapb_clk = devm_clk_get(&pdev->dev, "vapb"); | |
199 | if (IS_ERR(vapb_clk)) { | |
200 | dev_err(&pdev->dev, "vapb clock request failed\n"); | |
201 | return PTR_ERR(vapb_clk); | |
202 | } | |
203 | ||
204 | vpu_hdmi_pd.regmap_ao = regmap_ao; | |
205 | vpu_hdmi_pd.regmap_hhi = regmap_hhi; | |
206 | vpu_hdmi_pd.rstc = rstc; | |
207 | vpu_hdmi_pd.vpu_clk = vpu_clk; | |
208 | vpu_hdmi_pd.vapb_clk = vapb_clk; | |
209 | ||
339cd0ea NA |
210 | powered_off = meson_gx_pwrc_vpu_get_power(&vpu_hdmi_pd); |
211 | ||
212 | /* If already powered, sync the clock states */ | |
213 | if (!powered_off) { | |
214 | ret = meson_gx_pwrc_vpu_setup_clk(&vpu_hdmi_pd); | |
215 | if (ret) | |
216 | return ret; | |
217 | } | |
218 | ||
219 | pm_genpd_init(&vpu_hdmi_pd.genpd, &pm_domain_always_on_gov, | |
220 | powered_off); | |
75fcb5ca NA |
221 | |
222 | return of_genpd_add_provider_simple(pdev->dev.of_node, | |
223 | &vpu_hdmi_pd.genpd); | |
224 | } | |
225 | ||
226 | static void meson_gx_pwrc_vpu_shutdown(struct platform_device *pdev) | |
227 | { | |
339cd0ea | 228 | meson_gx_pwrc_vpu_power_off(&vpu_hdmi_pd.genpd); |
75fcb5ca NA |
229 | } |
230 | ||
231 | static const struct of_device_id meson_gx_pwrc_vpu_match_table[] = { | |
232 | { .compatible = "amlogic,meson-gx-pwrc-vpu" }, | |
233 | { /* sentinel */ } | |
234 | }; | |
235 | ||
236 | static struct platform_driver meson_gx_pwrc_vpu_driver = { | |
237 | .probe = meson_gx_pwrc_vpu_probe, | |
238 | .shutdown = meson_gx_pwrc_vpu_shutdown, | |
239 | .driver = { | |
240 | .name = "meson_gx_pwrc_vpu", | |
241 | .of_match_table = meson_gx_pwrc_vpu_match_table, | |
242 | }, | |
243 | }; | |
244 | builtin_platform_driver(meson_gx_pwrc_vpu_driver); |