0001
0002
0003
0004
0005
0006
0007 #include <linux/clk-provider.h>
0008 #include <linux/io.h>
0009
0010 #include "ccu_frac.h"
0011 #include "ccu_gate.h"
0012 #include "ccu_nm.h"
0013
0014 struct _ccu_nm {
0015 unsigned long n, min_n, max_n;
0016 unsigned long m, min_m, max_m;
0017 };
0018
0019 static unsigned long ccu_nm_calc_rate(unsigned long parent,
0020 unsigned long n, unsigned long m)
0021 {
0022 u64 rate = parent;
0023
0024 rate *= n;
0025 do_div(rate, m);
0026
0027 return rate;
0028 }
0029
0030 static void ccu_nm_find_best(unsigned long parent, unsigned long rate,
0031 struct _ccu_nm *nm)
0032 {
0033 unsigned long best_rate = 0;
0034 unsigned long best_n = 0, best_m = 0;
0035 unsigned long _n, _m;
0036
0037 for (_n = nm->min_n; _n <= nm->max_n; _n++) {
0038 for (_m = nm->min_m; _m <= nm->max_m; _m++) {
0039 unsigned long tmp_rate = ccu_nm_calc_rate(parent,
0040 _n, _m);
0041
0042 if (tmp_rate > rate)
0043 continue;
0044
0045 if ((rate - tmp_rate) < (rate - best_rate)) {
0046 best_rate = tmp_rate;
0047 best_n = _n;
0048 best_m = _m;
0049 }
0050 }
0051 }
0052
0053 nm->n = best_n;
0054 nm->m = best_m;
0055 }
0056
0057 static void ccu_nm_disable(struct clk_hw *hw)
0058 {
0059 struct ccu_nm *nm = hw_to_ccu_nm(hw);
0060
0061 return ccu_gate_helper_disable(&nm->common, nm->enable);
0062 }
0063
0064 static int ccu_nm_enable(struct clk_hw *hw)
0065 {
0066 struct ccu_nm *nm = hw_to_ccu_nm(hw);
0067
0068 return ccu_gate_helper_enable(&nm->common, nm->enable);
0069 }
0070
0071 static int ccu_nm_is_enabled(struct clk_hw *hw)
0072 {
0073 struct ccu_nm *nm = hw_to_ccu_nm(hw);
0074
0075 return ccu_gate_helper_is_enabled(&nm->common, nm->enable);
0076 }
0077
0078 static unsigned long ccu_nm_recalc_rate(struct clk_hw *hw,
0079 unsigned long parent_rate)
0080 {
0081 struct ccu_nm *nm = hw_to_ccu_nm(hw);
0082 unsigned long rate;
0083 unsigned long n, m;
0084 u32 reg;
0085
0086 if (ccu_frac_helper_is_enabled(&nm->common, &nm->frac)) {
0087 rate = ccu_frac_helper_read_rate(&nm->common, &nm->frac);
0088
0089 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
0090 rate /= nm->fixed_post_div;
0091
0092 return rate;
0093 }
0094
0095 reg = readl(nm->common.base + nm->common.reg);
0096
0097 n = reg >> nm->n.shift;
0098 n &= (1 << nm->n.width) - 1;
0099 n += nm->n.offset;
0100 if (!n)
0101 n++;
0102
0103 m = reg >> nm->m.shift;
0104 m &= (1 << nm->m.width) - 1;
0105 m += nm->m.offset;
0106 if (!m)
0107 m++;
0108
0109 if (ccu_sdm_helper_is_enabled(&nm->common, &nm->sdm))
0110 rate = ccu_sdm_helper_read_rate(&nm->common, &nm->sdm, m, n);
0111 else
0112 rate = ccu_nm_calc_rate(parent_rate, n, m);
0113
0114 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
0115 rate /= nm->fixed_post_div;
0116
0117 return rate;
0118 }
0119
0120 static long ccu_nm_round_rate(struct clk_hw *hw, unsigned long rate,
0121 unsigned long *parent_rate)
0122 {
0123 struct ccu_nm *nm = hw_to_ccu_nm(hw);
0124 struct _ccu_nm _nm;
0125
0126 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
0127 rate *= nm->fixed_post_div;
0128
0129 if (rate < nm->min_rate) {
0130 rate = nm->min_rate;
0131 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
0132 rate /= nm->fixed_post_div;
0133 return rate;
0134 }
0135
0136 if (nm->max_rate && rate > nm->max_rate) {
0137 rate = nm->max_rate;
0138 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
0139 rate /= nm->fixed_post_div;
0140 return rate;
0141 }
0142
0143 if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) {
0144 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
0145 rate /= nm->fixed_post_div;
0146 return rate;
0147 }
0148
0149 if (ccu_sdm_helper_has_rate(&nm->common, &nm->sdm, rate)) {
0150 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
0151 rate /= nm->fixed_post_div;
0152 return rate;
0153 }
0154
0155 _nm.min_n = nm->n.min ?: 1;
0156 _nm.max_n = nm->n.max ?: 1 << nm->n.width;
0157 _nm.min_m = 1;
0158 _nm.max_m = nm->m.max ?: 1 << nm->m.width;
0159
0160 ccu_nm_find_best(*parent_rate, rate, &_nm);
0161 rate = ccu_nm_calc_rate(*parent_rate, _nm.n, _nm.m);
0162
0163 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
0164 rate /= nm->fixed_post_div;
0165
0166 return rate;
0167 }
0168
0169 static int ccu_nm_set_rate(struct clk_hw *hw, unsigned long rate,
0170 unsigned long parent_rate)
0171 {
0172 struct ccu_nm *nm = hw_to_ccu_nm(hw);
0173 struct _ccu_nm _nm;
0174 unsigned long flags;
0175 u32 reg;
0176
0177
0178 if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
0179 rate = rate * nm->fixed_post_div;
0180
0181 if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) {
0182 spin_lock_irqsave(nm->common.lock, flags);
0183
0184
0185 reg = readl(nm->common.base + nm->common.reg);
0186 reg &= ~GENMASK(nm->m.width + nm->m.shift - 1, nm->m.shift);
0187 writel(reg, nm->common.base + nm->common.reg);
0188
0189 spin_unlock_irqrestore(nm->common.lock, flags);
0190
0191 ccu_frac_helper_enable(&nm->common, &nm->frac);
0192
0193 return ccu_frac_helper_set_rate(&nm->common, &nm->frac,
0194 rate, nm->lock);
0195 } else {
0196 ccu_frac_helper_disable(&nm->common, &nm->frac);
0197 }
0198
0199 _nm.min_n = nm->n.min ?: 1;
0200 _nm.max_n = nm->n.max ?: 1 << nm->n.width;
0201 _nm.min_m = 1;
0202 _nm.max_m = nm->m.max ?: 1 << nm->m.width;
0203
0204 if (ccu_sdm_helper_has_rate(&nm->common, &nm->sdm, rate)) {
0205 ccu_sdm_helper_enable(&nm->common, &nm->sdm, rate);
0206
0207
0208 ccu_sdm_helper_get_factors(&nm->common, &nm->sdm, rate,
0209 &_nm.m, &_nm.n);
0210 } else {
0211 ccu_sdm_helper_disable(&nm->common, &nm->sdm);
0212 ccu_nm_find_best(parent_rate, rate, &_nm);
0213 }
0214
0215 spin_lock_irqsave(nm->common.lock, flags);
0216
0217 reg = readl(nm->common.base + nm->common.reg);
0218 reg &= ~GENMASK(nm->n.width + nm->n.shift - 1, nm->n.shift);
0219 reg &= ~GENMASK(nm->m.width + nm->m.shift - 1, nm->m.shift);
0220
0221 reg |= (_nm.n - nm->n.offset) << nm->n.shift;
0222 reg |= (_nm.m - nm->m.offset) << nm->m.shift;
0223 writel(reg, nm->common.base + nm->common.reg);
0224
0225 spin_unlock_irqrestore(nm->common.lock, flags);
0226
0227 ccu_helper_wait_for_lock(&nm->common, nm->lock);
0228
0229 return 0;
0230 }
0231
0232 const struct clk_ops ccu_nm_ops = {
0233 .disable = ccu_nm_disable,
0234 .enable = ccu_nm_enable,
0235 .is_enabled = ccu_nm_is_enabled,
0236
0237 .recalc_rate = ccu_nm_recalc_rate,
0238 .round_rate = ccu_nm_round_rate,
0239 .set_rate = ccu_nm_set_rate,
0240 };
0241 EXPORT_SYMBOL_NS_GPL(ccu_nm_ops, SUNXI_CCU);