xref: /linux/drivers/clk/spacemit/ccu_pll.c (revision 1fd1dc41724319406b0aff221a352a400b0ddfc5)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (c) 2024 SpacemiT Technology Co. Ltd
4  * Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
5  */
6 
7 #include <linux/clk-provider.h>
8 #include <linux/math.h>
9 #include <linux/regmap.h>
10 
11 #include "ccu_common.h"
12 #include "ccu_pll.h"
13 
14 #define PLL_TIMEOUT_US		3000
15 #define PLL_DELAY_US		5
16 
17 #define PLL_SWCR3_EN		((u32)BIT(31))
18 #define PLL_SWCR3_MASK		GENMASK(30, 0)
19 
20 #define PLLA_SWCR2_EN		((u32)BIT(16))
21 #define PLLA_SWCR2_MASK		GENMASK(15, 8)
22 
23 static const struct ccu_pll_rate_tbl *ccu_pll_lookup_best_rate(struct ccu_pll *pll,
24 							       unsigned long rate)
25 {
26 	struct ccu_pll_config *config = &pll->config;
27 	const struct ccu_pll_rate_tbl *best_entry;
28 	unsigned long best_delta = ULONG_MAX;
29 	int i;
30 
31 	for (i = 0; i < config->tbl_num; i++) {
32 		const struct ccu_pll_rate_tbl *entry = &config->rate_tbl[i];
33 		unsigned long delta = abs_diff(entry->rate, rate);
34 
35 		if (delta < best_delta) {
36 			best_delta = delta;
37 			best_entry = entry;
38 		}
39 	}
40 
41 	return best_entry;
42 }
43 
44 static const struct ccu_pll_rate_tbl *ccu_pll_lookup_matched_entry(struct ccu_pll *pll)
45 {
46 	struct ccu_pll_config *config = &pll->config;
47 	u32 swcr1, swcr3;
48 	int i;
49 
50 	swcr1 = ccu_read(&pll->common, swcr1);
51 	swcr3 = ccu_read(&pll->common, swcr3);
52 	swcr3 &= PLL_SWCR3_MASK;
53 
54 	for (i = 0; i < config->tbl_num; i++) {
55 		const struct ccu_pll_rate_tbl *entry = &config->rate_tbl[i];
56 
57 		if (swcr1 == entry->swcr1 && swcr3 == entry->swcr3)
58 			return entry;
59 	}
60 
61 	return NULL;
62 }
63 
64 static void ccu_pll_update_param(struct ccu_pll *pll, const struct ccu_pll_rate_tbl *entry)
65 {
66 	struct ccu_common *common = &pll->common;
67 
68 	regmap_write(common->regmap, common->reg_swcr1, entry->swcr1);
69 	ccu_update(common, swcr3, PLL_SWCR3_MASK, entry->swcr3);
70 }
71 
72 static int ccu_pll_is_enabled(struct clk_hw *hw)
73 {
74 	struct ccu_common *common = hw_to_ccu_common(hw);
75 
76 	return ccu_read(common, swcr3) & PLL_SWCR3_EN;
77 }
78 
79 static int ccu_pll_enable(struct clk_hw *hw)
80 {
81 	struct ccu_pll *pll = hw_to_ccu_pll(hw);
82 	struct ccu_common *common = &pll->common;
83 	unsigned int tmp;
84 
85 	ccu_update(common, swcr3, PLL_SWCR3_EN, PLL_SWCR3_EN);
86 
87 	/* check lock status */
88 	return regmap_read_poll_timeout_atomic(common->lock_regmap,
89 					       pll->config.reg_lock,
90 					       tmp,
91 					       tmp & pll->config.mask_lock,
92 					       PLL_DELAY_US, PLL_TIMEOUT_US);
93 }
94 
95 static void ccu_pll_disable(struct clk_hw *hw)
96 {
97 	struct ccu_common *common = hw_to_ccu_common(hw);
98 
99 	ccu_update(common, swcr3, PLL_SWCR3_EN, 0);
100 }
101 
102 /*
103  * PLLs must be gated before changing rate, which is ensured by
104  * flag CLK_SET_RATE_GATE.
105  */
106 static int ccu_pll_set_rate(struct clk_hw *hw, unsigned long rate,
107 			    unsigned long parent_rate)
108 {
109 	struct ccu_pll *pll = hw_to_ccu_pll(hw);
110 	const struct ccu_pll_rate_tbl *entry;
111 
112 	entry = ccu_pll_lookup_best_rate(pll, rate);
113 	ccu_pll_update_param(pll, entry);
114 
115 	return 0;
116 }
117 
118 static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw,
119 					 unsigned long parent_rate)
120 {
121 	struct ccu_pll *pll = hw_to_ccu_pll(hw);
122 	const struct ccu_pll_rate_tbl *entry;
123 
124 	entry = ccu_pll_lookup_matched_entry(pll);
125 
126 	WARN_ON_ONCE(!entry);
127 
128 	return entry ? entry->rate : 0;
129 }
130 
131 static int ccu_pll_determine_rate(struct clk_hw *hw,
132 				  struct clk_rate_request *req)
133 {
134 	struct ccu_pll *pll = hw_to_ccu_pll(hw);
135 
136 	req->rate = ccu_pll_lookup_best_rate(pll, req->rate)->rate;
137 
138 	return 0;
139 }
140 
141 static int ccu_pll_init(struct clk_hw *hw)
142 {
143 	struct ccu_pll *pll = hw_to_ccu_pll(hw);
144 
145 	if (ccu_pll_lookup_matched_entry(pll))
146 		return 0;
147 
148 	ccu_pll_disable(hw);
149 	ccu_pll_update_param(pll, &pll->config.rate_tbl[0]);
150 
151 	return 0;
152 }
153 
154 static const struct ccu_pll_rate_tbl *ccu_plla_lookup_matched_entry(struct ccu_pll *pll)
155 {
156 	struct ccu_pll_config *config = &pll->config;
157 	const struct ccu_pll_rate_tbl *entry;
158 	u32 i, swcr1, swcr2, swcr3;
159 
160 	swcr1 = ccu_read(&pll->common, swcr1);
161 	swcr2 = ccu_read(&pll->common, swcr2);
162 	swcr2 &= PLLA_SWCR2_MASK;
163 	swcr3 = ccu_read(&pll->common, swcr3);
164 
165 	for (i = 0; i < config->tbl_num; i++) {
166 		entry = &config->rate_tbl[i];
167 
168 		if (swcr1 == entry->swcr1 &&
169 		    swcr2 == entry->swcr2 &&
170 		    swcr3 == entry->swcr3)
171 			return entry;
172 	}
173 
174 	return NULL;
175 }
176 
177 static void ccu_plla_update_param(struct ccu_pll *pll, const struct ccu_pll_rate_tbl *entry)
178 {
179 	struct ccu_common *common = &pll->common;
180 
181 	regmap_write(common->regmap, common->reg_swcr1, entry->swcr1);
182 	regmap_write(common->regmap, common->reg_swcr3, entry->swcr3);
183 	ccu_update(common, swcr2, PLLA_SWCR2_MASK, entry->swcr2);
184 }
185 
186 static int ccu_plla_is_enabled(struct clk_hw *hw)
187 {
188 	struct ccu_common *common = hw_to_ccu_common(hw);
189 
190 	return ccu_read(common, swcr2) & PLLA_SWCR2_EN;
191 }
192 
193 static int ccu_plla_enable(struct clk_hw *hw)
194 {
195 	struct ccu_pll *pll = hw_to_ccu_pll(hw);
196 	struct ccu_common *common = &pll->common;
197 	unsigned int tmp;
198 
199 	ccu_update(common, swcr2, PLLA_SWCR2_EN, PLLA_SWCR2_EN);
200 
201 	/* check lock status */
202 	return regmap_read_poll_timeout_atomic(common->lock_regmap,
203 					       pll->config.reg_lock,
204 					       tmp,
205 					       tmp & pll->config.mask_lock,
206 					       PLL_DELAY_US, PLL_TIMEOUT_US);
207 }
208 
209 static void ccu_plla_disable(struct clk_hw *hw)
210 {
211 	struct ccu_common *common = hw_to_ccu_common(hw);
212 
213 	ccu_update(common, swcr2, PLLA_SWCR2_EN, 0);
214 }
215 
216 /*
217  * PLLAs must be gated before changing rate, which is ensured by
218  * flag CLK_SET_RATE_GATE.
219  */
220 static int ccu_plla_set_rate(struct clk_hw *hw, unsigned long rate,
221 			     unsigned long parent_rate)
222 {
223 	struct ccu_pll *pll = hw_to_ccu_pll(hw);
224 	const struct ccu_pll_rate_tbl *entry;
225 
226 	entry = ccu_pll_lookup_best_rate(pll, rate);
227 	ccu_plla_update_param(pll, entry);
228 
229 	return 0;
230 }
231 
232 static unsigned long ccu_plla_recalc_rate(struct clk_hw *hw,
233 					  unsigned long parent_rate)
234 {
235 	struct ccu_pll *pll = hw_to_ccu_pll(hw);
236 	const struct ccu_pll_rate_tbl *entry;
237 
238 	entry = ccu_plla_lookup_matched_entry(pll);
239 
240 	WARN_ON_ONCE(!entry);
241 
242 	return entry ? entry->rate : 0;
243 }
244 
245 static int ccu_plla_init(struct clk_hw *hw)
246 {
247 	struct ccu_pll *pll = hw_to_ccu_pll(hw);
248 
249 	if (ccu_plla_lookup_matched_entry(pll))
250 		return 0;
251 
252 	ccu_plla_disable(hw);
253 	ccu_plla_update_param(pll, &pll->config.rate_tbl[0]);
254 
255 	return 0;
256 }
257 
258 const struct clk_ops spacemit_ccu_pll_ops = {
259 	.init		= ccu_pll_init,
260 	.enable		= ccu_pll_enable,
261 	.disable	= ccu_pll_disable,
262 	.set_rate	= ccu_pll_set_rate,
263 	.recalc_rate	= ccu_pll_recalc_rate,
264 	.determine_rate = ccu_pll_determine_rate,
265 	.is_enabled	= ccu_pll_is_enabled,
266 };
267 EXPORT_SYMBOL_NS_GPL(spacemit_ccu_pll_ops, "CLK_SPACEMIT");
268 
269 const struct clk_ops spacemit_ccu_plla_ops = {
270 	.init		= ccu_plla_init,
271 	.enable		= ccu_plla_enable,
272 	.disable	= ccu_plla_disable,
273 	.set_rate	= ccu_plla_set_rate,
274 	.recalc_rate	= ccu_plla_recalc_rate,
275 	.determine_rate	= ccu_pll_determine_rate,
276 	.is_enabled	= ccu_plla_is_enabled,
277 };
278 EXPORT_SYMBOL_NS_GPL(spacemit_ccu_plla_ops, "CLK_SPACEMIT");
279