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