18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2016 Maxime Ripard 48c2ecf20Sopenharmony_ci * Maxime Ripard <maxime.ripard@free-electrons.com> 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/clk-provider.h> 88c2ecf20Sopenharmony_ci#include <linux/io.h> 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include "ccu_frac.h" 118c2ecf20Sopenharmony_ci#include "ccu_gate.h" 128c2ecf20Sopenharmony_ci#include "ccu_nm.h" 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_cistruct _ccu_nm { 158c2ecf20Sopenharmony_ci unsigned long n, min_n, max_n; 168c2ecf20Sopenharmony_ci unsigned long m, min_m, max_m; 178c2ecf20Sopenharmony_ci}; 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_cistatic unsigned long ccu_nm_calc_rate(unsigned long parent, 208c2ecf20Sopenharmony_ci unsigned long n, unsigned long m) 218c2ecf20Sopenharmony_ci{ 228c2ecf20Sopenharmony_ci u64 rate = parent; 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci rate *= n; 258c2ecf20Sopenharmony_ci do_div(rate, m); 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci return rate; 288c2ecf20Sopenharmony_ci} 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistatic void ccu_nm_find_best(unsigned long parent, unsigned long rate, 318c2ecf20Sopenharmony_ci struct _ccu_nm *nm) 328c2ecf20Sopenharmony_ci{ 338c2ecf20Sopenharmony_ci unsigned long best_rate = 0; 348c2ecf20Sopenharmony_ci unsigned long best_n = 0, best_m = 0; 358c2ecf20Sopenharmony_ci unsigned long _n, _m; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci for (_n = nm->min_n; _n <= nm->max_n; _n++) { 388c2ecf20Sopenharmony_ci for (_m = nm->min_m; _m <= nm->max_m; _m++) { 398c2ecf20Sopenharmony_ci unsigned long tmp_rate = ccu_nm_calc_rate(parent, 408c2ecf20Sopenharmony_ci _n, _m); 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci if (tmp_rate > rate) 438c2ecf20Sopenharmony_ci continue; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci if ((rate - tmp_rate) < (rate - best_rate)) { 468c2ecf20Sopenharmony_ci best_rate = tmp_rate; 478c2ecf20Sopenharmony_ci best_n = _n; 488c2ecf20Sopenharmony_ci best_m = _m; 498c2ecf20Sopenharmony_ci } 508c2ecf20Sopenharmony_ci } 518c2ecf20Sopenharmony_ci } 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci nm->n = best_n; 548c2ecf20Sopenharmony_ci nm->m = best_m; 558c2ecf20Sopenharmony_ci} 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic void ccu_nm_disable(struct clk_hw *hw) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci struct ccu_nm *nm = hw_to_ccu_nm(hw); 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci return ccu_gate_helper_disable(&nm->common, nm->enable); 628c2ecf20Sopenharmony_ci} 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic int ccu_nm_enable(struct clk_hw *hw) 658c2ecf20Sopenharmony_ci{ 668c2ecf20Sopenharmony_ci struct ccu_nm *nm = hw_to_ccu_nm(hw); 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci return ccu_gate_helper_enable(&nm->common, nm->enable); 698c2ecf20Sopenharmony_ci} 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cistatic int ccu_nm_is_enabled(struct clk_hw *hw) 728c2ecf20Sopenharmony_ci{ 738c2ecf20Sopenharmony_ci struct ccu_nm *nm = hw_to_ccu_nm(hw); 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci return ccu_gate_helper_is_enabled(&nm->common, nm->enable); 768c2ecf20Sopenharmony_ci} 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cistatic unsigned long ccu_nm_recalc_rate(struct clk_hw *hw, 798c2ecf20Sopenharmony_ci unsigned long parent_rate) 808c2ecf20Sopenharmony_ci{ 818c2ecf20Sopenharmony_ci struct ccu_nm *nm = hw_to_ccu_nm(hw); 828c2ecf20Sopenharmony_ci unsigned long rate; 838c2ecf20Sopenharmony_ci unsigned long n, m; 848c2ecf20Sopenharmony_ci u32 reg; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci if (ccu_frac_helper_is_enabled(&nm->common, &nm->frac)) { 878c2ecf20Sopenharmony_ci rate = ccu_frac_helper_read_rate(&nm->common, &nm->frac); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 908c2ecf20Sopenharmony_ci rate /= nm->fixed_post_div; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci return rate; 938c2ecf20Sopenharmony_ci } 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci reg = readl(nm->common.base + nm->common.reg); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci n = reg >> nm->n.shift; 988c2ecf20Sopenharmony_ci n &= (1 << nm->n.width) - 1; 998c2ecf20Sopenharmony_ci n += nm->n.offset; 1008c2ecf20Sopenharmony_ci if (!n) 1018c2ecf20Sopenharmony_ci n++; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci m = reg >> nm->m.shift; 1048c2ecf20Sopenharmony_ci m &= (1 << nm->m.width) - 1; 1058c2ecf20Sopenharmony_ci m += nm->m.offset; 1068c2ecf20Sopenharmony_ci if (!m) 1078c2ecf20Sopenharmony_ci m++; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci if (ccu_sdm_helper_is_enabled(&nm->common, &nm->sdm)) 1108c2ecf20Sopenharmony_ci rate = ccu_sdm_helper_read_rate(&nm->common, &nm->sdm, m, n); 1118c2ecf20Sopenharmony_ci else 1128c2ecf20Sopenharmony_ci rate = ccu_nm_calc_rate(parent_rate, n, m); 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1158c2ecf20Sopenharmony_ci rate /= nm->fixed_post_div; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci return rate; 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_cistatic long ccu_nm_round_rate(struct clk_hw *hw, unsigned long rate, 1218c2ecf20Sopenharmony_ci unsigned long *parent_rate) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci struct ccu_nm *nm = hw_to_ccu_nm(hw); 1248c2ecf20Sopenharmony_ci struct _ccu_nm _nm; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1278c2ecf20Sopenharmony_ci rate *= nm->fixed_post_div; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci if (rate < nm->min_rate) { 1308c2ecf20Sopenharmony_ci rate = nm->min_rate; 1318c2ecf20Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1328c2ecf20Sopenharmony_ci rate /= nm->fixed_post_div; 1338c2ecf20Sopenharmony_ci return rate; 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci if (nm->max_rate && rate > nm->max_rate) { 1378c2ecf20Sopenharmony_ci rate = nm->max_rate; 1388c2ecf20Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1398c2ecf20Sopenharmony_ci rate /= nm->fixed_post_div; 1408c2ecf20Sopenharmony_ci return rate; 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) { 1448c2ecf20Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1458c2ecf20Sopenharmony_ci rate /= nm->fixed_post_div; 1468c2ecf20Sopenharmony_ci return rate; 1478c2ecf20Sopenharmony_ci } 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci if (ccu_sdm_helper_has_rate(&nm->common, &nm->sdm, rate)) { 1508c2ecf20Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1518c2ecf20Sopenharmony_ci rate /= nm->fixed_post_div; 1528c2ecf20Sopenharmony_ci return rate; 1538c2ecf20Sopenharmony_ci } 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci _nm.min_n = nm->n.min ?: 1; 1568c2ecf20Sopenharmony_ci _nm.max_n = nm->n.max ?: 1 << nm->n.width; 1578c2ecf20Sopenharmony_ci _nm.min_m = 1; 1588c2ecf20Sopenharmony_ci _nm.max_m = nm->m.max ?: 1 << nm->m.width; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci ccu_nm_find_best(*parent_rate, rate, &_nm); 1618c2ecf20Sopenharmony_ci rate = ccu_nm_calc_rate(*parent_rate, _nm.n, _nm.m); 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1648c2ecf20Sopenharmony_ci rate /= nm->fixed_post_div; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci return rate; 1678c2ecf20Sopenharmony_ci} 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_cistatic int ccu_nm_set_rate(struct clk_hw *hw, unsigned long rate, 1708c2ecf20Sopenharmony_ci unsigned long parent_rate) 1718c2ecf20Sopenharmony_ci{ 1728c2ecf20Sopenharmony_ci struct ccu_nm *nm = hw_to_ccu_nm(hw); 1738c2ecf20Sopenharmony_ci struct _ccu_nm _nm; 1748c2ecf20Sopenharmony_ci unsigned long flags; 1758c2ecf20Sopenharmony_ci u32 reg; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci /* Adjust target rate according to post-dividers */ 1788c2ecf20Sopenharmony_ci if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1798c2ecf20Sopenharmony_ci rate = rate * nm->fixed_post_div; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) { 1828c2ecf20Sopenharmony_ci spin_lock_irqsave(nm->common.lock, flags); 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci /* most SoCs require M to be 0 if fractional mode is used */ 1858c2ecf20Sopenharmony_ci reg = readl(nm->common.base + nm->common.reg); 1868c2ecf20Sopenharmony_ci reg &= ~GENMASK(nm->m.width + nm->m.shift - 1, nm->m.shift); 1878c2ecf20Sopenharmony_ci writel(reg, nm->common.base + nm->common.reg); 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci spin_unlock_irqrestore(nm->common.lock, flags); 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci ccu_frac_helper_enable(&nm->common, &nm->frac); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci return ccu_frac_helper_set_rate(&nm->common, &nm->frac, 1948c2ecf20Sopenharmony_ci rate, nm->lock); 1958c2ecf20Sopenharmony_ci } else { 1968c2ecf20Sopenharmony_ci ccu_frac_helper_disable(&nm->common, &nm->frac); 1978c2ecf20Sopenharmony_ci } 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci _nm.min_n = nm->n.min ?: 1; 2008c2ecf20Sopenharmony_ci _nm.max_n = nm->n.max ?: 1 << nm->n.width; 2018c2ecf20Sopenharmony_ci _nm.min_m = 1; 2028c2ecf20Sopenharmony_ci _nm.max_m = nm->m.max ?: 1 << nm->m.width; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci if (ccu_sdm_helper_has_rate(&nm->common, &nm->sdm, rate)) { 2058c2ecf20Sopenharmony_ci ccu_sdm_helper_enable(&nm->common, &nm->sdm, rate); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci /* Sigma delta modulation requires specific N and M factors */ 2088c2ecf20Sopenharmony_ci ccu_sdm_helper_get_factors(&nm->common, &nm->sdm, rate, 2098c2ecf20Sopenharmony_ci &_nm.m, &_nm.n); 2108c2ecf20Sopenharmony_ci } else { 2118c2ecf20Sopenharmony_ci ccu_sdm_helper_disable(&nm->common, &nm->sdm); 2128c2ecf20Sopenharmony_ci ccu_nm_find_best(parent_rate, rate, &_nm); 2138c2ecf20Sopenharmony_ci } 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci spin_lock_irqsave(nm->common.lock, flags); 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci reg = readl(nm->common.base + nm->common.reg); 2188c2ecf20Sopenharmony_ci reg &= ~GENMASK(nm->n.width + nm->n.shift - 1, nm->n.shift); 2198c2ecf20Sopenharmony_ci reg &= ~GENMASK(nm->m.width + nm->m.shift - 1, nm->m.shift); 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci reg |= (_nm.n - nm->n.offset) << nm->n.shift; 2228c2ecf20Sopenharmony_ci reg |= (_nm.m - nm->m.offset) << nm->m.shift; 2238c2ecf20Sopenharmony_ci writel(reg, nm->common.base + nm->common.reg); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci spin_unlock_irqrestore(nm->common.lock, flags); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci ccu_helper_wait_for_lock(&nm->common, nm->lock); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci return 0; 2308c2ecf20Sopenharmony_ci} 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ciconst struct clk_ops ccu_nm_ops = { 2338c2ecf20Sopenharmony_ci .disable = ccu_nm_disable, 2348c2ecf20Sopenharmony_ci .enable = ccu_nm_enable, 2358c2ecf20Sopenharmony_ci .is_enabled = ccu_nm_is_enabled, 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci .recalc_rate = ccu_nm_recalc_rate, 2388c2ecf20Sopenharmony_ci .round_rate = ccu_nm_round_rate, 2398c2ecf20Sopenharmony_ci .set_rate = ccu_nm_set_rate, 2408c2ecf20Sopenharmony_ci}; 241