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 30ee28648cSMaxime Ripard static void 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; 55ee28648cSMaxime Ripard } 56ee28648cSMaxime Ripard 576174a1e2SMaxime Ripard static void ccu_nm_disable(struct clk_hw *hw) 586174a1e2SMaxime Ripard { 596174a1e2SMaxime Ripard struct ccu_nm *nm = hw_to_ccu_nm(hw); 606174a1e2SMaxime Ripard 616174a1e2SMaxime Ripard return ccu_gate_helper_disable(&nm->common, nm->enable); 626174a1e2SMaxime Ripard } 636174a1e2SMaxime Ripard 646174a1e2SMaxime Ripard static int ccu_nm_enable(struct clk_hw *hw) 656174a1e2SMaxime Ripard { 666174a1e2SMaxime Ripard struct ccu_nm *nm = hw_to_ccu_nm(hw); 676174a1e2SMaxime Ripard 686174a1e2SMaxime Ripard return ccu_gate_helper_enable(&nm->common, nm->enable); 696174a1e2SMaxime Ripard } 706174a1e2SMaxime Ripard 716174a1e2SMaxime Ripard static int ccu_nm_is_enabled(struct clk_hw *hw) 726174a1e2SMaxime Ripard { 736174a1e2SMaxime Ripard struct ccu_nm *nm = hw_to_ccu_nm(hw); 746174a1e2SMaxime Ripard 756174a1e2SMaxime Ripard return ccu_gate_helper_is_enabled(&nm->common, nm->enable); 766174a1e2SMaxime Ripard } 776174a1e2SMaxime Ripard 786174a1e2SMaxime Ripard static unsigned long ccu_nm_recalc_rate(struct clk_hw *hw, 796174a1e2SMaxime Ripard unsigned long parent_rate) 806174a1e2SMaxime Ripard { 816174a1e2SMaxime Ripard struct ccu_nm *nm = hw_to_ccu_nm(hw); 827d333ef1SChen-Yu Tsai unsigned long rate; 836174a1e2SMaxime Ripard unsigned long n, m; 846174a1e2SMaxime Ripard u32 reg; 856174a1e2SMaxime Ripard 867d333ef1SChen-Yu Tsai if (ccu_frac_helper_is_enabled(&nm->common, &nm->frac)) { 877d333ef1SChen-Yu Tsai rate = ccu_frac_helper_read_rate(&nm->common, &nm->frac); 887d333ef1SChen-Yu Tsai 897d333ef1SChen-Yu Tsai if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 907d333ef1SChen-Yu Tsai rate /= nm->fixed_post_div; 917d333ef1SChen-Yu Tsai 927d333ef1SChen-Yu Tsai return rate; 937d333ef1SChen-Yu Tsai } 946174a1e2SMaxime Ripard 956174a1e2SMaxime Ripard reg = readl(nm->common.base + nm->common.reg); 966174a1e2SMaxime Ripard 976174a1e2SMaxime Ripard n = reg >> nm->n.shift; 986174a1e2SMaxime Ripard n &= (1 << nm->n.width) - 1; 99e66f81bbSMaxime Ripard n += nm->n.offset; 100e66f81bbSMaxime Ripard if (!n) 101e66f81bbSMaxime Ripard n++; 1026174a1e2SMaxime Ripard 1036174a1e2SMaxime Ripard m = reg >> nm->m.shift; 1046174a1e2SMaxime Ripard m &= (1 << nm->m.width) - 1; 105e66f81bbSMaxime Ripard m += nm->m.offset; 106e66f81bbSMaxime Ripard if (!m) 107e66f81bbSMaxime Ripard m++; 1086174a1e2SMaxime Ripard 1097d333ef1SChen-Yu Tsai if (ccu_sdm_helper_is_enabled(&nm->common, &nm->sdm)) 1107d333ef1SChen-Yu Tsai rate = ccu_sdm_helper_read_rate(&nm->common, &nm->sdm, m, n); 1117d333ef1SChen-Yu Tsai else 11265b66576SJernej Skrabec rate = ccu_nm_calc_rate(parent_rate, n, m); 113392ba5faSChen-Yu Tsai 1147d333ef1SChen-Yu Tsai if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1157d333ef1SChen-Yu Tsai rate /= nm->fixed_post_div; 1167d333ef1SChen-Yu Tsai 1177d333ef1SChen-Yu Tsai return rate; 1186174a1e2SMaxime Ripard } 1196174a1e2SMaxime Ripard 1206174a1e2SMaxime Ripard static long ccu_nm_round_rate(struct clk_hw *hw, unsigned long rate, 1216174a1e2SMaxime Ripard unsigned long *parent_rate) 1226174a1e2SMaxime Ripard { 1236174a1e2SMaxime Ripard struct ccu_nm *nm = hw_to_ccu_nm(hw); 124ee28648cSMaxime Ripard struct _ccu_nm _nm; 1256174a1e2SMaxime Ripard 1267d333ef1SChen-Yu Tsai if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1277d333ef1SChen-Yu Tsai rate *= nm->fixed_post_div; 1284cdbc40dSChen-Yu Tsai 1292d2b61c1SJernej Skrabec if (rate < nm->min_rate) { 1302d2b61c1SJernej Skrabec rate = nm->min_rate; 1312d2b61c1SJernej Skrabec if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1322d2b61c1SJernej Skrabec rate /= nm->fixed_post_div; 1332d2b61c1SJernej Skrabec return rate; 1342d2b61c1SJernej Skrabec } 1352d2b61c1SJernej Skrabec 136cb54fbd2SJernej Skrabec if (nm->max_rate && rate > nm->max_rate) { 137cb54fbd2SJernej Skrabec rate = nm->max_rate; 138cb54fbd2SJernej Skrabec if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 139cb54fbd2SJernej Skrabec rate /= nm->fixed_post_div; 140cb54fbd2SJernej Skrabec return rate; 141cb54fbd2SJernej Skrabec } 142cb54fbd2SJernej Skrabec 1437d333ef1SChen-Yu Tsai if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) { 1447d333ef1SChen-Yu Tsai if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1457d333ef1SChen-Yu Tsai rate /= nm->fixed_post_div; 146392ba5faSChen-Yu Tsai return rate; 1477d333ef1SChen-Yu Tsai } 1487d333ef1SChen-Yu Tsai 1497d333ef1SChen-Yu Tsai if (ccu_sdm_helper_has_rate(&nm->common, &nm->sdm, rate)) { 1507d333ef1SChen-Yu Tsai if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1517d333ef1SChen-Yu Tsai rate /= nm->fixed_post_div; 1527d333ef1SChen-Yu Tsai return rate; 1537d333ef1SChen-Yu Tsai } 154392ba5faSChen-Yu Tsai 1554162c5ceSChen-Yu Tsai _nm.min_n = nm->n.min ?: 1; 1560c3c8e13SMaxime Ripard _nm.max_n = nm->n.max ?: 1 << nm->n.width; 1576e0d50daSMaxime Ripard _nm.min_m = 1; 158ee28648cSMaxime Ripard _nm.max_m = nm->m.max ?: 1 << nm->m.width; 15987ba9e59SMaxime Ripard 160ee28648cSMaxime Ripard ccu_nm_find_best(*parent_rate, rate, &_nm); 16165b66576SJernej Skrabec rate = ccu_nm_calc_rate(*parent_rate, _nm.n, _nm.m); 1626174a1e2SMaxime Ripard 1637d333ef1SChen-Yu Tsai if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1647d333ef1SChen-Yu Tsai rate /= nm->fixed_post_div; 1657d333ef1SChen-Yu Tsai 1667d333ef1SChen-Yu Tsai return rate; 1676174a1e2SMaxime Ripard } 1686174a1e2SMaxime Ripard 1696174a1e2SMaxime Ripard static int ccu_nm_set_rate(struct clk_hw *hw, unsigned long rate, 1706174a1e2SMaxime Ripard unsigned long parent_rate) 1716174a1e2SMaxime Ripard { 1726174a1e2SMaxime Ripard struct ccu_nm *nm = hw_to_ccu_nm(hw); 173ee28648cSMaxime Ripard struct _ccu_nm _nm; 1746174a1e2SMaxime Ripard unsigned long flags; 1756174a1e2SMaxime Ripard u32 reg; 1766174a1e2SMaxime Ripard 1777d333ef1SChen-Yu Tsai /* Adjust target rate according to post-dividers */ 1787d333ef1SChen-Yu Tsai if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV) 1797d333ef1SChen-Yu Tsai rate = rate * nm->fixed_post_div; 1807d333ef1SChen-Yu Tsai 181b64dfec0SJernej Škrabec if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) { 182b64dfec0SJernej Škrabec spin_lock_irqsave(nm->common.lock, flags); 183b64dfec0SJernej Škrabec 184b64dfec0SJernej Škrabec /* most SoCs require M to be 0 if fractional mode is used */ 185b64dfec0SJernej Škrabec reg = readl(nm->common.base + nm->common.reg); 186b64dfec0SJernej Škrabec reg &= ~GENMASK(nm->m.width + nm->m.shift - 1, nm->m.shift); 187b64dfec0SJernej Škrabec writel(reg, nm->common.base + nm->common.reg); 188b64dfec0SJernej Škrabec 189b64dfec0SJernej Škrabec spin_unlock_irqrestore(nm->common.lock, flags); 190b64dfec0SJernej Škrabec 191b64dfec0SJernej Škrabec ccu_frac_helper_enable(&nm->common, &nm->frac); 192b64dfec0SJernej Škrabec 1931d42460aSJernej Škrabec return ccu_frac_helper_set_rate(&nm->common, &nm->frac, 1941d42460aSJernej Škrabec rate, nm->lock); 195b64dfec0SJernej Škrabec } else { 1966174a1e2SMaxime Ripard ccu_frac_helper_disable(&nm->common, &nm->frac); 197b64dfec0SJernej Škrabec } 1986174a1e2SMaxime Ripard 19995ad8ed9SChen-Yu Tsai _nm.min_n = nm->n.min ?: 1; 2000c3c8e13SMaxime Ripard _nm.max_n = nm->n.max ?: 1 << nm->n.width; 2016e0d50daSMaxime Ripard _nm.min_m = 1; 202ee28648cSMaxime Ripard _nm.max_m = nm->m.max ?: 1 << nm->m.width; 20387ba9e59SMaxime Ripard 204392ba5faSChen-Yu Tsai if (ccu_sdm_helper_has_rate(&nm->common, &nm->sdm, rate)) { 205392ba5faSChen-Yu Tsai ccu_sdm_helper_enable(&nm->common, &nm->sdm, rate); 206392ba5faSChen-Yu Tsai 207392ba5faSChen-Yu Tsai /* Sigma delta modulation requires specific N and M factors */ 208392ba5faSChen-Yu Tsai ccu_sdm_helper_get_factors(&nm->common, &nm->sdm, rate, 209392ba5faSChen-Yu Tsai &_nm.m, &_nm.n); 210392ba5faSChen-Yu Tsai } else { 211392ba5faSChen-Yu Tsai ccu_sdm_helper_disable(&nm->common, &nm->sdm); 212ee28648cSMaxime Ripard ccu_nm_find_best(parent_rate, rate, &_nm); 213392ba5faSChen-Yu Tsai } 2146174a1e2SMaxime Ripard 2156174a1e2SMaxime Ripard spin_lock_irqsave(nm->common.lock, flags); 2166174a1e2SMaxime Ripard 2176174a1e2SMaxime Ripard reg = readl(nm->common.base + nm->common.reg); 2186174a1e2SMaxime Ripard reg &= ~GENMASK(nm->n.width + nm->n.shift - 1, nm->n.shift); 2196174a1e2SMaxime Ripard reg &= ~GENMASK(nm->m.width + nm->m.shift - 1, nm->m.shift); 2206174a1e2SMaxime Ripard 221e66f81bbSMaxime Ripard reg |= (_nm.n - nm->n.offset) << nm->n.shift; 222e66f81bbSMaxime Ripard reg |= (_nm.m - nm->m.offset) << nm->m.shift; 223e66f81bbSMaxime Ripard writel(reg, nm->common.base + nm->common.reg); 2246174a1e2SMaxime Ripard 2256174a1e2SMaxime Ripard spin_unlock_irqrestore(nm->common.lock, flags); 2266174a1e2SMaxime Ripard 2276174a1e2SMaxime Ripard ccu_helper_wait_for_lock(&nm->common, nm->lock); 2286174a1e2SMaxime Ripard 2296174a1e2SMaxime Ripard return 0; 2306174a1e2SMaxime Ripard } 2316174a1e2SMaxime Ripard 2326174a1e2SMaxime Ripard const struct clk_ops ccu_nm_ops = { 2336174a1e2SMaxime Ripard .disable = ccu_nm_disable, 2346174a1e2SMaxime Ripard .enable = ccu_nm_enable, 2356174a1e2SMaxime Ripard .is_enabled = ccu_nm_is_enabled, 2366174a1e2SMaxime Ripard 2376174a1e2SMaxime Ripard .recalc_rate = ccu_nm_recalc_rate, 2386174a1e2SMaxime Ripard .round_rate = ccu_nm_round_rate, 2396174a1e2SMaxime Ripard .set_rate = ccu_nm_set_rate, 2406174a1e2SMaxime Ripard }; 241*551b62b1SSamuel Holland EXPORT_SYMBOL_NS_GPL(ccu_nm_ops, SUNXI_CCU); 242