1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // Copyright (C) 2014 Broadcom Corporation |
3 | |
4 | #include <linux/kernel.h> |
5 | #include <linux/slab.h> |
6 | #include <linux/err.h> |
7 | #include <linux/clk-provider.h> |
8 | #include <linux/io.h> |
9 | #include <linux/of.h> |
10 | #include <linux/clkdev.h> |
11 | #include <linux/of_address.h> |
12 | |
13 | #include "clk-iproc.h" |
14 | |
15 | #define IPROC_CLK_MAX_FREQ_POLICY 0x3 |
16 | #define IPROC_CLK_POLICY_FREQ_OFFSET 0x008 |
17 | #define IPROC_CLK_POLICY_FREQ_POLICY_FREQ_SHIFT 8 |
18 | #define IPROC_CLK_POLICY_FREQ_POLICY_FREQ_MASK 0x7 |
19 | |
20 | #define IPROC_CLK_PLLARMA_OFFSET 0xc00 |
21 | #define IPROC_CLK_PLLARMA_LOCK_SHIFT 28 |
22 | #define IPROC_CLK_PLLARMA_PDIV_SHIFT 24 |
23 | #define IPROC_CLK_PLLARMA_PDIV_MASK 0xf |
24 | #define IPROC_CLK_PLLARMA_NDIV_INT_SHIFT 8 |
25 | #define IPROC_CLK_PLLARMA_NDIV_INT_MASK 0x3ff |
26 | |
27 | #define IPROC_CLK_PLLARMB_OFFSET 0xc04 |
28 | #define IPROC_CLK_PLLARMB_NDIV_FRAC_MASK 0xfffff |
29 | |
30 | #define IPROC_CLK_PLLARMC_OFFSET 0xc08 |
31 | #define IPROC_CLK_PLLARMC_BYPCLK_EN_SHIFT 8 |
32 | #define IPROC_CLK_PLLARMC_MDIV_MASK 0xff |
33 | |
34 | #define IPROC_CLK_PLLARMCTL5_OFFSET 0xc20 |
35 | #define IPROC_CLK_PLLARMCTL5_H_MDIV_MASK 0xff |
36 | |
37 | #define IPROC_CLK_PLLARM_OFFSET_OFFSET 0xc24 |
38 | #define IPROC_CLK_PLLARM_SW_CTL_SHIFT 29 |
39 | #define IPROC_CLK_PLLARM_NDIV_INT_OFFSET_SHIFT 20 |
40 | #define IPROC_CLK_PLLARM_NDIV_INT_OFFSET_MASK 0xff |
41 | #define IPROC_CLK_PLLARM_NDIV_FRAC_OFFSET_MASK 0xfffff |
42 | |
43 | #define IPROC_CLK_ARM_DIV_OFFSET 0xe00 |
44 | #define IPROC_CLK_ARM_DIV_PLL_SELECT_OVERRIDE_SHIFT 4 |
45 | #define IPROC_CLK_ARM_DIV_ARM_PLL_SELECT_MASK 0xf |
46 | |
47 | #define IPROC_CLK_POLICY_DBG_OFFSET 0xec0 |
48 | #define IPROC_CLK_POLICY_DBG_ACT_FREQ_SHIFT 12 |
49 | #define IPROC_CLK_POLICY_DBG_ACT_FREQ_MASK 0x7 |
50 | |
51 | enum iproc_arm_pll_fid { |
52 | ARM_PLL_FID_CRYSTAL_CLK = 0, |
53 | ARM_PLL_FID_SYS_CLK = 2, |
54 | ARM_PLL_FID_CH0_SLOW_CLK = 6, |
55 | ARM_PLL_FID_CH1_FAST_CLK = 7 |
56 | }; |
57 | |
58 | struct iproc_arm_pll { |
59 | struct clk_hw hw; |
60 | void __iomem *base; |
61 | unsigned long rate; |
62 | }; |
63 | |
64 | #define to_iproc_arm_pll(hw) container_of(hw, struct iproc_arm_pll, hw) |
65 | |
66 | static unsigned int __get_fid(struct iproc_arm_pll *pll) |
67 | { |
68 | u32 val; |
69 | unsigned int policy, fid, active_fid; |
70 | |
71 | val = readl(addr: pll->base + IPROC_CLK_ARM_DIV_OFFSET); |
72 | if (val & (1 << IPROC_CLK_ARM_DIV_PLL_SELECT_OVERRIDE_SHIFT)) |
73 | policy = val & IPROC_CLK_ARM_DIV_ARM_PLL_SELECT_MASK; |
74 | else |
75 | policy = 0; |
76 | |
77 | /* something is seriously wrong */ |
78 | BUG_ON(policy > IPROC_CLK_MAX_FREQ_POLICY); |
79 | |
80 | val = readl(addr: pll->base + IPROC_CLK_POLICY_FREQ_OFFSET); |
81 | fid = (val >> (IPROC_CLK_POLICY_FREQ_POLICY_FREQ_SHIFT * policy)) & |
82 | IPROC_CLK_POLICY_FREQ_POLICY_FREQ_MASK; |
83 | |
84 | val = readl(addr: pll->base + IPROC_CLK_POLICY_DBG_OFFSET); |
85 | active_fid = IPROC_CLK_POLICY_DBG_ACT_FREQ_MASK & |
86 | (val >> IPROC_CLK_POLICY_DBG_ACT_FREQ_SHIFT); |
87 | if (fid != active_fid) { |
88 | pr_debug("%s: fid override %u->%u\n" , __func__, fid, |
89 | active_fid); |
90 | fid = active_fid; |
91 | } |
92 | |
93 | pr_debug("%s: active fid: %u\n" , __func__, fid); |
94 | |
95 | return fid; |
96 | } |
97 | |
98 | /* |
99 | * Determine the mdiv (post divider) based on the frequency ID being used. |
100 | * There are 4 sources that can be used to derive the output clock rate: |
101 | * - 25 MHz Crystal |
102 | * - System clock |
103 | * - PLL channel 0 (slow clock) |
104 | * - PLL channel 1 (fast clock) |
105 | */ |
106 | static int __get_mdiv(struct iproc_arm_pll *pll) |
107 | { |
108 | unsigned int fid; |
109 | int mdiv; |
110 | u32 val; |
111 | |
112 | fid = __get_fid(pll); |
113 | |
114 | switch (fid) { |
115 | case ARM_PLL_FID_CRYSTAL_CLK: |
116 | case ARM_PLL_FID_SYS_CLK: |
117 | mdiv = 1; |
118 | break; |
119 | |
120 | case ARM_PLL_FID_CH0_SLOW_CLK: |
121 | val = readl(addr: pll->base + IPROC_CLK_PLLARMC_OFFSET); |
122 | mdiv = val & IPROC_CLK_PLLARMC_MDIV_MASK; |
123 | if (mdiv == 0) |
124 | mdiv = 256; |
125 | break; |
126 | |
127 | case ARM_PLL_FID_CH1_FAST_CLK: |
128 | val = readl(addr: pll->base + IPROC_CLK_PLLARMCTL5_OFFSET); |
129 | mdiv = val & IPROC_CLK_PLLARMCTL5_H_MDIV_MASK; |
130 | if (mdiv == 0) |
131 | mdiv = 256; |
132 | break; |
133 | |
134 | default: |
135 | mdiv = -EFAULT; |
136 | } |
137 | |
138 | return mdiv; |
139 | } |
140 | |
141 | static unsigned int __get_ndiv(struct iproc_arm_pll *pll) |
142 | { |
143 | u32 val; |
144 | unsigned int ndiv_int, ndiv_frac, ndiv; |
145 | |
146 | val = readl(addr: pll->base + IPROC_CLK_PLLARM_OFFSET_OFFSET); |
147 | if (val & (1 << IPROC_CLK_PLLARM_SW_CTL_SHIFT)) { |
148 | /* |
149 | * offset mode is active. Read the ndiv from the PLLARM OFFSET |
150 | * register |
151 | */ |
152 | ndiv_int = (val >> IPROC_CLK_PLLARM_NDIV_INT_OFFSET_SHIFT) & |
153 | IPROC_CLK_PLLARM_NDIV_INT_OFFSET_MASK; |
154 | if (ndiv_int == 0) |
155 | ndiv_int = 256; |
156 | |
157 | ndiv_frac = val & IPROC_CLK_PLLARM_NDIV_FRAC_OFFSET_MASK; |
158 | } else { |
159 | /* offset mode not active */ |
160 | val = readl(addr: pll->base + IPROC_CLK_PLLARMA_OFFSET); |
161 | ndiv_int = (val >> IPROC_CLK_PLLARMA_NDIV_INT_SHIFT) & |
162 | IPROC_CLK_PLLARMA_NDIV_INT_MASK; |
163 | if (ndiv_int == 0) |
164 | ndiv_int = 1024; |
165 | |
166 | val = readl(addr: pll->base + IPROC_CLK_PLLARMB_OFFSET); |
167 | ndiv_frac = val & IPROC_CLK_PLLARMB_NDIV_FRAC_MASK; |
168 | } |
169 | |
170 | ndiv = (ndiv_int << 20) | ndiv_frac; |
171 | |
172 | return ndiv; |
173 | } |
174 | |
175 | /* |
176 | * The output frequency of the ARM PLL is calculated based on the ARM PLL |
177 | * divider values: |
178 | * pdiv = ARM PLL pre-divider |
179 | * ndiv = ARM PLL multiplier |
180 | * mdiv = ARM PLL post divider |
181 | * |
182 | * The frequency is calculated by: |
183 | * ((ndiv * parent clock rate) / pdiv) / mdiv |
184 | */ |
185 | static unsigned long iproc_arm_pll_recalc_rate(struct clk_hw *hw, |
186 | unsigned long parent_rate) |
187 | { |
188 | struct iproc_arm_pll *pll = to_iproc_arm_pll(hw); |
189 | u32 val; |
190 | int mdiv; |
191 | u64 ndiv; |
192 | unsigned int pdiv; |
193 | |
194 | /* in bypass mode, use parent rate */ |
195 | val = readl(addr: pll->base + IPROC_CLK_PLLARMC_OFFSET); |
196 | if (val & (1 << IPROC_CLK_PLLARMC_BYPCLK_EN_SHIFT)) { |
197 | pll->rate = parent_rate; |
198 | return pll->rate; |
199 | } |
200 | |
201 | /* PLL needs to be locked */ |
202 | val = readl(addr: pll->base + IPROC_CLK_PLLARMA_OFFSET); |
203 | if (!(val & (1 << IPROC_CLK_PLLARMA_LOCK_SHIFT))) { |
204 | pll->rate = 0; |
205 | return 0; |
206 | } |
207 | |
208 | pdiv = (val >> IPROC_CLK_PLLARMA_PDIV_SHIFT) & |
209 | IPROC_CLK_PLLARMA_PDIV_MASK; |
210 | if (pdiv == 0) |
211 | pdiv = 16; |
212 | |
213 | ndiv = __get_ndiv(pll); |
214 | mdiv = __get_mdiv(pll); |
215 | if (mdiv <= 0) { |
216 | pll->rate = 0; |
217 | return 0; |
218 | } |
219 | pll->rate = (ndiv * parent_rate) >> 20; |
220 | pll->rate = (pll->rate / pdiv) / mdiv; |
221 | |
222 | pr_debug("%s: ARM PLL rate: %lu. parent rate: %lu\n" , __func__, |
223 | pll->rate, parent_rate); |
224 | pr_debug("%s: ndiv_int: %u, pdiv: %u, mdiv: %d\n" , __func__, |
225 | (unsigned int)(ndiv >> 20), pdiv, mdiv); |
226 | |
227 | return pll->rate; |
228 | } |
229 | |
230 | static const struct clk_ops iproc_arm_pll_ops = { |
231 | .recalc_rate = iproc_arm_pll_recalc_rate, |
232 | }; |
233 | |
234 | void __init iproc_armpll_setup(struct device_node *node) |
235 | { |
236 | int ret; |
237 | struct iproc_arm_pll *pll; |
238 | struct clk_init_data init; |
239 | const char *parent_name; |
240 | |
241 | pll = kzalloc(size: sizeof(*pll), GFP_KERNEL); |
242 | if (WARN_ON(!pll)) |
243 | return; |
244 | |
245 | pll->base = of_iomap(node, index: 0); |
246 | if (WARN_ON(!pll->base)) |
247 | goto err_free_pll; |
248 | |
249 | init.name = node->name; |
250 | init.ops = &iproc_arm_pll_ops; |
251 | init.flags = 0; |
252 | parent_name = of_clk_get_parent_name(np: node, index: 0); |
253 | init.parent_names = (parent_name ? &parent_name : NULL); |
254 | init.num_parents = (parent_name ? 1 : 0); |
255 | pll->hw.init = &init; |
256 | |
257 | ret = clk_hw_register(NULL, hw: &pll->hw); |
258 | if (WARN_ON(ret)) |
259 | goto err_iounmap; |
260 | |
261 | ret = of_clk_add_hw_provider(np: node, get: of_clk_hw_simple_get, data: &pll->hw); |
262 | if (WARN_ON(ret)) |
263 | goto err_clk_unregister; |
264 | |
265 | return; |
266 | |
267 | err_clk_unregister: |
268 | clk_hw_unregister(hw: &pll->hw); |
269 | err_iounmap: |
270 | iounmap(addr: pll->base); |
271 | err_free_pll: |
272 | kfree(objp: pll); |
273 | } |
274 | |