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