FILE
ccu_mix.c
File preview with syntax highlighting.
linux-6.19.14/drivers/clk/spacemit/ccu_mix.c
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 16static 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 23static 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 33static 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 41static 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 49static 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 */ 67static 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 83static int ccu_factor_determine_rate(struct clk_hw *hw, 84 struct clk_rate_request *req) 85{ 86 req->rate = ccu_factor_recalc_rate(hw, req->best_parent_rate); 87 88 return 0; 89} 90 91static int ccu_factor_set_rate(struct clk_hw *hw, unsigned long rate, 92 unsigned long parent_rate) 93{ 94 return 0; 95} 96 97static unsigned long 98ccu_mix_calc_best_rate(struct clk_hw *hw, unsigned long rate, 99 struct clk_hw **best_parent, 100 unsigned long *best_parent_rate, 101 u32 *div_val) 102{ 103 struct ccu_mix *mix = hw_to_ccu_mix(hw); 104 unsigned int parent_num = clk_hw_get_num_parents(hw); 105 struct ccu_div_config *div = &mix->div; 106 u32 div_max = 1 << div->width; 107 unsigned long best_rate = 0; 108 109 for (int i = 0; i < parent_num; i++) { 110 struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i); 111 unsigned long parent_rate; 112 113 if (!parent) 114 continue; 115 116 parent_rate = clk_hw_get_rate(parent); 117 118 for (int j = 1; j <= div_max; j++) { 119 unsigned long tmp = DIV_ROUND_CLOSEST_ULL(parent_rate, j); 120 121 if (abs(tmp - rate) < abs(best_rate - rate)) { 122 best_rate = tmp; 123 124 if (div_val) 125 *div_val = j - 1; 126 127 if (best_parent) { 128 *best_parent = parent; 129 *best_parent_rate = parent_rate; 130 } 131 } 132 } 133 } 134 135 return best_rate; 136} 137 138static int ccu_mix_determine_rate(struct clk_hw *hw, 139 struct clk_rate_request *req) 140{ 141 req->rate = ccu_mix_calc_best_rate(hw, req->rate, 142 &req->best_parent_hw, 143 &req->best_parent_rate, 144 NULL); 145 return 0; 146} 147 148static int ccu_mix_set_rate(struct clk_hw *hw, unsigned long rate, 149 unsigned long parent_rate) 150{ 151 struct ccu_mix *mix = hw_to_ccu_mix(hw); 152 struct ccu_common *common = &mix->common; 153 struct ccu_div_config *div = &mix->div; 154 u32 current_div, target_div, mask; 155 156 ccu_mix_calc_best_rate(hw, rate, NULL, NULL, &target_div); 157 158 current_div = ccu_read(common, ctrl) >> div->shift; 159 current_div &= (1 << div->width) - 1; 160 161 if (current_div == target_div) 162 return 0; 163 164 mask = GENMASK(div->width + div->shift - 1, div->shift); 165 166 ccu_update(common, ctrl, mask, target_div << div->shift); 167 168 return ccu_mix_trigger_fc(hw); 169} 170 171static u8 ccu_mux_get_parent(struct clk_hw *hw) 172{ 173 struct ccu_mix *mix = hw_to_ccu_mix(hw); 174 struct ccu_mux_config *mux = &mix->mux; 175 u8 parent; 176 177 parent = ccu_read(&mix->common, ctrl) >> mux->shift; 178 parent &= (1 << mux->width) - 1; 179 180 return parent; 181} 182 183static int ccu_mux_set_parent(struct clk_hw *hw, u8 index) 184{ 185 struct ccu_mix *mix = hw_to_ccu_mix(hw); 186 struct ccu_mux_config *mux = &mix->mux; 187 u32 mask; 188 189 mask = GENMASK(mux->width + mux->shift - 1, mux->shift); 190 191 ccu_update(&mix->common, ctrl, mask, index << mux->shift); 192 193 return ccu_mix_trigger_fc(hw); 194} 195 196const struct clk_ops spacemit_ccu_gate_ops = { 197 .disable = ccu_gate_disable, 198 .enable = ccu_gate_enable, 199 .is_enabled = ccu_gate_is_enabled, 200}; 201EXPORT_SYMBOL_NS_GPL(spacemit_ccu_gate_ops, "CLK_SPACEMIT"); 202 203const struct clk_ops spacemit_ccu_factor_ops = { 204 .determine_rate = ccu_factor_determine_rate, 205 .recalc_rate = ccu_factor_recalc_rate, 206 .set_rate = ccu_factor_set_rate, 207}; 208EXPORT_SYMBOL_NS_GPL(spacemit_ccu_factor_ops, "CLK_SPACEMIT"); 209 210const struct clk_ops spacemit_ccu_mux_ops = { 211 .determine_rate = ccu_mix_determine_rate, 212 .get_parent = ccu_mux_get_parent, 213 .set_parent = ccu_mux_set_parent, 214}; 215EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_ops, "CLK_SPACEMIT"); 216 217const struct clk_ops spacemit_ccu_div_ops = { 218 .determine_rate = ccu_mix_determine_rate, 219 .recalc_rate = ccu_div_recalc_rate, 220 .set_rate = ccu_mix_set_rate, 221}; 222EXPORT_SYMBOL_NS_GPL(spacemit_ccu_div_ops, "CLK_SPACEMIT"); 223 224const struct clk_ops spacemit_ccu_factor_gate_ops = { 225 .disable = ccu_gate_disable, 226 .enable = ccu_gate_enable, 227 .is_enabled = ccu_gate_is_enabled, 228 229 .determine_rate = ccu_factor_determine_rate, 230 .recalc_rate = ccu_factor_recalc_rate, 231 .set_rate = ccu_factor_set_rate, 232}; 233EXPORT_SYMBOL_NS_GPL(spacemit_ccu_factor_gate_ops, "CLK_SPACEMIT"); 234 235const struct clk_ops spacemit_ccu_mux_gate_ops = { 236 .disable = ccu_gate_disable, 237 .enable = ccu_gate_enable, 238 .is_enabled = ccu_gate_is_enabled, 239 240 .determine_rate = ccu_mix_determine_rate, 241 .get_parent = ccu_mux_get_parent, 242 .set_parent = ccu_mux_set_parent, 243}; 244EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_gate_ops, "CLK_SPACEMIT"); 245 246const struct clk_ops spacemit_ccu_div_gate_ops = { 247 .disable = ccu_gate_disable, 248 .enable = ccu_gate_enable, 249 .is_enabled = ccu_gate_is_enabled, 250 251 .determine_rate = ccu_mix_determine_rate, 252 .recalc_rate = ccu_div_recalc_rate, 253 .set_rate = ccu_mix_set_rate, 254}; 255EXPORT_SYMBOL_NS_GPL(spacemit_ccu_div_gate_ops, "CLK_SPACEMIT"); 256 257const struct clk_ops spacemit_ccu_mux_div_gate_ops = { 258 .disable = ccu_gate_disable, 259 .enable = ccu_gate_enable, 260 .is_enabled = ccu_gate_is_enabled, 261 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}; 269EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_div_gate_ops, "CLK_SPACEMIT"); 270 271const struct clk_ops spacemit_ccu_mux_div_ops = { 272 .get_parent = ccu_mux_get_parent, 273 .set_parent = ccu_mux_set_parent, 274 275 .determine_rate = ccu_mix_determine_rate, 276 .recalc_rate = ccu_div_recalc_rate, 277 .set_rate = ccu_mix_set_rate, 278}; 279EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_div_ops, "CLK_SPACEMIT");
~/
Read-only file preview.