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 | 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 | |
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 | |
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(map: common->regmap, reg: common->reg_swcr1, val: entry->swcr1); |
66 | ccu_update(common, swcr3, PLL_SWCR3_MASK, entry->swcr3); |
67 | } |
68 | |
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 | |
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 | |
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 | */ |
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 | |
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 : -EINVAL; |
126 | } |
127 | |
128 | static long ccu_pll_round_rate(struct clk_hw *hw, unsigned long rate, |
129 | unsigned long *prate) |
130 | { |
131 | struct ccu_pll *pll = hw_to_ccu_pll(hw); |
132 | |
133 | return ccu_pll_lookup_best_rate(pll, rate)->rate; |
134 | } |
135 | |
136 | static int ccu_pll_init(struct clk_hw *hw) |
137 | { |
138 | struct ccu_pll *pll = hw_to_ccu_pll(hw); |
139 | |
140 | if (ccu_pll_lookup_matched_entry(pll)) |
141 | return 0; |
142 | |
143 | ccu_pll_disable(hw); |
144 | ccu_pll_update_param(pll, entry: &pll->config.rate_tbl[0]); |
145 | |
146 | return 0; |
147 | } |
148 | |
149 | const struct clk_ops spacemit_ccu_pll_ops = { |
150 | .init = ccu_pll_init, |
151 | .enable = ccu_pll_enable, |
152 | .disable = ccu_pll_disable, |
153 | .set_rate = ccu_pll_set_rate, |
154 | .recalc_rate = ccu_pll_recalc_rate, |
155 | .round_rate = ccu_pll_round_rate, |
156 | .is_enabled = ccu_pll_is_enabled, |
157 | }; |
158 | |