Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Copyright (C) 2016 Maxime Ripard
0004  * Maxime Ripard <maxime.ripard@free-electrons.com>
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     /* Adjust target rate according to post-dividers */
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         /* most SoCs require M to be 0 if fractional mode is used */
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         /* Sigma delta modulation requires specific N and M factors */
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);