0001
0002
0003
0004
0005
0006
0007 #include <linux/clk-provider.h>
0008 #include <linux/io.h>
0009
0010 #include "ccu_gate.h"
0011 #include "ccu_nk.h"
0012
0013 struct _ccu_nk {
0014 unsigned long n, min_n, max_n;
0015 unsigned long k, min_k, max_k;
0016 };
0017
0018 static void ccu_nk_find_best(unsigned long parent, unsigned long rate,
0019 struct _ccu_nk *nk)
0020 {
0021 unsigned long best_rate = 0;
0022 unsigned int best_k = 0, best_n = 0;
0023 unsigned int _k, _n;
0024
0025 for (_k = nk->min_k; _k <= nk->max_k; _k++) {
0026 for (_n = nk->min_n; _n <= nk->max_n; _n++) {
0027 unsigned long tmp_rate = parent * _n * _k;
0028
0029 if (tmp_rate > rate)
0030 continue;
0031
0032 if ((rate - tmp_rate) < (rate - best_rate)) {
0033 best_rate = tmp_rate;
0034 best_k = _k;
0035 best_n = _n;
0036 }
0037 }
0038 }
0039
0040 nk->k = best_k;
0041 nk->n = best_n;
0042 }
0043
0044 static void ccu_nk_disable(struct clk_hw *hw)
0045 {
0046 struct ccu_nk *nk = hw_to_ccu_nk(hw);
0047
0048 return ccu_gate_helper_disable(&nk->common, nk->enable);
0049 }
0050
0051 static int ccu_nk_enable(struct clk_hw *hw)
0052 {
0053 struct ccu_nk *nk = hw_to_ccu_nk(hw);
0054
0055 return ccu_gate_helper_enable(&nk->common, nk->enable);
0056 }
0057
0058 static int ccu_nk_is_enabled(struct clk_hw *hw)
0059 {
0060 struct ccu_nk *nk = hw_to_ccu_nk(hw);
0061
0062 return ccu_gate_helper_is_enabled(&nk->common, nk->enable);
0063 }
0064
0065 static unsigned long ccu_nk_recalc_rate(struct clk_hw *hw,
0066 unsigned long parent_rate)
0067 {
0068 struct ccu_nk *nk = hw_to_ccu_nk(hw);
0069 unsigned long rate, n, k;
0070 u32 reg;
0071
0072 reg = readl(nk->common.base + nk->common.reg);
0073
0074 n = reg >> nk->n.shift;
0075 n &= (1 << nk->n.width) - 1;
0076 n += nk->n.offset;
0077 if (!n)
0078 n++;
0079
0080 k = reg >> nk->k.shift;
0081 k &= (1 << nk->k.width) - 1;
0082 k += nk->k.offset;
0083 if (!k)
0084 k++;
0085
0086 rate = parent_rate * n * k;
0087 if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV)
0088 rate /= nk->fixed_post_div;
0089
0090 return rate;
0091 }
0092
0093 static long ccu_nk_round_rate(struct clk_hw *hw, unsigned long rate,
0094 unsigned long *parent_rate)
0095 {
0096 struct ccu_nk *nk = hw_to_ccu_nk(hw);
0097 struct _ccu_nk _nk;
0098
0099 if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV)
0100 rate *= nk->fixed_post_div;
0101
0102 _nk.min_n = nk->n.min ?: 1;
0103 _nk.max_n = nk->n.max ?: 1 << nk->n.width;
0104 _nk.min_k = nk->k.min ?: 1;
0105 _nk.max_k = nk->k.max ?: 1 << nk->k.width;
0106
0107 ccu_nk_find_best(*parent_rate, rate, &_nk);
0108 rate = *parent_rate * _nk.n * _nk.k;
0109
0110 if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV)
0111 rate = rate / nk->fixed_post_div;
0112
0113 return rate;
0114 }
0115
0116 static int ccu_nk_set_rate(struct clk_hw *hw, unsigned long rate,
0117 unsigned long parent_rate)
0118 {
0119 struct ccu_nk *nk = hw_to_ccu_nk(hw);
0120 unsigned long flags;
0121 struct _ccu_nk _nk;
0122 u32 reg;
0123
0124 if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV)
0125 rate = rate * nk->fixed_post_div;
0126
0127 _nk.min_n = nk->n.min ?: 1;
0128 _nk.max_n = nk->n.max ?: 1 << nk->n.width;
0129 _nk.min_k = nk->k.min ?: 1;
0130 _nk.max_k = nk->k.max ?: 1 << nk->k.width;
0131
0132 ccu_nk_find_best(parent_rate, rate, &_nk);
0133
0134 spin_lock_irqsave(nk->common.lock, flags);
0135
0136 reg = readl(nk->common.base + nk->common.reg);
0137 reg &= ~GENMASK(nk->n.width + nk->n.shift - 1, nk->n.shift);
0138 reg &= ~GENMASK(nk->k.width + nk->k.shift - 1, nk->k.shift);
0139
0140 reg |= (_nk.k - nk->k.offset) << nk->k.shift;
0141 reg |= (_nk.n - nk->n.offset) << nk->n.shift;
0142 writel(reg, nk->common.base + nk->common.reg);
0143
0144 spin_unlock_irqrestore(nk->common.lock, flags);
0145
0146 ccu_helper_wait_for_lock(&nk->common, nk->lock);
0147
0148 return 0;
0149 }
0150
0151 const struct clk_ops ccu_nk_ops = {
0152 .disable = ccu_nk_disable,
0153 .enable = ccu_nk_enable,
0154 .is_enabled = ccu_nk_is_enabled,
0155
0156 .recalc_rate = ccu_nk_recalc_rate,
0157 .round_rate = ccu_nk_round_rate,
0158 .set_rate = ccu_nk_set_rate,
0159 };
0160 EXPORT_SYMBOL_NS_GPL(ccu_nk_ops, SUNXI_CCU);