]>
Commit | Line | Data |
---|---|---|
3ead6616 | 1 | // SPDX-License-Identifier: BSD-3-Clause AND GPL-2.0 |
7c75f7f1 | 2 | /* |
3ead6616 KD |
3 | * Clock and reset drivers for Qualcomm platforms Global Clock |
4 | * Controller (GCC). | |
7c75f7f1 JRO |
5 | * |
6 | * (C) Copyright 2015 Mateusz Kulikowski <[email protected]> | |
3ead6616 KD |
7 | * (C) Copyright 2020 Sartura Ltd. (reset driver) |
8 | * Author: Robert Marko <[email protected]> | |
9 | * (C) Copyright 2022 Linaro Ltd. (reset driver) | |
10 | * Author: Sumit Garg <[email protected]> | |
7c75f7f1 JRO |
11 | * |
12 | * Based on Little Kernel driver, simplified | |
7c75f7f1 JRO |
13 | */ |
14 | ||
7c75f7f1 | 15 | #include <clk-uclass.h> |
ba0598bd | 16 | #include <linux/clk-provider.h> |
7c75f7f1 | 17 | #include <dm.h> |
3ead6616 KD |
18 | #include <dm/device-internal.h> |
19 | #include <dm/lists.h> | |
7c75f7f1 JRO |
20 | #include <errno.h> |
21 | #include <asm/io.h> | |
d5db46cf CC |
22 | #include <linux/bug.h> |
23 | #include <linux/delay.h> | |
7c75f7f1 | 24 | #include <linux/bitops.h> |
b563e766 | 25 | #include <linux/iopoll.h> |
3ead6616 | 26 | #include <reset-uclass.h> |
b563e766 | 27 | #include <power-domain-uclass.h> |
3ead6616 | 28 | |
a623c14f | 29 | #include "clock-qcom.h" |
7c75f7f1 JRO |
30 | |
31 | /* CBCR register fields */ | |
32 | #define CBCR_BRANCH_ENABLE_BIT BIT(0) | |
33 | #define CBCR_BRANCH_OFF_BIT BIT(31) | |
34 | ||
b563e766 VB |
35 | #define GDSC_SW_COLLAPSE_MASK BIT(0) |
36 | #define GDSC_POWER_DOWN_COMPLETE BIT(15) | |
37 | #define GDSC_POWER_UP_COMPLETE BIT(16) | |
38 | #define GDSC_PWR_ON_MASK BIT(31) | |
39 | #define CFG_GDSCR_OFFSET 0x4 | |
40 | #define GDSC_STATUS_POLL_TIMEOUT_US 1500 | |
41 | ||
7c75f7f1 JRO |
42 | /* Enable clock controlled by CBC soft macro */ |
43 | void clk_enable_cbc(phys_addr_t cbcr) | |
44 | { | |
45 | setbits_le32(cbcr, CBCR_BRANCH_ENABLE_BIT); | |
46 | ||
47 | while (readl(cbcr) & CBCR_BRANCH_OFF_BIT) | |
48 | ; | |
49 | } | |
50 | ||
640dc349 | 51 | void clk_enable_gpll0(phys_addr_t base, const struct pll_vote_clk *gpll0) |
7c75f7f1 JRO |
52 | { |
53 | if (readl(base + gpll0->status) & gpll0->status_bit) | |
54 | return; /* clock already enabled */ | |
55 | ||
56 | setbits_le32(base + gpll0->ena_vote, gpll0->vote_bit); | |
57 | ||
58 | while ((readl(base + gpll0->status) & gpll0->status_bit) == 0) | |
59 | ; | |
60 | } | |
61 | ||
640dc349 RF |
62 | #define BRANCH_ON_VAL (0) |
63 | #define BRANCH_NOC_FSM_ON_VAL BIT(29) | |
64 | #define BRANCH_CHECK_MASK GENMASK(31, 28) | |
65 | ||
66 | void clk_enable_vote_clk(phys_addr_t base, const struct vote_clk *vclk) | |
67 | { | |
68 | u32 val; | |
69 | ||
70 | setbits_le32(base + vclk->ena_vote, vclk->vote_bit); | |
71 | do { | |
72 | val = readl(base + vclk->cbcr_reg); | |
73 | val &= BRANCH_CHECK_MASK; | |
74 | } while ((val != BRANCH_ON_VAL) && (val != BRANCH_NOC_FSM_ON_VAL)); | |
75 | } | |
76 | ||
6d430e11 | 77 | #define APPS_CMD_RCGR_UPDATE BIT(0) |
7c75f7f1 | 78 | |
6d430e11 SS |
79 | /* Update clock command via CMD_RCGR */ |
80 | void clk_bcr_update(phys_addr_t apps_cmd_rcgr) | |
7c75f7f1 | 81 | { |
d5db46cf | 82 | u32 count; |
6d430e11 | 83 | setbits_le32(apps_cmd_rcgr, APPS_CMD_RCGR_UPDATE); |
7c75f7f1 JRO |
84 | |
85 | /* Wait for frequency to be updated. */ | |
d5db46cf CC |
86 | for (count = 0; count < 50000; count++) { |
87 | if (!(readl(apps_cmd_rcgr) & APPS_CMD_RCGR_UPDATE)) | |
88 | break; | |
89 | udelay(1); | |
90 | } | |
91 | WARN(count == 50000, "WARNING: RCG @ %#llx [%#010x] stuck at off\n", | |
92 | apps_cmd_rcgr, readl(apps_cmd_rcgr)); | |
7c75f7f1 JRO |
93 | } |
94 | ||
d5db46cf CC |
95 | #define CFG_SRC_DIV_MASK 0b11111 |
96 | #define CFG_SRC_SEL_SHIFT 8 | |
97 | #define CFG_SRC_SEL_MASK (0x7 << CFG_SRC_SEL_SHIFT) | |
98 | #define CFG_MODE_SHIFT 12 | |
99 | #define CFG_MODE_MASK (0x3 << CFG_MODE_SHIFT) | |
100 | #define CFG_MODE_DUAL_EDGE (0x2 << CFG_MODE_SHIFT) | |
101 | #define CFG_HW_CLK_CTRL_MASK BIT(20) | |
7c75f7f1 | 102 | |
d5db46cf CC |
103 | /* |
104 | * root set rate for clocks with half integer and MND divider | |
105 | * div should be pre-calculated ((div * 2) - 1) | |
106 | */ | |
d33d4e0a | 107 | void clk_rcg_set_rate_mnd(phys_addr_t base, uint32_t cmd_rcgr, |
6acc4431 | 108 | int div, int m, int n, int source, u8 mnd_width) |
7c75f7f1 JRO |
109 | { |
110 | u32 cfg; | |
111 | /* M value for MND divider. */ | |
112 | u32 m_val = m; | |
d5db46cf | 113 | u32 n_minus_m = n - m; |
7c75f7f1 | 114 | /* NOT(N-M) value for MND divider. */ |
d5db46cf | 115 | u32 n_val = ~n_minus_m * !!(n); |
7c75f7f1 | 116 | /* NOT 2D value for MND divider. */ |
d5db46cf | 117 | u32 d_val = ~(clamp_t(u32, n, m, n_minus_m)); |
6acc4431 CC |
118 | u32 mask = BIT(mnd_width) - 1; |
119 | ||
120 | debug("m %#x n %#x d %#x div %#x mask %#x\n", m_val, n_val, d_val, div, mask); | |
7c75f7f1 JRO |
121 | |
122 | /* Program MND values */ | |
d33d4e0a CC |
123 | writel(m_val & mask, base + cmd_rcgr + RCG_M_REG); |
124 | writel(n_val & mask, base + cmd_rcgr + RCG_N_REG); | |
125 | writel(d_val & mask, base + cmd_rcgr + RCG_D_REG); | |
7c75f7f1 JRO |
126 | |
127 | /* setup src select and divider */ | |
d33d4e0a | 128 | cfg = readl(base + cmd_rcgr + RCG_CFG_REG); |
054eb877 VB |
129 | cfg &= ~(CFG_SRC_SEL_MASK | CFG_MODE_MASK | CFG_HW_CLK_CTRL_MASK | |
130 | CFG_SRC_DIV_MASK); | |
d5db46cf | 131 | cfg |= source & CFG_SRC_SEL_MASK; /* Select clock source */ |
7c75f7f1 | 132 | |
7c75f7f1 | 133 | if (div) |
d5db46cf | 134 | cfg |= div & CFG_SRC_DIV_MASK; |
7c75f7f1 | 135 | |
d5db46cf | 136 | if (n && n != m) |
7c75f7f1 JRO |
137 | cfg |= CFG_MODE_DUAL_EDGE; |
138 | ||
d33d4e0a | 139 | writel(cfg, base + cmd_rcgr + RCG_CFG_REG); /* Write new clock configuration */ |
7c75f7f1 JRO |
140 | |
141 | /* Inform h/w to start using the new config. */ | |
d33d4e0a | 142 | clk_bcr_update(base + cmd_rcgr); |
22d3fcd3 SG |
143 | } |
144 | ||
145 | /* root set rate for clocks with half integer and mnd_width=0 */ | |
d33d4e0a | 146 | void clk_rcg_set_rate(phys_addr_t base, uint32_t cmd_rcgr, int div, |
22d3fcd3 SG |
147 | int source) |
148 | { | |
149 | u32 cfg; | |
150 | ||
151 | /* setup src select and divider */ | |
d33d4e0a | 152 | cfg = readl(base + cmd_rcgr + RCG_CFG_REG); |
d5db46cf | 153 | cfg &= ~(CFG_SRC_SEL_MASK | CFG_MODE_MASK | CFG_HW_CLK_CTRL_MASK); |
22d3fcd3 SG |
154 | cfg |= source & CFG_CLK_SRC_MASK; /* Select clock source */ |
155 | ||
156 | /* | |
157 | * Set the divider; HW permits fraction dividers (+0.5), but | |
158 | * for simplicity, we will support integers only | |
159 | */ | |
160 | if (div) | |
d5db46cf | 161 | cfg |= (2 * div - 1) & CFG_SRC_DIV_MASK; |
22d3fcd3 | 162 | |
d33d4e0a | 163 | writel(cfg, base + cmd_rcgr + RCG_CFG_REG); /* Write new clock configuration */ |
22d3fcd3 SG |
164 | |
165 | /* Inform h/w to start using the new config. */ | |
d33d4e0a | 166 | clk_bcr_update(base + cmd_rcgr); |
7c75f7f1 JRO |
167 | } |
168 | ||
5b359312 NA |
169 | #define PHY_MUX_MASK GENMASK(1, 0) |
170 | #define PHY_MUX_PHY_SRC 0 | |
171 | #define PHY_MUX_REF_SRC 2 | |
172 | ||
173 | void clk_phy_mux_enable(phys_addr_t base, uint32_t cmd_rcgr, bool enabled) | |
174 | { | |
175 | u32 cfg; | |
176 | ||
177 | /* setup src select and divider */ | |
178 | cfg = readl(base + cmd_rcgr); | |
179 | cfg &= ~(PHY_MUX_MASK); | |
180 | if (enabled) | |
181 | cfg |= FIELD_PREP(PHY_MUX_MASK, PHY_MUX_PHY_SRC); | |
182 | else | |
183 | cfg |= FIELD_PREP(PHY_MUX_MASK, PHY_MUX_REF_SRC); | |
184 | ||
185 | writel(cfg, base + cmd_rcgr); | |
186 | } | |
187 | ||
d5db46cf CC |
188 | const struct freq_tbl *qcom_find_freq(const struct freq_tbl *f, uint rate) |
189 | { | |
190 | if (!f) | |
191 | return NULL; | |
192 | ||
193 | if (!f->freq) | |
194 | return f; | |
195 | ||
196 | for (; f->freq; f++) | |
197 | if (rate <= f->freq) | |
198 | return f; | |
199 | ||
200 | /* Default to our fastest rate */ | |
201 | return f - 1; | |
202 | } | |
203 | ||
7c75f7f1 JRO |
204 | static int msm_clk_probe(struct udevice *dev) |
205 | { | |
3ead6616 | 206 | struct msm_clk_data *data = (struct msm_clk_data *)dev_get_driver_data(dev); |
7c75f7f1 JRO |
207 | struct msm_clk_priv *priv = dev_get_priv(dev); |
208 | ||
2548493a | 209 | priv->base = dev_read_addr(dev); |
7c75f7f1 JRO |
210 | if (priv->base == FDT_ADDR_T_NONE) |
211 | return -EINVAL; | |
212 | ||
3ead6616 KD |
213 | priv->data = data; |
214 | ||
7c75f7f1 JRO |
215 | return 0; |
216 | } | |
217 | ||
218 | static ulong msm_clk_set_rate(struct clk *clk, ulong rate) | |
219 | { | |
37ea1343 CC |
220 | struct msm_clk_data *data = (struct msm_clk_data *)dev_get_driver_data(clk->dev); |
221 | ||
222 | if (data->set_rate) | |
223 | return data->set_rate(clk, rate); | |
224 | ||
225 | return 0; | |
7c75f7f1 JRO |
226 | } |
227 | ||
c9e384e9 SG |
228 | static int msm_clk_enable(struct clk *clk) |
229 | { | |
37ea1343 CC |
230 | struct msm_clk_data *data = (struct msm_clk_data *)dev_get_driver_data(clk->dev); |
231 | ||
232 | if (data->enable) | |
233 | return data->enable(clk); | |
234 | ||
235 | return 0; | |
c9e384e9 SG |
236 | } |
237 | ||
ba0598bd CC |
238 | static void dump_gplls(struct udevice *dev, phys_addr_t base) |
239 | { | |
240 | struct msm_clk_data *data = (struct msm_clk_data *)dev_get_driver_data(dev); | |
241 | u32 i; | |
242 | bool locked; | |
243 | u64 l, a, xo_rate = 19200000; | |
244 | struct clk *clk = NULL; | |
245 | struct udevice *xodev; | |
246 | const phys_addr_t *gplls = data->dbg_pll_addrs; | |
247 | ||
248 | uclass_foreach_dev_probe(UCLASS_CLK, xodev) { | |
249 | if (!strcmp(xodev->name, "xo-board") || !strcmp(xodev->name, "xo_board")) { | |
250 | clk = dev_get_clk_ptr(xodev); | |
251 | break; | |
252 | } | |
253 | } | |
254 | ||
255 | if (clk) { | |
256 | xo_rate = clk_get_rate(clk); | |
257 | ||
258 | /* On SDM845 this needs to be divided by 2 for some reason */ | |
259 | if (xo_rate && of_machine_is_compatible("qcom,sdm845")) | |
260 | xo_rate /= 2; | |
261 | } else { | |
262 | printf("Can't find XO clock, XO_BOARD rate may be wrong\n"); | |
263 | } | |
264 | ||
265 | printf("GPLL clocks:\n"); | |
266 | printf("| GPLL | LOCKED | XO_BOARD | PLL_L | ALPHA |\n"); | |
267 | printf("+--------+--------+-----------+------------+----------------+\n"); | |
268 | for (i = 0; i < data->num_plls; i++) { | |
269 | locked = !!(readl(gplls[i]) & BIT(31)); | |
270 | l = readl(gplls[i] + 4) & (BIT(16) - 1); | |
271 | a = readq(gplls[i] + 40) & (BIT(16) - 1); | |
272 | printf("| GPLL%-2d | %-6s | %9llu * (%#-9llx + %#-13llx * 2 ** -40 ) / 1000000\n", | |
273 | i, locked ? "X" : "", xo_rate, l, a); | |
274 | } | |
275 | } | |
276 | ||
277 | static void dump_rcgs(struct udevice *dev) | |
278 | { | |
279 | struct msm_clk_data *data = (struct msm_clk_data *)dev_get_driver_data(dev); | |
280 | int i; | |
281 | u32 cmd; | |
282 | u32 cfg; | |
283 | u32 not_n_minus_m; | |
284 | u32 src, m, n, div; | |
285 | bool root_on, d_odd; | |
286 | ||
287 | printf("\nRCGs:\n"); | |
288 | ||
289 | /* | |
290 | * Which GPLL SRC corresponds to depends on the parent map, see gcc-<soc>.c in Linux | |
291 | * and find the parent map associated with the clock. Note that often there are multiple | |
292 | * outputs from a single GPLL where one is actually half the rate of the other (_EVEN). | |
293 | * intput_freq = associated GPLL output freq (potentially divided depending on SRC). | |
294 | */ | |
295 | printf("| NAME | ON | SRC | OUT_FREQ = input_freq * (m/n) * (1/d) | [CMD REG ] |\n"); | |
296 | printf("+----------------------------------+----+-----+---------------------------------------+--------------+\n"); | |
297 | for (i = 0; i < data->num_rcgs; i++) { | |
298 | cmd = readl(data->dbg_rcg_addrs[i]); | |
299 | cfg = readl(data->dbg_rcg_addrs[i] + 0x4); | |
300 | m = readl(data->dbg_rcg_addrs[i] + 0x8); | |
301 | n = 0; | |
302 | not_n_minus_m = readl(data->dbg_rcg_addrs[i] + 0xc); | |
303 | ||
304 | root_on = !(cmd & BIT(31)); // ROOT_OFF | |
305 | src = (cfg >> 8) & 7; | |
306 | ||
307 | if (not_n_minus_m) { | |
308 | n = (~not_n_minus_m & 0xffff); | |
309 | ||
310 | /* A clumsy assumption that this is an 8-bit MND RCG */ | |
311 | if ((n & 0xff00) == 0xff00) | |
312 | n = n & 0xff; | |
313 | ||
314 | n += m; | |
315 | } | |
316 | ||
317 | div = ((cfg & 0b11111) + 1) / 2; | |
318 | d_odd = ((cfg & 0b11111) + 1) % 2 == 1; | |
319 | printf("%-34s | %-2s | %3d | input_freq * (%4d/%5d) * (1/%1d%-2s) | [%#010x]\n", | |
320 | data->dbg_rcg_names[i], root_on ? "X" : "", src, | |
321 | m ?: 1, n ?: 1, div, d_odd ? ".5" : "", cmd); | |
322 | } | |
323 | ||
324 | printf("\n"); | |
325 | } | |
326 | ||
327 | static void __maybe_unused msm_dump_clks(struct udevice *dev) | |
328 | { | |
329 | struct msm_clk_data *data = (struct msm_clk_data *)dev_get_driver_data(dev); | |
330 | struct msm_clk_priv *priv = dev_get_priv(dev); | |
331 | const struct gate_clk *sclk; | |
332 | int val, i; | |
333 | ||
334 | if (!data->clks) { | |
335 | printf("No clocks\n"); | |
336 | return; | |
337 | } | |
338 | ||
339 | printf("Gate Clocks:\n"); | |
340 | for (i = 0; i < data->num_clks; i++) { | |
341 | sclk = &data->clks[i]; | |
342 | if (!sclk->name) | |
343 | continue; | |
344 | printf("%-32s: ", sclk->name); | |
345 | val = readl(priv->base + sclk->reg) & sclk->en_val; | |
346 | printf("%s\n", val ? "ON" : ""); | |
347 | } | |
348 | ||
349 | dump_gplls(dev, priv->base); | |
350 | dump_rcgs(dev); | |
351 | } | |
352 | ||
7c75f7f1 JRO |
353 | static struct clk_ops msm_clk_ops = { |
354 | .set_rate = msm_clk_set_rate, | |
c9e384e9 | 355 | .enable = msm_clk_enable, |
ba0598bd CC |
356 | #if IS_ENABLED(CONFIG_CMD_CLK) |
357 | .dump = msm_dump_clks, | |
358 | #endif | |
7c75f7f1 JRO |
359 | }; |
360 | ||
3ead6616 KD |
361 | U_BOOT_DRIVER(qcom_clk) = { |
362 | .name = "qcom_clk", | |
7c75f7f1 | 363 | .id = UCLASS_CLK, |
7c75f7f1 | 364 | .ops = &msm_clk_ops, |
41575d8e | 365 | .priv_auto = sizeof(struct msm_clk_priv), |
7c75f7f1 | 366 | .probe = msm_clk_probe, |
8670cb40 | 367 | .flags = DM_FLAG_PRE_RELOC | DM_FLAG_DEFAULT_PD_CTRL_OFF, |
7c75f7f1 | 368 | }; |
3ead6616 KD |
369 | |
370 | int qcom_cc_bind(struct udevice *parent) | |
371 | { | |
372 | struct msm_clk_data *data = (struct msm_clk_data *)dev_get_driver_data(parent); | |
b563e766 | 373 | struct udevice *clkdev = NULL, *rstdev = NULL, *pwrdev; |
3ead6616 KD |
374 | struct driver *drv; |
375 | int ret; | |
376 | ||
377 | /* Get a handle to the common clk handler */ | |
378 | drv = lists_driver_lookup_name("qcom_clk"); | |
379 | if (!drv) | |
380 | return -ENOENT; | |
381 | ||
382 | /* Register the clock controller */ | |
383 | ret = device_bind_with_driver_data(parent, drv, "qcom_clk", (ulong)data, | |
384 | dev_ofnode(parent), &clkdev); | |
385 | if (ret) | |
386 | return ret; | |
387 | ||
b563e766 VB |
388 | if (data->resets) { |
389 | /* Get a handle to the common reset handler */ | |
390 | drv = lists_driver_lookup_name("qcom_reset"); | |
391 | if (!drv) { | |
392 | ret = -ENOENT; | |
393 | goto unbind_clkdev; | |
394 | } | |
395 | ||
396 | /* Register the reset controller */ | |
397 | ret = device_bind_with_driver_data(parent, drv, "qcom_reset", (ulong)data, | |
398 | dev_ofnode(parent), &rstdev); | |
399 | if (ret) | |
400 | goto unbind_clkdev; | |
401 | } | |
3ead6616 | 402 | |
b563e766 VB |
403 | if (data->power_domains) { |
404 | /* Get a handle to the common power domain handler */ | |
405 | drv = lists_driver_lookup_name("qcom_power"); | |
406 | if (!drv) { | |
407 | ret = -ENOENT; | |
408 | goto unbind_rstdev; | |
409 | } | |
410 | /* Register the power domain controller */ | |
411 | ret = device_bind_with_driver_data(parent, drv, "qcom_power", (ulong)data, | |
412 | dev_ofnode(parent), &pwrdev); | |
413 | if (ret) | |
414 | goto unbind_rstdev; | |
415 | } | |
3ead6616 | 416 | |
b563e766 VB |
417 | return 0; |
418 | ||
419 | unbind_rstdev: | |
420 | device_unbind(rstdev); | |
421 | unbind_clkdev: | |
422 | device_unbind(clkdev); | |
3ead6616 KD |
423 | |
424 | return ret; | |
425 | } | |
426 | ||
427 | static int qcom_reset_set(struct reset_ctl *rst, bool assert) | |
428 | { | |
429 | struct msm_clk_data *data = (struct msm_clk_data *)dev_get_driver_data(rst->dev); | |
430 | void __iomem *base = dev_get_priv(rst->dev); | |
431 | const struct qcom_reset_map *map; | |
432 | u32 value; | |
433 | ||
434 | map = &data->resets[rst->id]; | |
435 | ||
436 | value = readl(base + map->reg); | |
437 | ||
438 | if (assert) | |
439 | value |= BIT(map->bit); | |
440 | else | |
441 | value &= ~BIT(map->bit); | |
442 | ||
443 | writel(value, base + map->reg); | |
444 | ||
445 | return 0; | |
446 | } | |
447 | ||
448 | static int qcom_reset_assert(struct reset_ctl *rst) | |
449 | { | |
450 | return qcom_reset_set(rst, true); | |
451 | } | |
452 | ||
453 | static int qcom_reset_deassert(struct reset_ctl *rst) | |
454 | { | |
455 | return qcom_reset_set(rst, false); | |
456 | } | |
457 | ||
458 | static const struct reset_ops qcom_reset_ops = { | |
459 | .rst_assert = qcom_reset_assert, | |
460 | .rst_deassert = qcom_reset_deassert, | |
461 | }; | |
462 | ||
463 | static int qcom_reset_probe(struct udevice *dev) | |
464 | { | |
465 | /* Set our priv pointer to the base address */ | |
466 | dev_set_priv(dev, (void *)dev_read_addr(dev)); | |
467 | ||
468 | return 0; | |
469 | } | |
470 | ||
471 | U_BOOT_DRIVER(qcom_reset) = { | |
472 | .name = "qcom_reset", | |
473 | .id = UCLASS_RESET, | |
474 | .ops = &qcom_reset_ops, | |
475 | .probe = qcom_reset_probe, | |
476 | }; | |
b563e766 VB |
477 | |
478 | static int qcom_power_set(struct power_domain *pwr, bool on) | |
479 | { | |
480 | struct msm_clk_data *data = (struct msm_clk_data *)dev_get_driver_data(pwr->dev); | |
481 | void __iomem *base = dev_get_priv(pwr->dev); | |
482 | const struct qcom_power_map *map; | |
483 | u32 value; | |
484 | int ret; | |
485 | ||
486 | if (pwr->id >= data->num_power_domains) | |
487 | return -ENODEV; | |
488 | ||
489 | map = &data->power_domains[pwr->id]; | |
490 | ||
491 | if (!map->reg) | |
492 | return -ENODEV; | |
493 | ||
494 | value = readl(base + map->reg); | |
495 | ||
496 | if (on) | |
497 | value &= ~GDSC_SW_COLLAPSE_MASK; | |
498 | else | |
499 | value |= GDSC_SW_COLLAPSE_MASK; | |
500 | ||
501 | writel(value, base + map->reg); | |
502 | ||
503 | if (on) | |
504 | ret = readl_poll_timeout(base + map->reg + CFG_GDSCR_OFFSET, | |
505 | value, | |
506 | (value & GDSC_POWER_UP_COMPLETE) || | |
507 | (value & GDSC_PWR_ON_MASK), | |
508 | GDSC_STATUS_POLL_TIMEOUT_US); | |
509 | ||
510 | else | |
511 | ret = readl_poll_timeout(base + map->reg + CFG_GDSCR_OFFSET, | |
512 | value, | |
513 | (value & GDSC_POWER_DOWN_COMPLETE) || | |
514 | !(value & GDSC_PWR_ON_MASK), | |
515 | GDSC_STATUS_POLL_TIMEOUT_US); | |
516 | ||
b563e766 VB |
517 | if (ret == -ETIMEDOUT) |
518 | printf("WARNING: GDSC %lu is stuck during power on/off\n", | |
519 | pwr->id); | |
520 | return ret; | |
521 | } | |
522 | ||
523 | static int qcom_power_on(struct power_domain *pwr) | |
524 | { | |
525 | return qcom_power_set(pwr, true); | |
526 | } | |
527 | ||
528 | static int qcom_power_off(struct power_domain *pwr) | |
529 | { | |
530 | return qcom_power_set(pwr, false); | |
531 | } | |
532 | ||
533 | static const struct power_domain_ops qcom_power_ops = { | |
534 | .on = qcom_power_on, | |
535 | .off = qcom_power_off, | |
536 | }; | |
537 | ||
538 | static int qcom_power_probe(struct udevice *dev) | |
539 | { | |
540 | /* Set our priv pointer to the base address */ | |
541 | dev_set_priv(dev, (void *)dev_read_addr(dev)); | |
542 | ||
543 | return 0; | |
544 | } | |
545 | ||
546 | U_BOOT_DRIVER(qcom_power) = { | |
547 | .name = "qcom_power", | |
548 | .id = UCLASS_POWER_DOMAIN, | |
549 | .ops = &qcom_power_ops, | |
550 | .probe = qcom_power_probe, | |
8670cb40 | 551 | .flags = DM_FLAG_PRE_RELOC, |
b563e766 | 552 | }; |