Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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
23static 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
44static 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
64static 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
72static 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
79static 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
95static 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 */
106static 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
118static 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
131static 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
141static 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
154static 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
177static 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
186static 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
193static 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
209static 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 */
220static 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
232static 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
245static 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
258const 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};
267EXPORT_SYMBOL_NS_GPL(spacemit_ccu_pll_ops, "CLK_SPACEMIT");
268
269const 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};
278EXPORT_SYMBOL_NS_GPL(spacemit_ccu_plla_ops, "CLK_SPACEMIT");