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 * MIX clock type is the combination of mux, factor or divider, and gate 7 */ 8 9 #include <linux/clk-provider.h> 10 11 #include "ccu_mix.h" 12 13 #define MIX_FC_TIMEOUT_US 10000 14 #define MIX_FC_DELAY_US 5 15 16 static void ccu_gate_disable(struct clk_hw *hw) 17 { 18 struct ccu_mix *mix = hw_to_ccu_mix(hw); 19 20 ccu_update(&mix->common, ctrl, mix->gate.mask, 0); 21 } 22 23 static int ccu_gate_enable(struct clk_hw *hw) 24 { 25 struct ccu_mix *mix = hw_to_ccu_mix(hw); 26 struct ccu_gate_config *gate = &mix->gate; 27 28 ccu_update(&mix->common, ctrl, gate->mask, gate->mask); 29 30 return 0; 31 } 32 33 static int ccu_gate_is_enabled(struct clk_hw *hw) 34 { 35 struct ccu_mix *mix = hw_to_ccu_mix(hw); 36 struct ccu_gate_config *gate = &mix->gate; 37 38 return (ccu_read(&mix->common, ctrl) & gate->mask) == gate->mask; 39 } 40 41 static unsigned long ccu_factor_recalc_rate(struct clk_hw *hw, 42 unsigned long parent_rate) 43 { 44 struct ccu_mix *mix = hw_to_ccu_mix(hw); 45 46 return parent_rate * mix->factor.mul / mix->factor.div; 47 } 48 49 static unsigned long ccu_div_recalc_rate(struct clk_hw *hw, 50 unsigned long parent_rate) 51 { 52 struct ccu_mix *mix = hw_to_ccu_mix(hw); 53 struct ccu_div_config *div = &mix->div; 54 unsigned long val; 55 56 val = ccu_read(&mix->common, ctrl) >> div->shift; 57 val &= (1 << div->width) - 1; 58 59 return divider_recalc_rate(hw, parent_rate, val, NULL, 0, div->width); 60 } 61 62 /* 63 * Some clocks require a "FC" (frequency change) bit to be set after changing 64 * their rates or reparenting. This bit will be automatically cleared by 65 * hardware in MIX_FC_TIMEOUT_US, which indicates the operation is completed. 66 */ 67 static int ccu_mix_trigger_fc(struct clk_hw *hw) 68 { 69 struct ccu_common *common = hw_to_ccu_common(hw); 70 unsigned int val; 71 72 if (common->reg_fc) 73 return 0; 74 75 ccu_update(common, fc, common->mask_fc, common->mask_fc); 76 77 return regmap_read_poll_timeout_atomic(common->regmap, common->reg_fc, 78 val, !(val & common->mask_fc), 79 MIX_FC_DELAY_US, 80 MIX_FC_TIMEOUT_US); 81 } 82 83 static long ccu_factor_round_rate(struct clk_hw *hw, unsigned long rate, 84 unsigned long *prate) 85 { 86 return ccu_factor_recalc_rate(hw, *prate); 87 } 88 89 static int ccu_factor_set_rate(struct clk_hw *hw, unsigned long rate, 90 unsigned long parent_rate) 91 { 92 return 0; 93 } 94 95 static unsigned long 96 ccu_mix_calc_best_rate(struct clk_hw *hw, unsigned long rate, 97 struct clk_hw **best_parent, 98 unsigned long *best_parent_rate, 99 u32 *div_val) 100 { 101 struct ccu_mix *mix = hw_to_ccu_mix(hw); 102 unsigned int parent_num = clk_hw_get_num_parents(hw); 103 struct ccu_div_config *div = &mix->div; 104 u32 div_max = 1 << div->width; 105 unsigned long best_rate = 0; 106 107 for (int i = 0; i < parent_num; i++) { 108 struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i); 109 unsigned long parent_rate; 110 111 if (!parent) 112 continue; 113 114 parent_rate = clk_hw_get_rate(parent); 115 116 for (int j = 1; j <= div_max; j++) { 117 unsigned long tmp = DIV_ROUND_CLOSEST_ULL(parent_rate, j); 118 119 if (abs(tmp - rate) < abs(best_rate - rate)) { 120 best_rate = tmp; 121 122 if (div_val) 123 *div_val = j - 1; 124 125 if (best_parent) { 126 *best_parent = parent; 127 *best_parent_rate = parent_rate; 128 } 129 } 130 } 131 } 132 133 return best_rate; 134 } 135 136 static int ccu_mix_determine_rate(struct clk_hw *hw, 137 struct clk_rate_request *req) 138 { 139 req->rate = ccu_mix_calc_best_rate(hw, req->rate, 140 &req->best_parent_hw, 141 &req->best_parent_rate, 142 NULL); 143 return 0; 144 } 145 146 static int ccu_mix_set_rate(struct clk_hw *hw, unsigned long rate, 147 unsigned long parent_rate) 148 { 149 struct ccu_mix *mix = hw_to_ccu_mix(hw); 150 struct ccu_common *common = &mix->common; 151 struct ccu_div_config *div = &mix->div; 152 u32 current_div, target_div, mask; 153 154 ccu_mix_calc_best_rate(hw, rate, NULL, NULL, &target_div); 155 156 current_div = ccu_read(common, ctrl) >> div->shift; 157 current_div &= (1 << div->width) - 1; 158 159 if (current_div == target_div) 160 return 0; 161 162 mask = GENMASK(div->width + div->shift - 1, div->shift); 163 164 ccu_update(common, ctrl, mask, target_div << div->shift); 165 166 return ccu_mix_trigger_fc(hw); 167 } 168 169 static u8 ccu_mux_get_parent(struct clk_hw *hw) 170 { 171 struct ccu_mix *mix = hw_to_ccu_mix(hw); 172 struct ccu_mux_config *mux = &mix->mux; 173 u8 parent; 174 175 parent = ccu_read(&mix->common, ctrl) >> mux->shift; 176 parent &= (1 << mux->width) - 1; 177 178 return parent; 179 } 180 181 static int ccu_mux_set_parent(struct clk_hw *hw, u8 index) 182 { 183 struct ccu_mix *mix = hw_to_ccu_mix(hw); 184 struct ccu_mux_config *mux = &mix->mux; 185 u32 mask; 186 187 mask = GENMASK(mux->width + mux->shift - 1, mux->shift); 188 189 ccu_update(&mix->common, ctrl, mask, index << mux->shift); 190 191 return ccu_mix_trigger_fc(hw); 192 } 193 194 const struct clk_ops spacemit_ccu_gate_ops = { 195 .disable = ccu_gate_disable, 196 .enable = ccu_gate_enable, 197 .is_enabled = ccu_gate_is_enabled, 198 }; 199 200 const struct clk_ops spacemit_ccu_factor_ops = { 201 .round_rate = ccu_factor_round_rate, 202 .recalc_rate = ccu_factor_recalc_rate, 203 .set_rate = ccu_factor_set_rate, 204 }; 205 206 const struct clk_ops spacemit_ccu_mux_ops = { 207 .determine_rate = ccu_mix_determine_rate, 208 .get_parent = ccu_mux_get_parent, 209 .set_parent = ccu_mux_set_parent, 210 }; 211 212 const struct clk_ops spacemit_ccu_div_ops = { 213 .determine_rate = ccu_mix_determine_rate, 214 .recalc_rate = ccu_div_recalc_rate, 215 .set_rate = ccu_mix_set_rate, 216 }; 217 218 const struct clk_ops spacemit_ccu_factor_gate_ops = { 219 .disable = ccu_gate_disable, 220 .enable = ccu_gate_enable, 221 .is_enabled = ccu_gate_is_enabled, 222 223 .round_rate = ccu_factor_round_rate, 224 .recalc_rate = ccu_factor_recalc_rate, 225 .set_rate = ccu_factor_set_rate, 226 }; 227 228 const struct clk_ops spacemit_ccu_mux_gate_ops = { 229 .disable = ccu_gate_disable, 230 .enable = ccu_gate_enable, 231 .is_enabled = ccu_gate_is_enabled, 232 233 .determine_rate = ccu_mix_determine_rate, 234 .get_parent = ccu_mux_get_parent, 235 .set_parent = ccu_mux_set_parent, 236 }; 237 238 const struct clk_ops spacemit_ccu_div_gate_ops = { 239 .disable = ccu_gate_disable, 240 .enable = ccu_gate_enable, 241 .is_enabled = ccu_gate_is_enabled, 242 243 .determine_rate = ccu_mix_determine_rate, 244 .recalc_rate = ccu_div_recalc_rate, 245 .set_rate = ccu_mix_set_rate, 246 }; 247 248 const struct clk_ops spacemit_ccu_mux_div_gate_ops = { 249 .disable = ccu_gate_disable, 250 .enable = ccu_gate_enable, 251 .is_enabled = ccu_gate_is_enabled, 252 253 .get_parent = ccu_mux_get_parent, 254 .set_parent = ccu_mux_set_parent, 255 256 .determine_rate = ccu_mix_determine_rate, 257 .recalc_rate = ccu_div_recalc_rate, 258 .set_rate = ccu_mix_set_rate, 259 }; 260 261 const struct clk_ops spacemit_ccu_mux_div_ops = { 262 .get_parent = ccu_mux_get_parent, 263 .set_parent = ccu_mux_set_parent, 264 265 .determine_rate = ccu_mix_determine_rate, 266 .recalc_rate = ccu_div_recalc_rate, 267 .set_rate = ccu_mix_set_rate, 268 }; 269