12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 26174a1e2SMaxime Ripard /* 36174a1e2SMaxime Ripard * Copyright (C) 2016 Maxime Ripard 46174a1e2SMaxime Ripard * Maxime Ripard <maxime.ripard@free-electrons.com> 56174a1e2SMaxime Ripard */ 66174a1e2SMaxime Ripard 76174a1e2SMaxime Ripard #include <linux/clk-provider.h> 862e59c4eSStephen Boyd #include <linux/io.h> 96174a1e2SMaxime Ripard 106174a1e2SMaxime Ripard #include "ccu_frac.h" 116174a1e2SMaxime Ripard #include "ccu_gate.h" 126174a1e2SMaxime Ripard #include "ccu_nm.h" 136174a1e2SMaxime Ripard 14ee28648cSMaxime Ripard struct _ccu_nm { 156e0d50daSMaxime Ripard unsigned long n, min_n, max_n; 166e0d50daSMaxime Ripard unsigned long m, min_m, max_m; 17ee28648cSMaxime Ripard }; 18ee28648cSMaxime Ripard 1965b66576SJernej Skrabec static unsigned long ccu_nm_calc_rate(unsigned long parent, 2065b66576SJernej Skrabec unsigned long n, unsigned long m) 2165b66576SJernej Skrabec { 2265b66576SJernej Skrabec u64 rate = parent; 2365b66576SJernej Skrabec 2465b66576SJernej Skrabec rate *= n; 2565b66576SJernej Skrabec do_div(rate, m); 2665b66576SJernej Skrabec 2765b66576SJernej Skrabec return rate; 2865b66576SJernej Skrabec } 2965b66576SJernej Skrabec 30*657f477aSSamuel Holland static unsigned long ccu_nm_find_best(unsigned long parent, unsigned long rate, 31ee28648cSMaxime Ripard struct _ccu_nm *nm) 32ee28648cSMaxime Ripard { 33ee28648cSMaxime Ripard unsigned long best_rate = 0; 34ee28648cSMaxime Ripard unsigned long best_n = 0, best_m = 0; 35ee28648cSMaxime Ripard unsigned long _n, _m; 36ee28648cSMaxime Ripard 376e0d50daSMaxime Ripard for (_n = nm->min_n; _n <= nm->max_n; _n++) { 386e0d50daSMaxime Ripard for (_m = nm->min_m; _m <= nm->max_m; _m++) { 3965b66576SJernej Skrabec unsigned long tmp_rate = ccu_nm_calc_rate(parent, 4065b66576SJernej Skrabec _n, _m); 41ee28648cSMaxime Ripard 42ee28648cSMaxime Ripard if (tmp_rate > rate) 43ee28648cSMaxime Ripard continue; 44ee28648cSMaxime Ripard 45ee28648cSMaxime Ripard if ((rate - tmp_rate) < (rate - best_rate)) { 46ee28648cSMaxime Ripard best_rate = tmp_rate; 47ee28648cSMaxime Ripard best_n = _n; 48ee28648cSMaxime Ripard best_m = _m; 49ee28648cSMaxime Ripard } 50ee28648cSMaxime Ripard } 51ee28648cSMaxime Ripard } 52ee28648cSMaxime Ripard 53ee28648cSMaxime Ripard nm->n = best_n; 54ee28648cSMaxime Ripard nm->m = best_m; 55*657f477aSSamuel Holland 56*657f477aSSamuel Holland return best_rate; 57ee28648cSMaxime Ripard } 58ee28648cSMaxime Ripard 596174a1e2SMaxime Ripard static void ccu_nm_disable(struct clk_hw *hw) 606174a1e2SMaxime Ripard { 616174a1e2SMaxime Ripard struct ccu_nm *nm = hw_to_ccu_nm(hw); 626174a1e2SMaxime Ripard 636174a1e2SMaxime Ripard return ccu_gate_helper_disable(&nm->common, nm->enable); 646174a1e2SMaxime Ripard } 656174a1e2SMaxime Ripard 666174a1e2SMaxime Ripard static int ccu_nm_enable(struct clk_hw *hw) 676174a1e2SMaxime Ripard { 686174a1e2SMaxime Ripard struct ccu_nm *nm = hw_to_ccu_nm(hw); 696174a1e2SMaxime Ripard 706174a1e2SMaxime Ripard return ccu_gate_helper_enable(&nm->common, nm->enable); 716174a1e2SMaxime Ripard } 726174a1e2SMaxime Ripard 736174a1e2SMaxime Ripard static int ccu_nm_is_enabled(struct clk_hw *hw) 746174a1e2SMaxime Ripard { 756174a1e2SMaxime Ripard struct ccu_nm *nm = hw_to_ccu_nm(hw); 766174a1e2SMaxime Ripard 776174a1e2SMaxime Ripard return ccu_gate_helper_is_enabled(&nm->common, nm->enable); 786174a1e2SMaxime Ripard } 796174a1e2SMaxime Ripard 806174a1e2SMaxime Ripard static unsigned long ccu_nm_recalc_rate(struct clk_hw *hw, 816174a1e2SMaxime Ripard unsigned long parent_rate) 826174a1e2SMaxime Ripard { 836174a1e2SMaxime Ripard struct ccu_nm *nm = hw_to_ccu_nm(hw); 847d333ef1SChen-Yu Tsai unsigned long rate; 856174a1e2SMaxime Ripard unsigned long n, m; 866174a1e2SMaxime Ripard u32 reg; 876174a1e2SMaxime Ripard 887d333ef1SChen-Yu Tsai if (ccu_frac_helper_is_enabled(&nm->common, &nm->frac)) { 897d333ef1SChen-Yu Tsai rate = ccu_frac_helper_read_rate(&nm->common, &nm->frac); 907d333ef1SChen-Yu Tsai 917d333ef1SChen-Yu Tsai if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 927d333ef1SChen-Yu Tsai rate /= nm->fixed_post_div; 937d333ef1SChen-Yu Tsai 947d333ef1SChen-Yu Tsai return rate; 957d333ef1SChen-Yu Tsai } 966174a1e2SMaxime Ripard 976174a1e2SMaxime Ripard reg = readl(nm->common.base + nm->common.reg); 986174a1e2SMaxime Ripard 996174a1e2SMaxime Ripard n = reg >> nm->n.shift; 1006174a1e2SMaxime Ripard n &= (1 << nm->n.width) - 1; 101e66f81bbSMaxime Ripard n += nm->n.offset; 102e66f81bbSMaxime Ripard if (!n) 103e66f81bbSMaxime Ripard n++; 1046174a1e2SMaxime Ripard 1056174a1e2SMaxime Ripard m = reg >> nm->m.shift; 1066174a1e2SMaxime Ripard m &= (1 << nm->m.width) - 1; 107e66f81bbSMaxime Ripard m += nm->m.offset; 108e66f81bbSMaxime Ripard if (!m) 109e66f81bbSMaxime Ripard m++; 1106174a1e2SMaxime Ripard 1117d333ef1SChen-Yu Tsai if (ccu_sdm_helper_is_enabled(&nm->common, &nm->sdm)) 1127d333ef1SChen-Yu Tsai rate = ccu_sdm_helper_read_rate(&nm->common, &nm->sdm, m, n); 1137d333ef1SChen-Yu Tsai else 11465b66576SJernej Skrabec rate = ccu_nm_calc_rate(parent_rate, n, m); 115392ba5faSChen-Yu Tsai 1167d333ef1SChen-Yu Tsai if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1177d333ef1SChen-Yu Tsai rate /= nm->fixed_post_div; 1187d333ef1SChen-Yu Tsai 1197d333ef1SChen-Yu Tsai return rate; 1206174a1e2SMaxime Ripard } 1216174a1e2SMaxime Ripard 1226174a1e2SMaxime Ripard static long ccu_nm_round_rate(struct clk_hw *hw, unsigned long rate, 1236174a1e2SMaxime Ripard unsigned long *parent_rate) 1246174a1e2SMaxime Ripard { 1256174a1e2SMaxime Ripard struct ccu_nm *nm = hw_to_ccu_nm(hw); 126ee28648cSMaxime Ripard struct _ccu_nm _nm; 1276174a1e2SMaxime Ripard 1287d333ef1SChen-Yu Tsai if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1297d333ef1SChen-Yu Tsai rate *= nm->fixed_post_div; 1304cdbc40dSChen-Yu Tsai 1312d2b61c1SJernej Skrabec if (rate < nm->min_rate) { 1322d2b61c1SJernej Skrabec rate = nm->min_rate; 1332d2b61c1SJernej Skrabec if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1342d2b61c1SJernej Skrabec rate /= nm->fixed_post_div; 1352d2b61c1SJernej Skrabec return rate; 1362d2b61c1SJernej Skrabec } 1372d2b61c1SJernej Skrabec 138cb54fbd2SJernej Skrabec if (nm->max_rate && rate > nm->max_rate) { 139cb54fbd2SJernej Skrabec rate = nm->max_rate; 140cb54fbd2SJernej Skrabec if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 141cb54fbd2SJernej Skrabec rate /= nm->fixed_post_div; 142cb54fbd2SJernej Skrabec return rate; 143cb54fbd2SJernej Skrabec } 144cb54fbd2SJernej Skrabec 1457d333ef1SChen-Yu Tsai if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) { 1467d333ef1SChen-Yu Tsai if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1477d333ef1SChen-Yu Tsai rate /= nm->fixed_post_div; 148392ba5faSChen-Yu Tsai return rate; 1497d333ef1SChen-Yu Tsai } 1507d333ef1SChen-Yu Tsai 1517d333ef1SChen-Yu Tsai if (ccu_sdm_helper_has_rate(&nm->common, &nm->sdm, rate)) { 1527d333ef1SChen-Yu Tsai if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1537d333ef1SChen-Yu Tsai rate /= nm->fixed_post_div; 1547d333ef1SChen-Yu Tsai return rate; 1557d333ef1SChen-Yu Tsai } 156392ba5faSChen-Yu Tsai 1574162c5ceSChen-Yu Tsai _nm.min_n = nm->n.min ?: 1; 1580c3c8e13SMaxime Ripard _nm.max_n = nm->n.max ?: 1 << nm->n.width; 1596e0d50daSMaxime Ripard _nm.min_m = 1; 160ee28648cSMaxime Ripard _nm.max_m = nm->m.max ?: 1 << nm->m.width; 16187ba9e59SMaxime Ripard 162*657f477aSSamuel Holland rate = ccu_nm_find_best(*parent_rate, rate, &_nm); 1636174a1e2SMaxime Ripard 1647d333ef1SChen-Yu Tsai if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1657d333ef1SChen-Yu Tsai rate /= nm->fixed_post_div; 1667d333ef1SChen-Yu Tsai 1677d333ef1SChen-Yu Tsai return rate; 1686174a1e2SMaxime Ripard } 1696174a1e2SMaxime Ripard 1706174a1e2SMaxime Ripard static int ccu_nm_set_rate(struct clk_hw *hw, unsigned long rate, 1716174a1e2SMaxime Ripard unsigned long parent_rate) 1726174a1e2SMaxime Ripard { 1736174a1e2SMaxime Ripard struct ccu_nm *nm = hw_to_ccu_nm(hw); 174ee28648cSMaxime Ripard struct _ccu_nm _nm; 1756174a1e2SMaxime Ripard unsigned long flags; 1766174a1e2SMaxime Ripard u32 reg; 1776174a1e2SMaxime Ripard 1787d333ef1SChen-Yu Tsai /* Adjust target rate according to post-dividers */ 1797d333ef1SChen-Yu Tsai if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1807d333ef1SChen-Yu Tsai rate = rate * nm->fixed_post_div; 1817d333ef1SChen-Yu Tsai 182b64dfec0SJernej Škrabec if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) { 183b64dfec0SJernej Škrabec spin_lock_irqsave(nm->common.lock, flags); 184b64dfec0SJernej Škrabec 185b64dfec0SJernej Škrabec /* most SoCs require M to be 0 if fractional mode is used */ 186b64dfec0SJernej Škrabec reg = readl(nm->common.base + nm->common.reg); 187b64dfec0SJernej Škrabec reg &= ~GENMASK(nm->m.width + nm->m.shift - 1, nm->m.shift); 188b64dfec0SJernej Škrabec writel(reg, nm->common.base + nm->common.reg); 189b64dfec0SJernej Škrabec 190b64dfec0SJernej Škrabec spin_unlock_irqrestore(nm->common.lock, flags); 191b64dfec0SJernej Škrabec 192b64dfec0SJernej Škrabec ccu_frac_helper_enable(&nm->common, &nm->frac); 193b64dfec0SJernej Škrabec 1941d42460aSJernej Škrabec return ccu_frac_helper_set_rate(&nm->common, &nm->frac, 1951d42460aSJernej Škrabec rate, nm->lock); 196b64dfec0SJernej Škrabec } else { 1976174a1e2SMaxime Ripard ccu_frac_helper_disable(&nm->common, &nm->frac); 198b64dfec0SJernej Škrabec } 1996174a1e2SMaxime Ripard 20095ad8ed9SChen-Yu Tsai _nm.min_n = nm->n.min ?: 1; 2010c3c8e13SMaxime Ripard _nm.max_n = nm->n.max ?: 1 << nm->n.width; 2026e0d50daSMaxime Ripard _nm.min_m = 1; 203ee28648cSMaxime Ripard _nm.max_m = nm->m.max ?: 1 << nm->m.width; 20487ba9e59SMaxime Ripard 205392ba5faSChen-Yu Tsai if (ccu_sdm_helper_has_rate(&nm->common, &nm->sdm, rate)) { 206392ba5faSChen-Yu Tsai ccu_sdm_helper_enable(&nm->common, &nm->sdm, rate); 207392ba5faSChen-Yu Tsai 208392ba5faSChen-Yu Tsai /* Sigma delta modulation requires specific N and M factors */ 209392ba5faSChen-Yu Tsai ccu_sdm_helper_get_factors(&nm->common, &nm->sdm, rate, 210392ba5faSChen-Yu Tsai &_nm.m, &_nm.n); 211392ba5faSChen-Yu Tsai } else { 212392ba5faSChen-Yu Tsai ccu_sdm_helper_disable(&nm->common, &nm->sdm); 213ee28648cSMaxime Ripard ccu_nm_find_best(parent_rate, rate, &_nm); 214392ba5faSChen-Yu Tsai } 2156174a1e2SMaxime Ripard 2166174a1e2SMaxime Ripard spin_lock_irqsave(nm->common.lock, flags); 2176174a1e2SMaxime Ripard 2186174a1e2SMaxime Ripard reg = readl(nm->common.base + nm->common.reg); 2196174a1e2SMaxime Ripard reg &= ~GENMASK(nm->n.width + nm->n.shift - 1, nm->n.shift); 2206174a1e2SMaxime Ripard reg &= ~GENMASK(nm->m.width + nm->m.shift - 1, nm->m.shift); 2216174a1e2SMaxime Ripard 222e66f81bbSMaxime Ripard reg |= (_nm.n - nm->n.offset) << nm->n.shift; 223e66f81bbSMaxime Ripard reg |= (_nm.m - nm->m.offset) << nm->m.shift; 224e66f81bbSMaxime Ripard writel(reg, nm->common.base + nm->common.reg); 2256174a1e2SMaxime Ripard 2266174a1e2SMaxime Ripard spin_unlock_irqrestore(nm->common.lock, flags); 2276174a1e2SMaxime Ripard 2286174a1e2SMaxime Ripard ccu_helper_wait_for_lock(&nm->common, nm->lock); 2296174a1e2SMaxime Ripard 2306174a1e2SMaxime Ripard return 0; 2316174a1e2SMaxime Ripard } 2326174a1e2SMaxime Ripard 2336174a1e2SMaxime Ripard const struct clk_ops ccu_nm_ops = { 2346174a1e2SMaxime Ripard .disable = ccu_nm_disable, 2356174a1e2SMaxime Ripard .enable = ccu_nm_enable, 2366174a1e2SMaxime Ripard .is_enabled = ccu_nm_is_enabled, 2376174a1e2SMaxime Ripard 2386174a1e2SMaxime Ripard .recalc_rate = ccu_nm_recalc_rate, 2396174a1e2SMaxime Ripard .round_rate = ccu_nm_round_rate, 2406174a1e2SMaxime Ripard .set_rate = ccu_nm_set_rate, 2416174a1e2SMaxime Ripard }; 242551b62b1SSamuel Holland EXPORT_SYMBOL_NS_GPL(ccu_nm_ops, SUNXI_CCU); 243