1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Copyright (C) 2016 Maxime Ripard 4 * Maxime Ripard <maxime.ripard@free-electrons.com> 5 */ 6 7 #include <linux/clk-provider.h> 8 #include <linux/io.h> 9 10 #include "ccu_frac.h" 11 #include "ccu_gate.h" 12 #include "ccu_nm.h" 13 14 struct _ccu_nm { 15 unsigned long n, min_n, max_n; 16 unsigned long m, min_m, max_m; 17 }; 18 19 static unsigned long ccu_nm_calc_rate(unsigned long parent, 20 unsigned long n, unsigned long m) 21 { 22 u64 rate = parent; 23 24 rate *= n; 25 do_div(rate, m); 26 27 return rate; 28 } 29 30 static unsigned long ccu_nm_find_best(unsigned long parent, unsigned long rate, 31 struct _ccu_nm *nm) 32 { 33 unsigned long best_rate = 0; 34 unsigned long best_n = 0, best_m = 0; 35 unsigned long _n, _m; 36 37 for (_n = nm->min_n; _n <= nm->max_n; _n++) { 38 for (_m = nm->min_m; _m <= nm->max_m; _m++) { 39 unsigned long tmp_rate = ccu_nm_calc_rate(parent, 40 _n, _m); 41 42 if (tmp_rate > rate) 43 continue; 44 45 if ((rate - tmp_rate) < (rate - best_rate)) { 46 best_rate = tmp_rate; 47 best_n = _n; 48 best_m = _m; 49 } 50 } 51 } 52 53 nm->n = best_n; 54 nm->m = best_m; 55 56 return best_rate; 57 } 58 59 static void ccu_nm_disable(struct clk_hw *hw) 60 { 61 struct ccu_nm *nm = hw_to_ccu_nm(hw); 62 63 return ccu_gate_helper_disable(&nm->common, nm->enable); 64 } 65 66 static int ccu_nm_enable(struct clk_hw *hw) 67 { 68 struct ccu_nm *nm = hw_to_ccu_nm(hw); 69 70 return ccu_gate_helper_enable(&nm->common, nm->enable); 71 } 72 73 static int ccu_nm_is_enabled(struct clk_hw *hw) 74 { 75 struct ccu_nm *nm = hw_to_ccu_nm(hw); 76 77 return ccu_gate_helper_is_enabled(&nm->common, nm->enable); 78 } 79 80 static unsigned long ccu_nm_recalc_rate(struct clk_hw *hw, 81 unsigned long parent_rate) 82 { 83 struct ccu_nm *nm = hw_to_ccu_nm(hw); 84 unsigned long rate; 85 unsigned long n, m; 86 u32 reg; 87 88 if (ccu_frac_helper_is_enabled(&nm->common, &nm->frac)) { 89 rate = ccu_frac_helper_read_rate(&nm->common, &nm->frac); 90 91 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 92 rate /= nm->fixed_post_div; 93 94 return rate; 95 } 96 97 reg = readl(nm->common.base + nm->common.reg); 98 99 n = reg >> nm->n.shift; 100 n &= (1 << nm->n.width) - 1; 101 n += nm->n.offset; 102 if (!n) 103 n++; 104 105 m = reg >> nm->m.shift; 106 m &= (1 << nm->m.width) - 1; 107 m += nm->m.offset; 108 if (!m) 109 m++; 110 111 if (ccu_sdm_helper_is_enabled(&nm->common, &nm->sdm)) 112 rate = ccu_sdm_helper_read_rate(&nm->common, &nm->sdm, m, n); 113 else 114 rate = ccu_nm_calc_rate(parent_rate, n, m); 115 116 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 117 rate /= nm->fixed_post_div; 118 119 return rate; 120 } 121 122 static long ccu_nm_round_rate(struct clk_hw *hw, unsigned long rate, 123 unsigned long *parent_rate) 124 { 125 struct ccu_nm *nm = hw_to_ccu_nm(hw); 126 struct _ccu_nm _nm; 127 128 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 129 rate *= nm->fixed_post_div; 130 131 if (rate < nm->min_rate) { 132 rate = nm->min_rate; 133 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 134 rate /= nm->fixed_post_div; 135 return rate; 136 } 137 138 if (nm->max_rate && rate > nm->max_rate) { 139 rate = nm->max_rate; 140 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 141 rate /= nm->fixed_post_div; 142 return rate; 143 } 144 145 if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) { 146 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 147 rate /= nm->fixed_post_div; 148 return rate; 149 } 150 151 if (ccu_sdm_helper_has_rate(&nm->common, &nm->sdm, rate)) { 152 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 153 rate /= nm->fixed_post_div; 154 return rate; 155 } 156 157 _nm.min_n = nm->n.min ?: 1; 158 _nm.max_n = nm->n.max ?: 1 << nm->n.width; 159 _nm.min_m = 1; 160 _nm.max_m = nm->m.max ?: 1 << nm->m.width; 161 162 rate = ccu_nm_find_best(*parent_rate, rate, &_nm); 163 164 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 165 rate /= nm->fixed_post_div; 166 167 return rate; 168 } 169 170 static int ccu_nm_set_rate(struct clk_hw *hw, unsigned long rate, 171 unsigned long parent_rate) 172 { 173 struct ccu_nm *nm = hw_to_ccu_nm(hw); 174 struct _ccu_nm _nm; 175 unsigned long flags; 176 u32 reg; 177 178 /* Adjust target rate according to post-dividers */ 179 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 180 rate = rate * nm->fixed_post_div; 181 182 if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) { 183 spin_lock_irqsave(nm->common.lock, flags); 184 185 /* most SoCs require M to be 0 if fractional mode is used */ 186 reg = readl(nm->common.base + nm->common.reg); 187 reg &= ~GENMASK(nm->m.width + nm->m.shift - 1, nm->m.shift); 188 writel(reg, nm->common.base + nm->common.reg); 189 190 spin_unlock_irqrestore(nm->common.lock, flags); 191 192 ccu_frac_helper_enable(&nm->common, &nm->frac); 193 194 return ccu_frac_helper_set_rate(&nm->common, &nm->frac, 195 rate, nm->lock); 196 } else { 197 ccu_frac_helper_disable(&nm->common, &nm->frac); 198 } 199 200 _nm.min_n = nm->n.min ?: 1; 201 _nm.max_n = nm->n.max ?: 1 << nm->n.width; 202 _nm.min_m = 1; 203 _nm.max_m = nm->m.max ?: 1 << nm->m.width; 204 205 if (ccu_sdm_helper_has_rate(&nm->common, &nm->sdm, rate)) { 206 ccu_sdm_helper_enable(&nm->common, &nm->sdm, rate); 207 208 /* Sigma delta modulation requires specific N and M factors */ 209 ccu_sdm_helper_get_factors(&nm->common, &nm->sdm, rate, 210 &_nm.m, &_nm.n); 211 } else { 212 ccu_sdm_helper_disable(&nm->common, &nm->sdm); 213 ccu_nm_find_best(parent_rate, rate, &_nm); 214 } 215 216 spin_lock_irqsave(nm->common.lock, flags); 217 218 reg = readl(nm->common.base + nm->common.reg); 219 reg &= ~GENMASK(nm->n.width + nm->n.shift - 1, nm->n.shift); 220 reg &= ~GENMASK(nm->m.width + nm->m.shift - 1, nm->m.shift); 221 222 reg |= (_nm.n - nm->n.offset) << nm->n.shift; 223 reg |= (_nm.m - nm->m.offset) << nm->m.shift; 224 writel(reg, nm->common.base + nm->common.reg); 225 226 spin_unlock_irqrestore(nm->common.lock, flags); 227 228 ccu_helper_wait_for_lock(&nm->common, nm->lock); 229 230 return 0; 231 } 232 233 const struct clk_ops ccu_nm_ops = { 234 .disable = ccu_nm_disable, 235 .enable = ccu_nm_enable, 236 .is_enabled = ccu_nm_is_enabled, 237 238 .recalc_rate = ccu_nm_recalc_rate, 239 .round_rate = ccu_nm_round_rate, 240 .set_rate = ccu_nm_set_rate, 241 }; 242 EXPORT_SYMBOL_NS_GPL(ccu_nm_ops, SUNXI_CCU); 243