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_gate.h" 118c2ecf20Sopenharmony_ci#include "ccu_nkmp.h" 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_cistruct _ccu_nkmp { 148c2ecf20Sopenharmony_ci unsigned long n, min_n, max_n; 158c2ecf20Sopenharmony_ci unsigned long k, min_k, max_k; 168c2ecf20Sopenharmony_ci unsigned long m, min_m, max_m; 178c2ecf20Sopenharmony_ci unsigned long p, min_p, max_p; 188c2ecf20Sopenharmony_ci}; 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_cistatic unsigned long ccu_nkmp_calc_rate(unsigned long parent, 218c2ecf20Sopenharmony_ci unsigned long n, unsigned long k, 228c2ecf20Sopenharmony_ci unsigned long m, unsigned long p) 238c2ecf20Sopenharmony_ci{ 248c2ecf20Sopenharmony_ci u64 rate = parent; 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci rate *= n * k; 278c2ecf20Sopenharmony_ci do_div(rate, m * p); 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci return rate; 308c2ecf20Sopenharmony_ci} 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistatic void ccu_nkmp_find_best(unsigned long parent, unsigned long rate, 338c2ecf20Sopenharmony_ci struct _ccu_nkmp *nkmp) 348c2ecf20Sopenharmony_ci{ 358c2ecf20Sopenharmony_ci unsigned long best_rate = 0; 368c2ecf20Sopenharmony_ci unsigned long best_n = 0, best_k = 0, best_m = 0, best_p = 0; 378c2ecf20Sopenharmony_ci unsigned long _n, _k, _m, _p; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci for (_k = nkmp->min_k; _k <= nkmp->max_k; _k++) { 408c2ecf20Sopenharmony_ci for (_n = nkmp->min_n; _n <= nkmp->max_n; _n++) { 418c2ecf20Sopenharmony_ci for (_m = nkmp->min_m; _m <= nkmp->max_m; _m++) { 428c2ecf20Sopenharmony_ci for (_p = nkmp->min_p; _p <= nkmp->max_p; _p <<= 1) { 438c2ecf20Sopenharmony_ci unsigned long tmp_rate; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci tmp_rate = ccu_nkmp_calc_rate(parent, 468c2ecf20Sopenharmony_ci _n, _k, 478c2ecf20Sopenharmony_ci _m, _p); 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci if (tmp_rate > rate) 508c2ecf20Sopenharmony_ci continue; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci if ((rate - tmp_rate) < (rate - best_rate)) { 538c2ecf20Sopenharmony_ci best_rate = tmp_rate; 548c2ecf20Sopenharmony_ci best_n = _n; 558c2ecf20Sopenharmony_ci best_k = _k; 568c2ecf20Sopenharmony_ci best_m = _m; 578c2ecf20Sopenharmony_ci best_p = _p; 588c2ecf20Sopenharmony_ci } 598c2ecf20Sopenharmony_ci } 608c2ecf20Sopenharmony_ci } 618c2ecf20Sopenharmony_ci } 628c2ecf20Sopenharmony_ci } 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci nkmp->n = best_n; 658c2ecf20Sopenharmony_ci nkmp->k = best_k; 668c2ecf20Sopenharmony_ci nkmp->m = best_m; 678c2ecf20Sopenharmony_ci nkmp->p = best_p; 688c2ecf20Sopenharmony_ci} 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistatic void ccu_nkmp_disable(struct clk_hw *hw) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci return ccu_gate_helper_disable(&nkmp->common, nkmp->enable); 758c2ecf20Sopenharmony_ci} 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistatic int ccu_nkmp_enable(struct clk_hw *hw) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci return ccu_gate_helper_enable(&nkmp->common, nkmp->enable); 828c2ecf20Sopenharmony_ci} 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistatic int ccu_nkmp_is_enabled(struct clk_hw *hw) 858c2ecf20Sopenharmony_ci{ 868c2ecf20Sopenharmony_ci struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci return ccu_gate_helper_is_enabled(&nkmp->common, nkmp->enable); 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic unsigned long ccu_nkmp_recalc_rate(struct clk_hw *hw, 928c2ecf20Sopenharmony_ci unsigned long parent_rate) 938c2ecf20Sopenharmony_ci{ 948c2ecf20Sopenharmony_ci struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); 958c2ecf20Sopenharmony_ci unsigned long n, m, k, p, rate; 968c2ecf20Sopenharmony_ci u32 reg; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci reg = readl(nkmp->common.base + nkmp->common.reg); 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci n = reg >> nkmp->n.shift; 1018c2ecf20Sopenharmony_ci n &= (1 << nkmp->n.width) - 1; 1028c2ecf20Sopenharmony_ci n += nkmp->n.offset; 1038c2ecf20Sopenharmony_ci if (!n) 1048c2ecf20Sopenharmony_ci n++; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci k = reg >> nkmp->k.shift; 1078c2ecf20Sopenharmony_ci k &= (1 << nkmp->k.width) - 1; 1088c2ecf20Sopenharmony_ci k += nkmp->k.offset; 1098c2ecf20Sopenharmony_ci if (!k) 1108c2ecf20Sopenharmony_ci k++; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci m = reg >> nkmp->m.shift; 1138c2ecf20Sopenharmony_ci m &= (1 << nkmp->m.width) - 1; 1148c2ecf20Sopenharmony_ci m += nkmp->m.offset; 1158c2ecf20Sopenharmony_ci if (!m) 1168c2ecf20Sopenharmony_ci m++; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci p = reg >> nkmp->p.shift; 1198c2ecf20Sopenharmony_ci p &= (1 << nkmp->p.width) - 1; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci rate = ccu_nkmp_calc_rate(parent_rate, n, k, m, 1 << p); 1228c2ecf20Sopenharmony_ci if (nkmp->common.features & CCU_FEATURE_FIXED_POSTDIV) 1238c2ecf20Sopenharmony_ci rate /= nkmp->fixed_post_div; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci return rate; 1268c2ecf20Sopenharmony_ci} 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_cistatic long ccu_nkmp_round_rate(struct clk_hw *hw, unsigned long rate, 1298c2ecf20Sopenharmony_ci unsigned long *parent_rate) 1308c2ecf20Sopenharmony_ci{ 1318c2ecf20Sopenharmony_ci struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); 1328c2ecf20Sopenharmony_ci struct _ccu_nkmp _nkmp; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci if (nkmp->common.features & CCU_FEATURE_FIXED_POSTDIV) 1358c2ecf20Sopenharmony_ci rate *= nkmp->fixed_post_div; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci if (nkmp->max_rate && rate > nkmp->max_rate) { 1388c2ecf20Sopenharmony_ci rate = nkmp->max_rate; 1398c2ecf20Sopenharmony_ci if (nkmp->common.features & CCU_FEATURE_FIXED_POSTDIV) 1408c2ecf20Sopenharmony_ci rate /= nkmp->fixed_post_div; 1418c2ecf20Sopenharmony_ci return rate; 1428c2ecf20Sopenharmony_ci } 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci _nkmp.min_n = nkmp->n.min ?: 1; 1458c2ecf20Sopenharmony_ci _nkmp.max_n = nkmp->n.max ?: 1 << nkmp->n.width; 1468c2ecf20Sopenharmony_ci _nkmp.min_k = nkmp->k.min ?: 1; 1478c2ecf20Sopenharmony_ci _nkmp.max_k = nkmp->k.max ?: 1 << nkmp->k.width; 1488c2ecf20Sopenharmony_ci _nkmp.min_m = 1; 1498c2ecf20Sopenharmony_ci _nkmp.max_m = nkmp->m.max ?: 1 << nkmp->m.width; 1508c2ecf20Sopenharmony_ci _nkmp.min_p = 1; 1518c2ecf20Sopenharmony_ci _nkmp.max_p = nkmp->p.max ?: 1 << ((1 << nkmp->p.width) - 1); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci ccu_nkmp_find_best(*parent_rate, rate, &_nkmp); 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci rate = ccu_nkmp_calc_rate(*parent_rate, _nkmp.n, _nkmp.k, 1568c2ecf20Sopenharmony_ci _nkmp.m, _nkmp.p); 1578c2ecf20Sopenharmony_ci if (nkmp->common.features & CCU_FEATURE_FIXED_POSTDIV) 1588c2ecf20Sopenharmony_ci rate = rate / nkmp->fixed_post_div; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci return rate; 1618c2ecf20Sopenharmony_ci} 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_cistatic int ccu_nkmp_set_rate(struct clk_hw *hw, unsigned long rate, 1648c2ecf20Sopenharmony_ci unsigned long parent_rate) 1658c2ecf20Sopenharmony_ci{ 1668c2ecf20Sopenharmony_ci struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); 1678c2ecf20Sopenharmony_ci u32 n_mask = 0, k_mask = 0, m_mask = 0, p_mask = 0; 1688c2ecf20Sopenharmony_ci struct _ccu_nkmp _nkmp; 1698c2ecf20Sopenharmony_ci unsigned long flags; 1708c2ecf20Sopenharmony_ci u32 reg; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci if (nkmp->common.features & CCU_FEATURE_FIXED_POSTDIV) 1738c2ecf20Sopenharmony_ci rate = rate * nkmp->fixed_post_div; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci _nkmp.min_n = nkmp->n.min ?: 1; 1768c2ecf20Sopenharmony_ci _nkmp.max_n = nkmp->n.max ?: 1 << nkmp->n.width; 1778c2ecf20Sopenharmony_ci _nkmp.min_k = nkmp->k.min ?: 1; 1788c2ecf20Sopenharmony_ci _nkmp.max_k = nkmp->k.max ?: 1 << nkmp->k.width; 1798c2ecf20Sopenharmony_ci _nkmp.min_m = 1; 1808c2ecf20Sopenharmony_ci _nkmp.max_m = nkmp->m.max ?: 1 << nkmp->m.width; 1818c2ecf20Sopenharmony_ci _nkmp.min_p = 1; 1828c2ecf20Sopenharmony_ci _nkmp.max_p = nkmp->p.max ?: 1 << ((1 << nkmp->p.width) - 1); 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci ccu_nkmp_find_best(parent_rate, rate, &_nkmp); 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci /* 1878c2ecf20Sopenharmony_ci * If width is 0, GENMASK() macro may not generate expected mask (0) 1888c2ecf20Sopenharmony_ci * as it falls under undefined behaviour by C standard due to shifts 1898c2ecf20Sopenharmony_ci * which are equal or greater than width of left operand. This can 1908c2ecf20Sopenharmony_ci * be easily avoided by explicitly checking if width is 0. 1918c2ecf20Sopenharmony_ci */ 1928c2ecf20Sopenharmony_ci if (nkmp->n.width) 1938c2ecf20Sopenharmony_ci n_mask = GENMASK(nkmp->n.width + nkmp->n.shift - 1, 1948c2ecf20Sopenharmony_ci nkmp->n.shift); 1958c2ecf20Sopenharmony_ci if (nkmp->k.width) 1968c2ecf20Sopenharmony_ci k_mask = GENMASK(nkmp->k.width + nkmp->k.shift - 1, 1978c2ecf20Sopenharmony_ci nkmp->k.shift); 1988c2ecf20Sopenharmony_ci if (nkmp->m.width) 1998c2ecf20Sopenharmony_ci m_mask = GENMASK(nkmp->m.width + nkmp->m.shift - 1, 2008c2ecf20Sopenharmony_ci nkmp->m.shift); 2018c2ecf20Sopenharmony_ci if (nkmp->p.width) 2028c2ecf20Sopenharmony_ci p_mask = GENMASK(nkmp->p.width + nkmp->p.shift - 1, 2038c2ecf20Sopenharmony_ci nkmp->p.shift); 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci spin_lock_irqsave(nkmp->common.lock, flags); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci reg = readl(nkmp->common.base + nkmp->common.reg); 2088c2ecf20Sopenharmony_ci reg &= ~(n_mask | k_mask | m_mask | p_mask); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci reg |= ((_nkmp.n - nkmp->n.offset) << nkmp->n.shift) & n_mask; 2118c2ecf20Sopenharmony_ci reg |= ((_nkmp.k - nkmp->k.offset) << nkmp->k.shift) & k_mask; 2128c2ecf20Sopenharmony_ci reg |= ((_nkmp.m - nkmp->m.offset) << nkmp->m.shift) & m_mask; 2138c2ecf20Sopenharmony_ci reg |= (ilog2(_nkmp.p) << nkmp->p.shift) & p_mask; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci writel(reg, nkmp->common.base + nkmp->common.reg); 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci spin_unlock_irqrestore(nkmp->common.lock, flags); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci ccu_helper_wait_for_lock(&nkmp->common, nkmp->lock); 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci return 0; 2228c2ecf20Sopenharmony_ci} 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ciconst struct clk_ops ccu_nkmp_ops = { 2258c2ecf20Sopenharmony_ci .disable = ccu_nkmp_disable, 2268c2ecf20Sopenharmony_ci .enable = ccu_nkmp_enable, 2278c2ecf20Sopenharmony_ci .is_enabled = ccu_nkmp_is_enabled, 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci .recalc_rate = ccu_nkmp_recalc_rate, 2308c2ecf20Sopenharmony_ci .round_rate = ccu_nkmp_round_rate, 2318c2ecf20Sopenharmony_ci .set_rate = ccu_nkmp_set_rate, 2328c2ecf20Sopenharmony_ci}; 233