xref: /linux/drivers/clk/sunxi-ng/ccu_nkm.c (revision 657f477a89acb25ba34414ac84a51a32c5013d7b)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2df6561e6SMaxime Ripard /*
3df6561e6SMaxime Ripard  * Copyright (C) 2016 Maxime Ripard
4df6561e6SMaxime Ripard  * Maxime Ripard <maxime.ripard@free-electrons.com>
5df6561e6SMaxime Ripard  */
6df6561e6SMaxime Ripard 
7df6561e6SMaxime Ripard #include <linux/clk-provider.h>
862e59c4eSStephen Boyd #include <linux/io.h>
9df6561e6SMaxime Ripard 
10df6561e6SMaxime Ripard #include "ccu_gate.h"
11df6561e6SMaxime Ripard #include "ccu_nkm.h"
12df6561e6SMaxime Ripard 
13df6561e6SMaxime Ripard struct _ccu_nkm {
146e0d50daSMaxime Ripard 	unsigned long	n, min_n, max_n;
156e0d50daSMaxime Ripard 	unsigned long	k, min_k, max_k;
166e0d50daSMaxime Ripard 	unsigned long	m, min_m, max_m;
17df6561e6SMaxime Ripard };
18df6561e6SMaxime Ripard 
19*657f477aSSamuel Holland static unsigned long ccu_nkm_find_best(unsigned long parent, unsigned long rate,
20df6561e6SMaxime Ripard 				       struct _ccu_nkm *nkm)
21df6561e6SMaxime Ripard {
22df6561e6SMaxime Ripard 	unsigned long best_rate = 0;
23df6561e6SMaxime Ripard 	unsigned long best_n = 0, best_k = 0, best_m = 0;
24df6561e6SMaxime Ripard 	unsigned long _n, _k, _m;
25df6561e6SMaxime Ripard 
266e0d50daSMaxime Ripard 	for (_k = nkm->min_k; _k <= nkm->max_k; _k++) {
276e0d50daSMaxime Ripard 		for (_n = nkm->min_n; _n <= nkm->max_n; _n++) {
286e0d50daSMaxime Ripard 			for (_m = nkm->min_m; _m <= nkm->max_m; _m++) {
29df6561e6SMaxime Ripard 				unsigned long tmp_rate;
30df6561e6SMaxime Ripard 
31df6561e6SMaxime Ripard 				tmp_rate = parent * _n * _k / _m;
32df6561e6SMaxime Ripard 
33df6561e6SMaxime Ripard 				if (tmp_rate > rate)
34df6561e6SMaxime Ripard 					continue;
35df6561e6SMaxime Ripard 				if ((rate - tmp_rate) < (rate - best_rate)) {
36df6561e6SMaxime Ripard 					best_rate = tmp_rate;
37df6561e6SMaxime Ripard 					best_n = _n;
38df6561e6SMaxime Ripard 					best_k = _k;
39df6561e6SMaxime Ripard 					best_m = _m;
40df6561e6SMaxime Ripard 				}
41df6561e6SMaxime Ripard 			}
42ee28648cSMaxime Ripard 		}
43ee28648cSMaxime Ripard 	}
44df6561e6SMaxime Ripard 
45df6561e6SMaxime Ripard 	nkm->n = best_n;
46df6561e6SMaxime Ripard 	nkm->k = best_k;
47df6561e6SMaxime Ripard 	nkm->m = best_m;
48*657f477aSSamuel Holland 
49*657f477aSSamuel Holland 	return best_rate;
50df6561e6SMaxime Ripard }
51df6561e6SMaxime Ripard 
52df6561e6SMaxime Ripard static void ccu_nkm_disable(struct clk_hw *hw)
53df6561e6SMaxime Ripard {
54df6561e6SMaxime Ripard 	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
55df6561e6SMaxime Ripard 
56df6561e6SMaxime Ripard 	return ccu_gate_helper_disable(&nkm->common, nkm->enable);
57df6561e6SMaxime Ripard }
58df6561e6SMaxime Ripard 
59df6561e6SMaxime Ripard static int ccu_nkm_enable(struct clk_hw *hw)
60df6561e6SMaxime Ripard {
61df6561e6SMaxime Ripard 	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
62df6561e6SMaxime Ripard 
63df6561e6SMaxime Ripard 	return ccu_gate_helper_enable(&nkm->common, nkm->enable);
64df6561e6SMaxime Ripard }
65df6561e6SMaxime Ripard 
66df6561e6SMaxime Ripard static int ccu_nkm_is_enabled(struct clk_hw *hw)
67df6561e6SMaxime Ripard {
68df6561e6SMaxime Ripard 	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
69df6561e6SMaxime Ripard 
70df6561e6SMaxime Ripard 	return ccu_gate_helper_is_enabled(&nkm->common, nkm->enable);
71df6561e6SMaxime Ripard }
72df6561e6SMaxime Ripard 
73df6561e6SMaxime Ripard static unsigned long ccu_nkm_recalc_rate(struct clk_hw *hw,
74df6561e6SMaxime Ripard 					unsigned long parent_rate)
75df6561e6SMaxime Ripard {
76df6561e6SMaxime Ripard 	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
77a6653773SIcenowy Zheng 	unsigned long n, m, k, rate;
78df6561e6SMaxime Ripard 	u32 reg;
79df6561e6SMaxime Ripard 
80df6561e6SMaxime Ripard 	reg = readl(nkm->common.base + nkm->common.reg);
81df6561e6SMaxime Ripard 
82df6561e6SMaxime Ripard 	n = reg >> nkm->n.shift;
83df6561e6SMaxime Ripard 	n &= (1 << nkm->n.width) - 1;
84e66f81bbSMaxime Ripard 	n += nkm->n.offset;
85e66f81bbSMaxime Ripard 	if (!n)
86e66f81bbSMaxime Ripard 		n++;
87df6561e6SMaxime Ripard 
88df6561e6SMaxime Ripard 	k = reg >> nkm->k.shift;
89df6561e6SMaxime Ripard 	k &= (1 << nkm->k.width) - 1;
90e66f81bbSMaxime Ripard 	k += nkm->k.offset;
91e66f81bbSMaxime Ripard 	if (!k)
92e66f81bbSMaxime Ripard 		k++;
93df6561e6SMaxime Ripard 
94df6561e6SMaxime Ripard 	m = reg >> nkm->m.shift;
95df6561e6SMaxime Ripard 	m &= (1 << nkm->m.width) - 1;
96e66f81bbSMaxime Ripard 	m += nkm->m.offset;
97e66f81bbSMaxime Ripard 	if (!m)
98e66f81bbSMaxime Ripard 		m++;
99df6561e6SMaxime Ripard 
100a6653773SIcenowy Zheng 	rate = parent_rate * n  * k / m;
101a6653773SIcenowy Zheng 
102a6653773SIcenowy Zheng 	if (nkm->common.features & CCU_FEATURE_FIXED_POSTDIV)
103a6653773SIcenowy Zheng 		rate /= nkm->fixed_post_div;
104a6653773SIcenowy Zheng 
105a6653773SIcenowy Zheng 	return rate;
106df6561e6SMaxime Ripard }
107df6561e6SMaxime Ripard 
108a3658359SChen-Yu Tsai static unsigned long ccu_nkm_round_rate(struct ccu_mux_internal *mux,
10910a8d9b9SMaxime Ripard 					struct clk_hw *hw,
11010a8d9b9SMaxime Ripard 					unsigned long *parent_rate,
111a3658359SChen-Yu Tsai 					unsigned long rate,
112a3658359SChen-Yu Tsai 					void *data)
113df6561e6SMaxime Ripard {
114a3658359SChen-Yu Tsai 	struct ccu_nkm *nkm = data;
115df6561e6SMaxime Ripard 	struct _ccu_nkm _nkm;
116df6561e6SMaxime Ripard 
1174162c5ceSChen-Yu Tsai 	_nkm.min_n = nkm->n.min ?: 1;
1180c3c8e13SMaxime Ripard 	_nkm.max_n = nkm->n.max ?: 1 << nkm->n.width;
1194162c5ceSChen-Yu Tsai 	_nkm.min_k = nkm->k.min ?: 1;
1200c3c8e13SMaxime Ripard 	_nkm.max_k = nkm->k.max ?: 1 << nkm->k.width;
1216e0d50daSMaxime Ripard 	_nkm.min_m = 1;
12287ba9e59SMaxime Ripard 	_nkm.max_m = nkm->m.max ?: 1 << nkm->m.width;
123df6561e6SMaxime Ripard 
124a6653773SIcenowy Zheng 	if (nkm->common.features & CCU_FEATURE_FIXED_POSTDIV)
125a6653773SIcenowy Zheng 		rate *= nkm->fixed_post_div;
126a6653773SIcenowy Zheng 
127*657f477aSSamuel Holland 	rate = ccu_nkm_find_best(*parent_rate, rate, &_nkm);
128a6653773SIcenowy Zheng 
129a6653773SIcenowy Zheng 	if (nkm->common.features & CCU_FEATURE_FIXED_POSTDIV)
130a6653773SIcenowy Zheng 		rate /= nkm->fixed_post_div;
131a6653773SIcenowy Zheng 
132a6653773SIcenowy Zheng 	return rate;
133a3658359SChen-Yu Tsai }
134a3658359SChen-Yu Tsai 
135a3658359SChen-Yu Tsai static int ccu_nkm_determine_rate(struct clk_hw *hw,
136a3658359SChen-Yu Tsai 				  struct clk_rate_request *req)
137a3658359SChen-Yu Tsai {
138a3658359SChen-Yu Tsai 	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
139a3658359SChen-Yu Tsai 
140a3658359SChen-Yu Tsai 	return ccu_mux_helper_determine_rate(&nkm->common, &nkm->mux,
141a3658359SChen-Yu Tsai 					     req, ccu_nkm_round_rate, nkm);
142df6561e6SMaxime Ripard }
143df6561e6SMaxime Ripard 
144df6561e6SMaxime Ripard static int ccu_nkm_set_rate(struct clk_hw *hw, unsigned long rate,
145df6561e6SMaxime Ripard 			   unsigned long parent_rate)
146df6561e6SMaxime Ripard {
147df6561e6SMaxime Ripard 	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
148df6561e6SMaxime Ripard 	struct _ccu_nkm _nkm;
149df6561e6SMaxime Ripard 	unsigned long flags;
150df6561e6SMaxime Ripard 	u32 reg;
151df6561e6SMaxime Ripard 
152a6653773SIcenowy Zheng 	if (nkm->common.features & CCU_FEATURE_FIXED_POSTDIV)
153a6653773SIcenowy Zheng 		rate *= nkm->fixed_post_div;
154a6653773SIcenowy Zheng 
1554162c5ceSChen-Yu Tsai 	_nkm.min_n = nkm->n.min ?: 1;
1560c3c8e13SMaxime Ripard 	_nkm.max_n = nkm->n.max ?: 1 << nkm->n.width;
1574162c5ceSChen-Yu Tsai 	_nkm.min_k = nkm->k.min ?: 1;
1580c3c8e13SMaxime Ripard 	_nkm.max_k = nkm->k.max ?: 1 << nkm->k.width;
1596e0d50daSMaxime Ripard 	_nkm.min_m = 1;
16087ba9e59SMaxime Ripard 	_nkm.max_m = nkm->m.max ?: 1 << nkm->m.width;
161df6561e6SMaxime Ripard 
162df6561e6SMaxime Ripard 	ccu_nkm_find_best(parent_rate, rate, &_nkm);
163df6561e6SMaxime Ripard 
164df6561e6SMaxime Ripard 	spin_lock_irqsave(nkm->common.lock, flags);
165df6561e6SMaxime Ripard 
166df6561e6SMaxime Ripard 	reg = readl(nkm->common.base + nkm->common.reg);
167df6561e6SMaxime Ripard 	reg &= ~GENMASK(nkm->n.width + nkm->n.shift - 1, nkm->n.shift);
168df6561e6SMaxime Ripard 	reg &= ~GENMASK(nkm->k.width + nkm->k.shift - 1, nkm->k.shift);
169df6561e6SMaxime Ripard 	reg &= ~GENMASK(nkm->m.width + nkm->m.shift - 1, nkm->m.shift);
170df6561e6SMaxime Ripard 
171e66f81bbSMaxime Ripard 	reg |= (_nkm.n - nkm->n.offset) << nkm->n.shift;
172e66f81bbSMaxime Ripard 	reg |= (_nkm.k - nkm->k.offset) << nkm->k.shift;
173e66f81bbSMaxime Ripard 	reg |= (_nkm.m - nkm->m.offset) << nkm->m.shift;
174df6561e6SMaxime Ripard 	writel(reg, nkm->common.base + nkm->common.reg);
175df6561e6SMaxime Ripard 
176df6561e6SMaxime Ripard 	spin_unlock_irqrestore(nkm->common.lock, flags);
177df6561e6SMaxime Ripard 
178df6561e6SMaxime Ripard 	ccu_helper_wait_for_lock(&nkm->common, nkm->lock);
179df6561e6SMaxime Ripard 
180df6561e6SMaxime Ripard 	return 0;
181df6561e6SMaxime Ripard }
182df6561e6SMaxime Ripard 
183a3658359SChen-Yu Tsai static u8 ccu_nkm_get_parent(struct clk_hw *hw)
184a3658359SChen-Yu Tsai {
185a3658359SChen-Yu Tsai 	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
186a3658359SChen-Yu Tsai 
187a3658359SChen-Yu Tsai 	return ccu_mux_helper_get_parent(&nkm->common, &nkm->mux);
188a3658359SChen-Yu Tsai }
189a3658359SChen-Yu Tsai 
190a3658359SChen-Yu Tsai static int ccu_nkm_set_parent(struct clk_hw *hw, u8 index)
191a3658359SChen-Yu Tsai {
192a3658359SChen-Yu Tsai 	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
193a3658359SChen-Yu Tsai 
194a3658359SChen-Yu Tsai 	return ccu_mux_helper_set_parent(&nkm->common, &nkm->mux, index);
195a3658359SChen-Yu Tsai }
196a3658359SChen-Yu Tsai 
197df6561e6SMaxime Ripard const struct clk_ops ccu_nkm_ops = {
198df6561e6SMaxime Ripard 	.disable	= ccu_nkm_disable,
199df6561e6SMaxime Ripard 	.enable		= ccu_nkm_enable,
200df6561e6SMaxime Ripard 	.is_enabled	= ccu_nkm_is_enabled,
201df6561e6SMaxime Ripard 
202a3658359SChen-Yu Tsai 	.get_parent	= ccu_nkm_get_parent,
203a3658359SChen-Yu Tsai 	.set_parent	= ccu_nkm_set_parent,
204a3658359SChen-Yu Tsai 
205a3658359SChen-Yu Tsai 	.determine_rate	= ccu_nkm_determine_rate,
206df6561e6SMaxime Ripard 	.recalc_rate	= ccu_nkm_recalc_rate,
207df6561e6SMaxime Ripard 	.set_rate	= ccu_nkm_set_rate,
208df6561e6SMaxime Ripard };
209551b62b1SSamuel Holland EXPORT_SYMBOL_NS_GPL(ccu_nkm_ops, SUNXI_CCU);
210