1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * OMAP3 Clock init |
4 | * |
5 | * Copyright (C) 2013 Texas Instruments, Inc |
6 | * Tero Kristo (t-kristo@ti.com) |
7 | */ |
8 | |
9 | #include <linux/kernel.h> |
10 | #include <linux/list.h> |
11 | #include <linux/clk.h> |
12 | #include <linux/clk-provider.h> |
13 | #include <linux/clk/ti.h> |
14 | |
15 | #include "clock.h" |
16 | |
17 | #define OMAP3430ES2_ST_DSS_IDLE_SHIFT 1 |
18 | #define OMAP3430ES2_ST_HSOTGUSB_IDLE_SHIFT 5 |
19 | #define OMAP3430ES2_ST_SSI_IDLE_SHIFT 8 |
20 | |
21 | #define OMAP34XX_CM_IDLEST_VAL 1 |
22 | |
23 | /* |
24 | * In AM35xx IPSS, the {ICK,FCK} enable bits for modules are exported |
25 | * in the same register at a bit offset of 0x8. The EN_ACK for ICK is |
26 | * at an offset of 4 from ICK enable bit. |
27 | */ |
28 | #define AM35XX_IPSS_ICK_MASK 0xF |
29 | #define AM35XX_IPSS_ICK_EN_ACK_OFFSET 0x4 |
30 | #define AM35XX_IPSS_ICK_FCK_OFFSET 0x8 |
31 | #define AM35XX_IPSS_CLK_IDLEST_VAL 0 |
32 | |
33 | #define AM35XX_ST_IPSS_SHIFT 5 |
34 | |
35 | /** |
36 | * omap3430es2_clk_ssi_find_idlest - return CM_IDLEST info for SSI |
37 | * @clk: struct clk * being enabled |
38 | * @idlest_reg: void __iomem ** to store CM_IDLEST reg address into |
39 | * @idlest_bit: pointer to a u8 to store the CM_IDLEST bit shift into |
40 | * @idlest_val: pointer to a u8 to store the CM_IDLEST indicator |
41 | * |
42 | * The OMAP3430ES2 SSI target CM_IDLEST bit is at a different shift |
43 | * from the CM_{I,F}CLKEN bit. Pass back the correct info via |
44 | * @idlest_reg and @idlest_bit. No return value. |
45 | */ |
46 | static void omap3430es2_clk_ssi_find_idlest(struct clk_hw_omap *clk, |
47 | struct clk_omap_reg *idlest_reg, |
48 | u8 *idlest_bit, |
49 | u8 *idlest_val) |
50 | { |
51 | memcpy(idlest_reg, &clk->enable_reg, sizeof(*idlest_reg)); |
52 | idlest_reg->offset &= ~0xf0; |
53 | idlest_reg->offset |= 0x20; |
54 | *idlest_bit = OMAP3430ES2_ST_SSI_IDLE_SHIFT; |
55 | *idlest_val = OMAP34XX_CM_IDLEST_VAL; |
56 | } |
57 | |
58 | const struct clk_hw_omap_ops clkhwops_omap3430es2_iclk_ssi_wait = { |
59 | .allow_idle = omap2_clkt_iclk_allow_idle, |
60 | .deny_idle = omap2_clkt_iclk_deny_idle, |
61 | .find_idlest = omap3430es2_clk_ssi_find_idlest, |
62 | .find_companion = omap2_clk_dflt_find_companion, |
63 | }; |
64 | |
65 | /** |
66 | * omap3430es2_clk_dss_usbhost_find_idlest - CM_IDLEST info for DSS, USBHOST |
67 | * @clk: struct clk * being enabled |
68 | * @idlest_reg: void __iomem ** to store CM_IDLEST reg address into |
69 | * @idlest_bit: pointer to a u8 to store the CM_IDLEST bit shift into |
70 | * @idlest_val: pointer to a u8 to store the CM_IDLEST indicator |
71 | * |
72 | * Some OMAP modules on OMAP3 ES2+ chips have both initiator and |
73 | * target IDLEST bits. For our purposes, we are concerned with the |
74 | * target IDLEST bits, which exist at a different bit position than |
75 | * the *CLKEN bit position for these modules (DSS and USBHOST) (The |
76 | * default find_idlest code assumes that they are at the same |
77 | * position.) No return value. |
78 | */ |
79 | static void |
80 | omap3430es2_clk_dss_usbhost_find_idlest(struct clk_hw_omap *clk, |
81 | struct clk_omap_reg *idlest_reg, |
82 | u8 *idlest_bit, u8 *idlest_val) |
83 | { |
84 | memcpy(idlest_reg, &clk->enable_reg, sizeof(*idlest_reg)); |
85 | |
86 | idlest_reg->offset &= ~0xf0; |
87 | idlest_reg->offset |= 0x20; |
88 | /* USBHOST_IDLE has same shift */ |
89 | *idlest_bit = OMAP3430ES2_ST_DSS_IDLE_SHIFT; |
90 | *idlest_val = OMAP34XX_CM_IDLEST_VAL; |
91 | } |
92 | |
93 | const struct clk_hw_omap_ops clkhwops_omap3430es2_dss_usbhost_wait = { |
94 | .find_idlest = omap3430es2_clk_dss_usbhost_find_idlest, |
95 | .find_companion = omap2_clk_dflt_find_companion, |
96 | }; |
97 | |
98 | const struct clk_hw_omap_ops clkhwops_omap3430es2_iclk_dss_usbhost_wait = { |
99 | .allow_idle = omap2_clkt_iclk_allow_idle, |
100 | .deny_idle = omap2_clkt_iclk_deny_idle, |
101 | .find_idlest = omap3430es2_clk_dss_usbhost_find_idlest, |
102 | .find_companion = omap2_clk_dflt_find_companion, |
103 | }; |
104 | |
105 | /** |
106 | * omap3430es2_clk_hsotgusb_find_idlest - return CM_IDLEST info for HSOTGUSB |
107 | * @clk: struct clk * being enabled |
108 | * @idlest_reg: void __iomem ** to store CM_IDLEST reg address into |
109 | * @idlest_bit: pointer to a u8 to store the CM_IDLEST bit shift into |
110 | * @idlest_val: pointer to a u8 to store the CM_IDLEST indicator |
111 | * |
112 | * The OMAP3430ES2 HSOTGUSB target CM_IDLEST bit is at a different |
113 | * shift from the CM_{I,F}CLKEN bit. Pass back the correct info via |
114 | * @idlest_reg and @idlest_bit. No return value. |
115 | */ |
116 | static void |
117 | omap3430es2_clk_hsotgusb_find_idlest(struct clk_hw_omap *clk, |
118 | struct clk_omap_reg *idlest_reg, |
119 | u8 *idlest_bit, |
120 | u8 *idlest_val) |
121 | { |
122 | memcpy(idlest_reg, &clk->enable_reg, sizeof(*idlest_reg)); |
123 | idlest_reg->offset &= ~0xf0; |
124 | idlest_reg->offset |= 0x20; |
125 | *idlest_bit = OMAP3430ES2_ST_HSOTGUSB_IDLE_SHIFT; |
126 | *idlest_val = OMAP34XX_CM_IDLEST_VAL; |
127 | } |
128 | |
129 | const struct clk_hw_omap_ops clkhwops_omap3430es2_iclk_hsotgusb_wait = { |
130 | .allow_idle = omap2_clkt_iclk_allow_idle, |
131 | .deny_idle = omap2_clkt_iclk_deny_idle, |
132 | .find_idlest = omap3430es2_clk_hsotgusb_find_idlest, |
133 | .find_companion = omap2_clk_dflt_find_companion, |
134 | }; |
135 | |
136 | /** |
137 | * am35xx_clk_find_idlest - return clock ACK info for AM35XX IPSS |
138 | * @clk: struct clk * being enabled |
139 | * @idlest_reg: void __iomem ** to store CM_IDLEST reg address into |
140 | * @idlest_bit: pointer to a u8 to store the CM_IDLEST bit shift into |
141 | * @idlest_val: pointer to a u8 to store the CM_IDLEST indicator |
142 | * |
143 | * The interface clocks on AM35xx IPSS reflects the clock idle status |
144 | * in the enable register itsel at a bit offset of 4 from the enable |
145 | * bit. A value of 1 indicates that clock is enabled. |
146 | */ |
147 | static void am35xx_clk_find_idlest(struct clk_hw_omap *clk, |
148 | struct clk_omap_reg *idlest_reg, |
149 | u8 *idlest_bit, |
150 | u8 *idlest_val) |
151 | { |
152 | memcpy(idlest_reg, &clk->enable_reg, sizeof(*idlest_reg)); |
153 | *idlest_bit = clk->enable_bit + AM35XX_IPSS_ICK_EN_ACK_OFFSET; |
154 | *idlest_val = AM35XX_IPSS_CLK_IDLEST_VAL; |
155 | } |
156 | |
157 | /** |
158 | * am35xx_clk_find_companion - find companion clock to @clk |
159 | * @clk: struct clk * to find the companion clock of |
160 | * @other_reg: void __iomem ** to return the companion clock CM_*CLKEN va in |
161 | * @other_bit: u8 ** to return the companion clock bit shift in |
162 | * |
163 | * Some clocks don't have companion clocks. For example, modules with |
164 | * only an interface clock (such as HECC) don't have a companion |
165 | * clock. Right now, this code relies on the hardware exporting a bit |
166 | * in the correct companion register that indicates that the |
167 | * nonexistent 'companion clock' is active. Future patches will |
168 | * associate this type of code with per-module data structures to |
169 | * avoid this issue, and remove the casts. No return value. |
170 | */ |
171 | static void am35xx_clk_find_companion(struct clk_hw_omap *clk, |
172 | struct clk_omap_reg *other_reg, |
173 | u8 *other_bit) |
174 | { |
175 | memcpy(other_reg, &clk->enable_reg, sizeof(*other_reg)); |
176 | if (clk->enable_bit & AM35XX_IPSS_ICK_MASK) |
177 | *other_bit = clk->enable_bit + AM35XX_IPSS_ICK_FCK_OFFSET; |
178 | else |
179 | *other_bit = clk->enable_bit - AM35XX_IPSS_ICK_FCK_OFFSET; |
180 | } |
181 | |
182 | const struct clk_hw_omap_ops clkhwops_am35xx_ipss_module_wait = { |
183 | .find_idlest = am35xx_clk_find_idlest, |
184 | .find_companion = am35xx_clk_find_companion, |
185 | }; |
186 | |
187 | /** |
188 | * am35xx_clk_ipss_find_idlest - return CM_IDLEST info for IPSS |
189 | * @clk: struct clk * being enabled |
190 | * @idlest_reg: void __iomem ** to store CM_IDLEST reg address into |
191 | * @idlest_bit: pointer to a u8 to store the CM_IDLEST bit shift into |
192 | * @idlest_val: pointer to a u8 to store the CM_IDLEST indicator |
193 | * |
194 | * The IPSS target CM_IDLEST bit is at a different shift from the |
195 | * CM_{I,F}CLKEN bit. Pass back the correct info via @idlest_reg |
196 | * and @idlest_bit. No return value. |
197 | */ |
198 | static void am35xx_clk_ipss_find_idlest(struct clk_hw_omap *clk, |
199 | struct clk_omap_reg *idlest_reg, |
200 | u8 *idlest_bit, |
201 | u8 *idlest_val) |
202 | { |
203 | memcpy(idlest_reg, &clk->enable_reg, sizeof(*idlest_reg)); |
204 | |
205 | idlest_reg->offset &= ~0xf0; |
206 | idlest_reg->offset |= 0x20; |
207 | *idlest_bit = AM35XX_ST_IPSS_SHIFT; |
208 | *idlest_val = OMAP34XX_CM_IDLEST_VAL; |
209 | } |
210 | |
211 | const struct clk_hw_omap_ops clkhwops_am35xx_ipss_wait = { |
212 | .allow_idle = omap2_clkt_iclk_allow_idle, |
213 | .deny_idle = omap2_clkt_iclk_deny_idle, |
214 | .find_idlest = am35xx_clk_ipss_find_idlest, |
215 | .find_companion = omap2_clk_dflt_find_companion, |
216 | }; |
217 | |
218 | static struct ti_dt_clk omap3xxx_clks[] = { |
219 | DT_CLK(NULL, "timer_32k_ck" , "omap_32k_fck" ), |
220 | DT_CLK(NULL, "timer_sys_ck" , "sys_ck" ), |
221 | { .node_name = NULL }, |
222 | }; |
223 | |
224 | static struct ti_dt_clk omap36xx_omap3430es2plus_clks[] = { |
225 | DT_CLK(NULL, "ssi_ssr_fck" , "ssi_ssr_fck_3430es2" ), |
226 | DT_CLK(NULL, "ssi_sst_fck" , "ssi_sst_fck_3430es2" ), |
227 | DT_CLK(NULL, "hsotgusb_ick" , "hsotgusb_ick_3430es2" ), |
228 | DT_CLK(NULL, "ssi_ick" , "ssi_ick_3430es2" ), |
229 | { .node_name = NULL }, |
230 | }; |
231 | |
232 | static struct ti_dt_clk omap3430es1_clks[] = { |
233 | DT_CLK(NULL, "ssi_ssr_fck" , "ssi_ssr_fck_3430es1" ), |
234 | DT_CLK(NULL, "ssi_sst_fck" , "ssi_sst_fck_3430es1" ), |
235 | DT_CLK(NULL, "hsotgusb_ick" , "hsotgusb_ick_3430es1" ), |
236 | DT_CLK(NULL, "ssi_ick" , "ssi_ick_3430es1" ), |
237 | DT_CLK(NULL, "dss1_alwon_fck" , "dss1_alwon_fck_3430es1" ), |
238 | DT_CLK(NULL, "dss_ick" , "dss_ick_3430es1" ), |
239 | { .node_name = NULL }, |
240 | }; |
241 | |
242 | static struct ti_dt_clk omap36xx_am35xx_omap3430es2plus_clks[] = { |
243 | DT_CLK(NULL, "dss1_alwon_fck" , "dss1_alwon_fck_3430es2" ), |
244 | DT_CLK(NULL, "dss_ick" , "dss_ick_3430es2" ), |
245 | { .node_name = NULL }, |
246 | }; |
247 | |
248 | static struct ti_dt_clk am35xx_clks[] = { |
249 | DT_CLK(NULL, "hsotgusb_ick" , "hsotgusb_ick_am35xx" ), |
250 | DT_CLK(NULL, "hsotgusb_fck" , "hsotgusb_fck_am35xx" ), |
251 | DT_CLK(NULL, "uart4_ick" , "uart4_ick_am35xx" ), |
252 | DT_CLK(NULL, "uart4_fck" , "uart4_fck_am35xx" ), |
253 | { .node_name = NULL }, |
254 | }; |
255 | |
256 | static const char *enable_init_clks[] = { |
257 | "sdrc_ick" , |
258 | "gpmc_fck" , |
259 | "omapctrl_ick" , |
260 | }; |
261 | |
262 | enum { |
263 | OMAP3_SOC_AM35XX, |
264 | OMAP3_SOC_OMAP3430_ES1, |
265 | OMAP3_SOC_OMAP3430_ES2_PLUS, |
266 | OMAP3_SOC_OMAP3630, |
267 | }; |
268 | |
269 | /** |
270 | * omap3_clk_lock_dpll5 - locks DPLL5 |
271 | * |
272 | * Locks DPLL5 to a pre-defined frequency. This is required for proper |
273 | * operation of USB. |
274 | */ |
275 | void __init omap3_clk_lock_dpll5(void) |
276 | { |
277 | struct clk *dpll5_clk; |
278 | struct clk *dpll5_m2_clk; |
279 | |
280 | /* |
281 | * Errata sprz319f advisory 2.1 documents a USB host clock drift issue |
282 | * that can be worked around using specially crafted dpll5 settings |
283 | * with a dpll5_m2 divider set to 8. Set the dpll5 rate to 8x the USB |
284 | * host clock rate, its .set_rate handler() will detect that frequency |
285 | * and use the errata settings. |
286 | */ |
287 | dpll5_clk = clk_get(NULL, id: "dpll5_ck" ); |
288 | clk_set_rate(clk: dpll5_clk, OMAP3_DPLL5_FREQ_FOR_USBHOST * 8); |
289 | clk_prepare_enable(clk: dpll5_clk); |
290 | |
291 | /* Program dpll5_m2_clk divider */ |
292 | dpll5_m2_clk = clk_get(NULL, id: "dpll5_m2_ck" ); |
293 | clk_prepare_enable(clk: dpll5_m2_clk); |
294 | clk_set_rate(clk: dpll5_m2_clk, OMAP3_DPLL5_FREQ_FOR_USBHOST); |
295 | |
296 | clk_disable_unprepare(clk: dpll5_m2_clk); |
297 | clk_disable_unprepare(clk: dpll5_clk); |
298 | } |
299 | |
300 | static int __init omap3xxx_dt_clk_init(int soc_type) |
301 | { |
302 | if (soc_type == OMAP3_SOC_AM35XX || soc_type == OMAP3_SOC_OMAP3630 || |
303 | soc_type == OMAP3_SOC_OMAP3430_ES1 || |
304 | soc_type == OMAP3_SOC_OMAP3430_ES2_PLUS) |
305 | ti_dt_clocks_register(oclks: omap3xxx_clks); |
306 | |
307 | if (soc_type == OMAP3_SOC_AM35XX) |
308 | ti_dt_clocks_register(oclks: am35xx_clks); |
309 | |
310 | if (soc_type == OMAP3_SOC_OMAP3630 || soc_type == OMAP3_SOC_AM35XX || |
311 | soc_type == OMAP3_SOC_OMAP3430_ES2_PLUS) |
312 | ti_dt_clocks_register(oclks: omap36xx_am35xx_omap3430es2plus_clks); |
313 | |
314 | if (soc_type == OMAP3_SOC_OMAP3430_ES1) |
315 | ti_dt_clocks_register(oclks: omap3430es1_clks); |
316 | |
317 | if (soc_type == OMAP3_SOC_OMAP3430_ES2_PLUS || |
318 | soc_type == OMAP3_SOC_OMAP3630) |
319 | ti_dt_clocks_register(oclks: omap36xx_omap3430es2plus_clks); |
320 | |
321 | omap2_clk_disable_autoidle_all(); |
322 | |
323 | ti_clk_add_aliases(); |
324 | |
325 | omap2_clk_enable_init_clocks(clk_names: enable_init_clks, |
326 | ARRAY_SIZE(enable_init_clks)); |
327 | |
328 | pr_info("Clocking rate (Crystal/Core/MPU): %ld.%01ld/%ld/%ld MHz\n" , |
329 | (clk_get_rate(clk_get_sys(NULL, "osc_sys_ck" )) / 1000000), |
330 | (clk_get_rate(clk_get_sys(NULL, "osc_sys_ck" )) / 100000) % 10, |
331 | (clk_get_rate(clk_get_sys(NULL, "core_ck" )) / 1000000), |
332 | (clk_get_rate(clk_get_sys(NULL, "arm_fck" )) / 1000000)); |
333 | |
334 | if (soc_type != OMAP3_SOC_OMAP3430_ES1) |
335 | omap3_clk_lock_dpll5(); |
336 | |
337 | return 0; |
338 | } |
339 | |
340 | int __init omap3430_dt_clk_init(void) |
341 | { |
342 | return omap3xxx_dt_clk_init(soc_type: OMAP3_SOC_OMAP3430_ES2_PLUS); |
343 | } |
344 | |
345 | int __init omap3630_dt_clk_init(void) |
346 | { |
347 | return omap3xxx_dt_clk_init(soc_type: OMAP3_SOC_OMAP3630); |
348 | } |
349 | |
350 | int __init am35xx_dt_clk_init(void) |
351 | { |
352 | return omap3xxx_dt_clk_init(soc_type: OMAP3_SOC_AM35XX); |
353 | } |
354 | |