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_frac.h" 1162306a36Sopenharmony_ci#include "ccu_gate.h" 1262306a36Sopenharmony_ci#include "ccu_nm.h" 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_cistruct _ccu_nm { 1562306a36Sopenharmony_ci unsigned long n, min_n, max_n; 1662306a36Sopenharmony_ci unsigned long m, min_m, max_m; 1762306a36Sopenharmony_ci}; 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cistatic unsigned long ccu_nm_calc_rate(unsigned long parent, 2062306a36Sopenharmony_ci unsigned long n, unsigned long m) 2162306a36Sopenharmony_ci{ 2262306a36Sopenharmony_ci u64 rate = parent; 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci rate *= n; 2562306a36Sopenharmony_ci do_div(rate, m); 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci return rate; 2862306a36Sopenharmony_ci} 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistatic unsigned long ccu_nm_find_best(struct ccu_common *common, unsigned long parent, 3162306a36Sopenharmony_ci unsigned long rate, struct _ccu_nm *nm) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci unsigned long best_rate = 0; 3462306a36Sopenharmony_ci unsigned long best_n = 0, best_m = 0; 3562306a36Sopenharmony_ci unsigned long _n, _m; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci for (_n = nm->min_n; _n <= nm->max_n; _n++) { 3862306a36Sopenharmony_ci for (_m = nm->min_m; _m <= nm->max_m; _m++) { 3962306a36Sopenharmony_ci unsigned long tmp_rate = ccu_nm_calc_rate(parent, 4062306a36Sopenharmony_ci _n, _m); 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci if (ccu_is_better_rate(common, rate, tmp_rate, best_rate)) { 4362306a36Sopenharmony_ci best_rate = tmp_rate; 4462306a36Sopenharmony_ci best_n = _n; 4562306a36Sopenharmony_ci best_m = _m; 4662306a36Sopenharmony_ci } 4762306a36Sopenharmony_ci } 4862306a36Sopenharmony_ci } 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci nm->n = best_n; 5162306a36Sopenharmony_ci nm->m = best_m; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci return best_rate; 5462306a36Sopenharmony_ci} 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic void ccu_nm_disable(struct clk_hw *hw) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci struct ccu_nm *nm = hw_to_ccu_nm(hw); 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci return ccu_gate_helper_disable(&nm->common, nm->enable); 6162306a36Sopenharmony_ci} 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_cistatic int ccu_nm_enable(struct clk_hw *hw) 6462306a36Sopenharmony_ci{ 6562306a36Sopenharmony_ci struct ccu_nm *nm = hw_to_ccu_nm(hw); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci return ccu_gate_helper_enable(&nm->common, nm->enable); 6862306a36Sopenharmony_ci} 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic int ccu_nm_is_enabled(struct clk_hw *hw) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci struct ccu_nm *nm = hw_to_ccu_nm(hw); 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci return ccu_gate_helper_is_enabled(&nm->common, nm->enable); 7562306a36Sopenharmony_ci} 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cistatic unsigned long ccu_nm_recalc_rate(struct clk_hw *hw, 7862306a36Sopenharmony_ci unsigned long parent_rate) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci struct ccu_nm *nm = hw_to_ccu_nm(hw); 8162306a36Sopenharmony_ci unsigned long rate; 8262306a36Sopenharmony_ci unsigned long n, m; 8362306a36Sopenharmony_ci u32 reg; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci if (ccu_frac_helper_is_enabled(&nm->common, &nm->frac)) { 8662306a36Sopenharmony_ci rate = ccu_frac_helper_read_rate(&nm->common, &nm->frac); 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 8962306a36Sopenharmony_ci rate /= nm->fixed_post_div; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci return rate; 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci reg = readl(nm->common.base + nm->common.reg); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci n = reg >> nm->n.shift; 9762306a36Sopenharmony_ci n &= (1 << nm->n.width) - 1; 9862306a36Sopenharmony_ci n += nm->n.offset; 9962306a36Sopenharmony_ci if (!n) 10062306a36Sopenharmony_ci n++; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci m = reg >> nm->m.shift; 10362306a36Sopenharmony_ci m &= (1 << nm->m.width) - 1; 10462306a36Sopenharmony_ci m += nm->m.offset; 10562306a36Sopenharmony_ci if (!m) 10662306a36Sopenharmony_ci m++; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci if (ccu_sdm_helper_is_enabled(&nm->common, &nm->sdm)) 10962306a36Sopenharmony_ci rate = ccu_sdm_helper_read_rate(&nm->common, &nm->sdm, m, n); 11062306a36Sopenharmony_ci else 11162306a36Sopenharmony_ci rate = ccu_nm_calc_rate(parent_rate, n, m); 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 11462306a36Sopenharmony_ci rate /= nm->fixed_post_div; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci return rate; 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cistatic long ccu_nm_round_rate(struct clk_hw *hw, unsigned long rate, 12062306a36Sopenharmony_ci unsigned long *parent_rate) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci struct ccu_nm *nm = hw_to_ccu_nm(hw); 12362306a36Sopenharmony_ci struct _ccu_nm _nm; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 12662306a36Sopenharmony_ci rate *= nm->fixed_post_div; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci if (rate < nm->min_rate) { 12962306a36Sopenharmony_ci rate = nm->min_rate; 13062306a36Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 13162306a36Sopenharmony_ci rate /= nm->fixed_post_div; 13262306a36Sopenharmony_ci return rate; 13362306a36Sopenharmony_ci } 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci if (nm->max_rate && rate > nm->max_rate) { 13662306a36Sopenharmony_ci rate = nm->max_rate; 13762306a36Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 13862306a36Sopenharmony_ci rate /= nm->fixed_post_div; 13962306a36Sopenharmony_ci return rate; 14062306a36Sopenharmony_ci } 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) { 14362306a36Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 14462306a36Sopenharmony_ci rate /= nm->fixed_post_div; 14562306a36Sopenharmony_ci return rate; 14662306a36Sopenharmony_ci } 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci if (ccu_sdm_helper_has_rate(&nm->common, &nm->sdm, rate)) { 14962306a36Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 15062306a36Sopenharmony_ci rate /= nm->fixed_post_div; 15162306a36Sopenharmony_ci return rate; 15262306a36Sopenharmony_ci } 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci _nm.min_n = nm->n.min ?: 1; 15562306a36Sopenharmony_ci _nm.max_n = nm->n.max ?: 1 << nm->n.width; 15662306a36Sopenharmony_ci _nm.min_m = 1; 15762306a36Sopenharmony_ci _nm.max_m = nm->m.max ?: 1 << nm->m.width; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci rate = ccu_nm_find_best(&nm->common, *parent_rate, rate, &_nm); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 16262306a36Sopenharmony_ci rate /= nm->fixed_post_div; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci return rate; 16562306a36Sopenharmony_ci} 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_cistatic int ccu_nm_set_rate(struct clk_hw *hw, unsigned long rate, 16862306a36Sopenharmony_ci unsigned long parent_rate) 16962306a36Sopenharmony_ci{ 17062306a36Sopenharmony_ci struct ccu_nm *nm = hw_to_ccu_nm(hw); 17162306a36Sopenharmony_ci struct _ccu_nm _nm; 17262306a36Sopenharmony_ci unsigned long flags; 17362306a36Sopenharmony_ci u32 reg; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci /* Adjust target rate according to post-dividers */ 17662306a36Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 17762306a36Sopenharmony_ci rate = rate * nm->fixed_post_div; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) { 18062306a36Sopenharmony_ci spin_lock_irqsave(nm->common.lock, flags); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci /* most SoCs require M to be 0 if fractional mode is used */ 18362306a36Sopenharmony_ci reg = readl(nm->common.base + nm->common.reg); 18462306a36Sopenharmony_ci reg &= ~GENMASK(nm->m.width + nm->m.shift - 1, nm->m.shift); 18562306a36Sopenharmony_ci writel(reg, nm->common.base + nm->common.reg); 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci spin_unlock_irqrestore(nm->common.lock, flags); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci ccu_frac_helper_enable(&nm->common, &nm->frac); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci return ccu_frac_helper_set_rate(&nm->common, &nm->frac, 19262306a36Sopenharmony_ci rate, nm->lock); 19362306a36Sopenharmony_ci } else { 19462306a36Sopenharmony_ci ccu_frac_helper_disable(&nm->common, &nm->frac); 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci _nm.min_n = nm->n.min ?: 1; 19862306a36Sopenharmony_ci _nm.max_n = nm->n.max ?: 1 << nm->n.width; 19962306a36Sopenharmony_ci _nm.min_m = 1; 20062306a36Sopenharmony_ci _nm.max_m = nm->m.max ?: 1 << nm->m.width; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci if (ccu_sdm_helper_has_rate(&nm->common, &nm->sdm, rate)) { 20362306a36Sopenharmony_ci ccu_sdm_helper_enable(&nm->common, &nm->sdm, rate); 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci /* Sigma delta modulation requires specific N and M factors */ 20662306a36Sopenharmony_ci ccu_sdm_helper_get_factors(&nm->common, &nm->sdm, rate, 20762306a36Sopenharmony_ci &_nm.m, &_nm.n); 20862306a36Sopenharmony_ci } else { 20962306a36Sopenharmony_ci ccu_sdm_helper_disable(&nm->common, &nm->sdm); 21062306a36Sopenharmony_ci ccu_nm_find_best(&nm->common, parent_rate, rate, &_nm); 21162306a36Sopenharmony_ci } 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci spin_lock_irqsave(nm->common.lock, flags); 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci reg = readl(nm->common.base + nm->common.reg); 21662306a36Sopenharmony_ci reg &= ~GENMASK(nm->n.width + nm->n.shift - 1, nm->n.shift); 21762306a36Sopenharmony_ci reg &= ~GENMASK(nm->m.width + nm->m.shift - 1, nm->m.shift); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci reg |= (_nm.n - nm->n.offset) << nm->n.shift; 22062306a36Sopenharmony_ci reg |= (_nm.m - nm->m.offset) << nm->m.shift; 22162306a36Sopenharmony_ci writel(reg, nm->common.base + nm->common.reg); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci spin_unlock_irqrestore(nm->common.lock, flags); 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci ccu_helper_wait_for_lock(&nm->common, nm->lock); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci return 0; 22862306a36Sopenharmony_ci} 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ciconst struct clk_ops ccu_nm_ops = { 23162306a36Sopenharmony_ci .disable = ccu_nm_disable, 23262306a36Sopenharmony_ci .enable = ccu_nm_enable, 23362306a36Sopenharmony_ci .is_enabled = ccu_nm_is_enabled, 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci .recalc_rate = ccu_nm_recalc_rate, 23662306a36Sopenharmony_ci .round_rate = ccu_nm_round_rate, 23762306a36Sopenharmony_ci .set_rate = ccu_nm_set_rate, 23862306a36Sopenharmony_ci}; 23962306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(ccu_nm_ops, SUNXI_CCU); 240