162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2016 Maxime Ripard 462306a36Sopenharmony_ci * Maxime Ripard <maxime.ripard@free-electrons.com> 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/clk-provider.h> 862306a36Sopenharmony_ci#include <linux/io.h> 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include "ccu_gate.h" 1162306a36Sopenharmony_ci#include "ccu_nkmp.h" 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_cistruct _ccu_nkmp { 1462306a36Sopenharmony_ci unsigned long n, min_n, max_n; 1562306a36Sopenharmony_ci unsigned long k, min_k, max_k; 1662306a36Sopenharmony_ci unsigned long m, min_m, max_m; 1762306a36Sopenharmony_ci unsigned long p, min_p, max_p; 1862306a36Sopenharmony_ci}; 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistatic unsigned long ccu_nkmp_calc_rate(unsigned long parent, 2162306a36Sopenharmony_ci unsigned long n, unsigned long k, 2262306a36Sopenharmony_ci unsigned long m, unsigned long p) 2362306a36Sopenharmony_ci{ 2462306a36Sopenharmony_ci u64 rate = parent; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci rate *= n * k; 2762306a36Sopenharmony_ci do_div(rate, m * p); 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci return rate; 3062306a36Sopenharmony_ci} 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistatic unsigned long ccu_nkmp_find_best(unsigned long parent, unsigned long rate, 3362306a36Sopenharmony_ci struct _ccu_nkmp *nkmp) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci unsigned long best_rate = 0; 3662306a36Sopenharmony_ci unsigned long best_n = 0, best_k = 0, best_m = 0, best_p = 0; 3762306a36Sopenharmony_ci unsigned long _n, _k, _m, _p; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci for (_k = nkmp->min_k; _k <= nkmp->max_k; _k++) { 4062306a36Sopenharmony_ci for (_n = nkmp->min_n; _n <= nkmp->max_n; _n++) { 4162306a36Sopenharmony_ci for (_m = nkmp->min_m; _m <= nkmp->max_m; _m++) { 4262306a36Sopenharmony_ci for (_p = nkmp->min_p; _p <= nkmp->max_p; _p <<= 1) { 4362306a36Sopenharmony_ci unsigned long tmp_rate; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci tmp_rate = ccu_nkmp_calc_rate(parent, 4662306a36Sopenharmony_ci _n, _k, 4762306a36Sopenharmony_ci _m, _p); 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci if (tmp_rate > rate) 5062306a36Sopenharmony_ci continue; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci if ((rate - tmp_rate) < (rate - best_rate)) { 5362306a36Sopenharmony_ci best_rate = tmp_rate; 5462306a36Sopenharmony_ci best_n = _n; 5562306a36Sopenharmony_ci best_k = _k; 5662306a36Sopenharmony_ci best_m = _m; 5762306a36Sopenharmony_ci best_p = _p; 5862306a36Sopenharmony_ci } 5962306a36Sopenharmony_ci } 6062306a36Sopenharmony_ci } 6162306a36Sopenharmony_ci } 6262306a36Sopenharmony_ci } 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci nkmp->n = best_n; 6562306a36Sopenharmony_ci nkmp->k = best_k; 6662306a36Sopenharmony_ci nkmp->m = best_m; 6762306a36Sopenharmony_ci nkmp->p = best_p; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci return best_rate; 7062306a36Sopenharmony_ci} 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_cistatic void ccu_nkmp_disable(struct clk_hw *hw) 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci return ccu_gate_helper_disable(&nkmp->common, nkmp->enable); 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic int ccu_nkmp_enable(struct clk_hw *hw) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci return ccu_gate_helper_enable(&nkmp->common, nkmp->enable); 8462306a36Sopenharmony_ci} 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic int ccu_nkmp_is_enabled(struct clk_hw *hw) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci return ccu_gate_helper_is_enabled(&nkmp->common, nkmp->enable); 9162306a36Sopenharmony_ci} 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_cistatic unsigned long ccu_nkmp_recalc_rate(struct clk_hw *hw, 9462306a36Sopenharmony_ci unsigned long parent_rate) 9562306a36Sopenharmony_ci{ 9662306a36Sopenharmony_ci struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); 9762306a36Sopenharmony_ci unsigned long n, m, k, p, rate; 9862306a36Sopenharmony_ci u32 reg; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci reg = readl(nkmp->common.base + nkmp->common.reg); 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci n = reg >> nkmp->n.shift; 10362306a36Sopenharmony_ci n &= (1 << nkmp->n.width) - 1; 10462306a36Sopenharmony_ci n += nkmp->n.offset; 10562306a36Sopenharmony_ci if (!n) 10662306a36Sopenharmony_ci n++; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci k = reg >> nkmp->k.shift; 10962306a36Sopenharmony_ci k &= (1 << nkmp->k.width) - 1; 11062306a36Sopenharmony_ci k += nkmp->k.offset; 11162306a36Sopenharmony_ci if (!k) 11262306a36Sopenharmony_ci k++; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci m = reg >> nkmp->m.shift; 11562306a36Sopenharmony_ci m &= (1 << nkmp->m.width) - 1; 11662306a36Sopenharmony_ci m += nkmp->m.offset; 11762306a36Sopenharmony_ci if (!m) 11862306a36Sopenharmony_ci m++; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci p = reg >> nkmp->p.shift; 12162306a36Sopenharmony_ci p &= (1 << nkmp->p.width) - 1; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci rate = ccu_nkmp_calc_rate(parent_rate, n, k, m, 1 << p); 12462306a36Sopenharmony_ci if (nkmp->common.features & CCU_FEATURE_FIXED_POSTDIV) 12562306a36Sopenharmony_ci rate /= nkmp->fixed_post_div; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci return rate; 12862306a36Sopenharmony_ci} 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_cistatic long ccu_nkmp_round_rate(struct clk_hw *hw, unsigned long rate, 13162306a36Sopenharmony_ci unsigned long *parent_rate) 13262306a36Sopenharmony_ci{ 13362306a36Sopenharmony_ci struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); 13462306a36Sopenharmony_ci struct _ccu_nkmp _nkmp; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci if (nkmp->common.features & CCU_FEATURE_FIXED_POSTDIV) 13762306a36Sopenharmony_ci rate *= nkmp->fixed_post_div; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci if (nkmp->max_rate && rate > nkmp->max_rate) { 14062306a36Sopenharmony_ci rate = nkmp->max_rate; 14162306a36Sopenharmony_ci if (nkmp->common.features & CCU_FEATURE_FIXED_POSTDIV) 14262306a36Sopenharmony_ci rate /= nkmp->fixed_post_div; 14362306a36Sopenharmony_ci return rate; 14462306a36Sopenharmony_ci } 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci _nkmp.min_n = nkmp->n.min ?: 1; 14762306a36Sopenharmony_ci _nkmp.max_n = nkmp->n.max ?: 1 << nkmp->n.width; 14862306a36Sopenharmony_ci _nkmp.min_k = nkmp->k.min ?: 1; 14962306a36Sopenharmony_ci _nkmp.max_k = nkmp->k.max ?: 1 << nkmp->k.width; 15062306a36Sopenharmony_ci _nkmp.min_m = 1; 15162306a36Sopenharmony_ci _nkmp.max_m = nkmp->m.max ?: 1 << nkmp->m.width; 15262306a36Sopenharmony_ci _nkmp.min_p = 1; 15362306a36Sopenharmony_ci _nkmp.max_p = nkmp->p.max ?: 1 << ((1 << nkmp->p.width) - 1); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci rate = ccu_nkmp_find_best(*parent_rate, rate, &_nkmp); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci if (nkmp->common.features & CCU_FEATURE_FIXED_POSTDIV) 15862306a36Sopenharmony_ci rate = rate / nkmp->fixed_post_div; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci return rate; 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_cistatic int ccu_nkmp_set_rate(struct clk_hw *hw, unsigned long rate, 16462306a36Sopenharmony_ci unsigned long parent_rate) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); 16762306a36Sopenharmony_ci u32 n_mask = 0, k_mask = 0, m_mask = 0, p_mask = 0; 16862306a36Sopenharmony_ci struct _ccu_nkmp _nkmp; 16962306a36Sopenharmony_ci unsigned long flags; 17062306a36Sopenharmony_ci u32 reg; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci if (nkmp->common.features & CCU_FEATURE_FIXED_POSTDIV) 17362306a36Sopenharmony_ci rate = rate * nkmp->fixed_post_div; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci _nkmp.min_n = nkmp->n.min ?: 1; 17662306a36Sopenharmony_ci _nkmp.max_n = nkmp->n.max ?: 1 << nkmp->n.width; 17762306a36Sopenharmony_ci _nkmp.min_k = nkmp->k.min ?: 1; 17862306a36Sopenharmony_ci _nkmp.max_k = nkmp->k.max ?: 1 << nkmp->k.width; 17962306a36Sopenharmony_ci _nkmp.min_m = 1; 18062306a36Sopenharmony_ci _nkmp.max_m = nkmp->m.max ?: 1 << nkmp->m.width; 18162306a36Sopenharmony_ci _nkmp.min_p = 1; 18262306a36Sopenharmony_ci _nkmp.max_p = nkmp->p.max ?: 1 << ((1 << nkmp->p.width) - 1); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci ccu_nkmp_find_best(parent_rate, rate, &_nkmp); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci /* 18762306a36Sopenharmony_ci * If width is 0, GENMASK() macro may not generate expected mask (0) 18862306a36Sopenharmony_ci * as it falls under undefined behaviour by C standard due to shifts 18962306a36Sopenharmony_ci * which are equal or greater than width of left operand. This can 19062306a36Sopenharmony_ci * be easily avoided by explicitly checking if width is 0. 19162306a36Sopenharmony_ci */ 19262306a36Sopenharmony_ci if (nkmp->n.width) 19362306a36Sopenharmony_ci n_mask = GENMASK(nkmp->n.width + nkmp->n.shift - 1, 19462306a36Sopenharmony_ci nkmp->n.shift); 19562306a36Sopenharmony_ci if (nkmp->k.width) 19662306a36Sopenharmony_ci k_mask = GENMASK(nkmp->k.width + nkmp->k.shift - 1, 19762306a36Sopenharmony_ci nkmp->k.shift); 19862306a36Sopenharmony_ci if (nkmp->m.width) 19962306a36Sopenharmony_ci m_mask = GENMASK(nkmp->m.width + nkmp->m.shift - 1, 20062306a36Sopenharmony_ci nkmp->m.shift); 20162306a36Sopenharmony_ci if (nkmp->p.width) 20262306a36Sopenharmony_ci p_mask = GENMASK(nkmp->p.width + nkmp->p.shift - 1, 20362306a36Sopenharmony_ci nkmp->p.shift); 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci spin_lock_irqsave(nkmp->common.lock, flags); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci reg = readl(nkmp->common.base + nkmp->common.reg); 20862306a36Sopenharmony_ci reg &= ~(n_mask | k_mask | m_mask | p_mask); 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci reg |= ((_nkmp.n - nkmp->n.offset) << nkmp->n.shift) & n_mask; 21162306a36Sopenharmony_ci reg |= ((_nkmp.k - nkmp->k.offset) << nkmp->k.shift) & k_mask; 21262306a36Sopenharmony_ci reg |= ((_nkmp.m - nkmp->m.offset) << nkmp->m.shift) & m_mask; 21362306a36Sopenharmony_ci reg |= (ilog2(_nkmp.p) << nkmp->p.shift) & p_mask; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci writel(reg, nkmp->common.base + nkmp->common.reg); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci spin_unlock_irqrestore(nkmp->common.lock, flags); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci ccu_helper_wait_for_lock(&nkmp->common, nkmp->lock); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci return 0; 22262306a36Sopenharmony_ci} 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ciconst struct clk_ops ccu_nkmp_ops = { 22562306a36Sopenharmony_ci .disable = ccu_nkmp_disable, 22662306a36Sopenharmony_ci .enable = ccu_nkmp_enable, 22762306a36Sopenharmony_ci .is_enabled = ccu_nkmp_is_enabled, 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci .recalc_rate = ccu_nkmp_recalc_rate, 23062306a36Sopenharmony_ci .round_rate = ccu_nkmp_round_rate, 23162306a36Sopenharmony_ci .set_rate = ccu_nkmp_set_rate, 23262306a36Sopenharmony_ci}; 23362306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(ccu_nkmp_ops, SUNXI_CCU); 234