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
ccu_pll_lookup_best_rate(struct ccu_pll * pll,unsigned long rate)20 static const struct ccu_pll_rate_tbl *ccu_pll_lookup_best_rate(struct ccu_pll *pll,
21 unsigned long rate)
22 {
23 struct ccu_pll_config *config = &pll->config;
24 const struct ccu_pll_rate_tbl *best_entry;
25 unsigned long best_delta = ULONG_MAX;
26 int i;
27
28 for (i = 0; i < config->tbl_num; i++) {
29 const struct ccu_pll_rate_tbl *entry = &config->rate_tbl[i];
30 unsigned long delta = abs_diff(entry->rate, rate);
31
32 if (delta < best_delta) {
33 best_delta = delta;
34 best_entry = entry;
35 }
36 }
37
38 return best_entry;
39 }
40
ccu_pll_lookup_matched_entry(struct ccu_pll * pll)41 static const struct ccu_pll_rate_tbl *ccu_pll_lookup_matched_entry(struct ccu_pll *pll)
42 {
43 struct ccu_pll_config *config = &pll->config;
44 u32 swcr1, swcr3;
45 int i;
46
47 swcr1 = ccu_read(&pll->common, swcr1);
48 swcr3 = ccu_read(&pll->common, swcr3);
49 swcr3 &= PLL_SWCR3_MASK;
50
51 for (i = 0; i < config->tbl_num; i++) {
52 const struct ccu_pll_rate_tbl *entry = &config->rate_tbl[i];
53
54 if (swcr1 == entry->swcr1 && swcr3 == entry->swcr3)
55 return entry;
56 }
57
58 return NULL;
59 }
60
ccu_pll_update_param(struct ccu_pll * pll,const struct ccu_pll_rate_tbl * entry)61 static void ccu_pll_update_param(struct ccu_pll *pll, const struct ccu_pll_rate_tbl *entry)
62 {
63 struct ccu_common *common = &pll->common;
64
65 regmap_write(common->regmap, common->reg_swcr1, entry->swcr1);
66 ccu_update(common, swcr3, PLL_SWCR3_MASK, entry->swcr3);
67 }
68
ccu_pll_is_enabled(struct clk_hw * hw)69 static int ccu_pll_is_enabled(struct clk_hw *hw)
70 {
71 struct ccu_common *common = hw_to_ccu_common(hw);
72
73 return ccu_read(common, swcr3) & PLL_SWCR3_EN;
74 }
75
ccu_pll_enable(struct clk_hw * hw)76 static int ccu_pll_enable(struct clk_hw *hw)
77 {
78 struct ccu_pll *pll = hw_to_ccu_pll(hw);
79 struct ccu_common *common = &pll->common;
80 unsigned int tmp;
81
82 ccu_update(common, swcr3, PLL_SWCR3_EN, PLL_SWCR3_EN);
83
84 /* check lock status */
85 return regmap_read_poll_timeout_atomic(common->lock_regmap,
86 pll->config.reg_lock,
87 tmp,
88 tmp & pll->config.mask_lock,
89 PLL_DELAY_US, PLL_TIMEOUT_US);
90 }
91
ccu_pll_disable(struct clk_hw * hw)92 static void ccu_pll_disable(struct clk_hw *hw)
93 {
94 struct ccu_common *common = hw_to_ccu_common(hw);
95
96 ccu_update(common, swcr3, PLL_SWCR3_EN, 0);
97 }
98
99 /*
100 * PLLs must be gated before changing rate, which is ensured by
101 * flag CLK_SET_RATE_GATE.
102 */
ccu_pll_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)103 static int ccu_pll_set_rate(struct clk_hw *hw, unsigned long rate,
104 unsigned long parent_rate)
105 {
106 struct ccu_pll *pll = hw_to_ccu_pll(hw);
107 const struct ccu_pll_rate_tbl *entry;
108
109 entry = ccu_pll_lookup_best_rate(pll, rate);
110 ccu_pll_update_param(pll, entry);
111
112 return 0;
113 }
114
ccu_pll_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)115 static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw,
116 unsigned long parent_rate)
117 {
118 struct ccu_pll *pll = hw_to_ccu_pll(hw);
119 const struct ccu_pll_rate_tbl *entry;
120
121 entry = ccu_pll_lookup_matched_entry(pll);
122
123 WARN_ON_ONCE(!entry);
124
125 return entry ? entry->rate : 0;
126 }
127
ccu_pll_determine_rate(struct clk_hw * hw,struct clk_rate_request * req)128 static int ccu_pll_determine_rate(struct clk_hw *hw,
129 struct clk_rate_request *req)
130 {
131 struct ccu_pll *pll = hw_to_ccu_pll(hw);
132
133 req->rate = ccu_pll_lookup_best_rate(pll, req->rate)->rate;
134
135 return 0;
136 }
137
ccu_pll_init(struct clk_hw * hw)138 static int ccu_pll_init(struct clk_hw *hw)
139 {
140 struct ccu_pll *pll = hw_to_ccu_pll(hw);
141
142 if (ccu_pll_lookup_matched_entry(pll))
143 return 0;
144
145 ccu_pll_disable(hw);
146 ccu_pll_update_param(pll, &pll->config.rate_tbl[0]);
147
148 return 0;
149 }
150
151 const struct clk_ops spacemit_ccu_pll_ops = {
152 .init = ccu_pll_init,
153 .enable = ccu_pll_enable,
154 .disable = ccu_pll_disable,
155 .set_rate = ccu_pll_set_rate,
156 .recalc_rate = ccu_pll_recalc_rate,
157 .determine_rate = ccu_pll_determine_rate,
158 .is_enabled = ccu_pll_is_enabled,
159 };
160