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 void 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 57 static void ccu_nm_disable(struct clk_hw *hw) 58 { 59 struct ccu_nm *nm = hw_to_ccu_nm(hw); 60 61 return ccu_gate_helper_disable(&nm->common, nm->enable); 62 } 63 64 static int ccu_nm_enable(struct clk_hw *hw) 65 { 66 struct ccu_nm *nm = hw_to_ccu_nm(hw); 67 68 return ccu_gate_helper_enable(&nm->common, nm->enable); 69 } 70 71 static int ccu_nm_is_enabled(struct clk_hw *hw) 72 { 73 struct ccu_nm *nm = hw_to_ccu_nm(hw); 74 75 return ccu_gate_helper_is_enabled(&nm->common, nm->enable); 76 } 77 78 static unsigned long ccu_nm_recalc_rate(struct clk_hw *hw, 79 unsigned long parent_rate) 80 { 81 struct ccu_nm *nm = hw_to_ccu_nm(hw); 82 unsigned long rate; 83 unsigned long n, m; 84 u32 reg; 85 86 if (ccu_frac_helper_is_enabled(&nm->common, &nm->frac)) { 87 rate = ccu_frac_helper_read_rate(&nm->common, &nm->frac); 88 89 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 90 rate /= nm->fixed_post_div; 91 92 return rate; 93 } 94 95 reg = readl(nm->common.base + nm->common.reg); 96 97 n = reg >> nm->n.shift; 98 n &= (1 << nm->n.width) - 1; 99 n += nm->n.offset; 100 if (!n) 101 n++; 102 103 m = reg >> nm->m.shift; 104 m &= (1 << nm->m.width) - 1; 105 m += nm->m.offset; 106 if (!m) 107 m++; 108 109 if (ccu_sdm_helper_is_enabled(&nm->common, &nm->sdm)) 110 rate = ccu_sdm_helper_read_rate(&nm->common, &nm->sdm, m, n); 111 else 112 rate = ccu_nm_calc_rate(parent_rate, n, m); 113 114 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 115 rate /= nm->fixed_post_div; 116 117 return rate; 118 } 119 120 static long ccu_nm_round_rate(struct clk_hw *hw, unsigned long rate, 121 unsigned long *parent_rate) 122 { 123 struct ccu_nm *nm = hw_to_ccu_nm(hw); 124 struct _ccu_nm _nm; 125 126 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 127 rate *= nm->fixed_post_div; 128 129 if (rate < nm->min_rate) { 130 rate = nm->min_rate; 131 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 132 rate /= nm->fixed_post_div; 133 return rate; 134 } 135 136 if (nm->max_rate && rate > nm->max_rate) { 137 rate = nm->max_rate; 138 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 139 rate /= nm->fixed_post_div; 140 return rate; 141 } 142 143 if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) { 144 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 145 rate /= nm->fixed_post_div; 146 return rate; 147 } 148 149 if (ccu_sdm_helper_has_rate(&nm->common, &nm->sdm, rate)) { 150 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 151 rate /= nm->fixed_post_div; 152 return rate; 153 } 154 155 _nm.min_n = nm->n.min ?: 1; 156 _nm.max_n = nm->n.max ?: 1 << nm->n.width; 157 _nm.min_m = 1; 158 _nm.max_m = nm->m.max ?: 1 << nm->m.width; 159 160 ccu_nm_find_best(*parent_rate, rate, &_nm); 161 rate = ccu_nm_calc_rate(*parent_rate, _nm.n, _nm.m); 162 163 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 164 rate /= nm->fixed_post_div; 165 166 return rate; 167 } 168 169 static int ccu_nm_set_rate(struct clk_hw *hw, unsigned long rate, 170 unsigned long parent_rate) 171 { 172 struct ccu_nm *nm = hw_to_ccu_nm(hw); 173 struct _ccu_nm _nm; 174 unsigned long flags; 175 u32 reg; 176 177 /* Adjust target rate according to post-dividers */ 178 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 179 rate = rate * nm->fixed_post_div; 180 181 if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) { 182 spin_lock_irqsave(nm->common.lock, flags); 183 184 /* most SoCs require M to be 0 if fractional mode is used */ 185 reg = readl(nm->common.base + nm->common.reg); 186 reg &= ~GENMASK(nm->m.width + nm->m.shift - 1, nm->m.shift); 187 writel(reg, nm->common.base + nm->common.reg); 188 189 spin_unlock_irqrestore(nm->common.lock, flags); 190 191 ccu_frac_helper_enable(&nm->common, &nm->frac); 192 193 return ccu_frac_helper_set_rate(&nm->common, &nm->frac, 194 rate, nm->lock); 195 } else { 196 ccu_frac_helper_disable(&nm->common, &nm->frac); 197 } 198 199 _nm.min_n = nm->n.min ?: 1; 200 _nm.max_n = nm->n.max ?: 1 << nm->n.width; 201 _nm.min_m = 1; 202 _nm.max_m = nm->m.max ?: 1 << nm->m.width; 203 204 if (ccu_sdm_helper_has_rate(&nm->common, &nm->sdm, rate)) { 205 ccu_sdm_helper_enable(&nm->common, &nm->sdm, rate); 206 207 /* Sigma delta modulation requires specific N and M factors */ 208 ccu_sdm_helper_get_factors(&nm->common, &nm->sdm, rate, 209 &_nm.m, &_nm.n); 210 } else { 211 ccu_sdm_helper_disable(&nm->common, &nm->sdm); 212 ccu_nm_find_best(parent_rate, rate, &_nm); 213 } 214 215 spin_lock_irqsave(nm->common.lock, flags); 216 217 reg = readl(nm->common.base + nm->common.reg); 218 reg &= ~GENMASK(nm->n.width + nm->n.shift - 1, nm->n.shift); 219 reg &= ~GENMASK(nm->m.width + nm->m.shift - 1, nm->m.shift); 220 221 reg |= (_nm.n - nm->n.offset) << nm->n.shift; 222 reg |= (_nm.m - nm->m.offset) << nm->m.shift; 223 writel(reg, nm->common.base + nm->common.reg); 224 225 spin_unlock_irqrestore(nm->common.lock, flags); 226 227 ccu_helper_wait_for_lock(&nm->common, nm->lock); 228 229 return 0; 230 } 231 232 const struct clk_ops ccu_nm_ops = { 233 .disable = ccu_nm_disable, 234 .enable = ccu_nm_enable, 235 .is_enabled = ccu_nm_is_enabled, 236 237 .recalc_rate = ccu_nm_recalc_rate, 238 .round_rate = ccu_nm_round_rate, 239 .set_rate = ccu_nm_set_rate, 240 }; 241