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(common->regmap, common->reg_swcr1, 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 : 0; 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, &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