xref: /linux/drivers/clk/sunxi-ng/ccu_nkm.c (revision a6653773d6e5f077bdc6cdd2c57cecdcb908d035)
1df6561e6SMaxime Ripard /*
2df6561e6SMaxime Ripard  * Copyright (C) 2016 Maxime Ripard
3df6561e6SMaxime Ripard  * Maxime Ripard <maxime.ripard@free-electrons.com>
4df6561e6SMaxime Ripard  *
5df6561e6SMaxime Ripard  * This program is free software; you can redistribute it and/or
6df6561e6SMaxime Ripard  * modify it under the terms of the GNU General Public License as
7df6561e6SMaxime Ripard  * published by the Free Software Foundation; either version 2 of
8df6561e6SMaxime Ripard  * the License, or (at your option) any later version.
9df6561e6SMaxime Ripard  */
10df6561e6SMaxime Ripard 
11df6561e6SMaxime Ripard #include <linux/clk-provider.h>
12df6561e6SMaxime Ripard 
13df6561e6SMaxime Ripard #include "ccu_gate.h"
14df6561e6SMaxime Ripard #include "ccu_nkm.h"
15df6561e6SMaxime Ripard 
16df6561e6SMaxime Ripard struct _ccu_nkm {
176e0d50daSMaxime Ripard 	unsigned long	n, min_n, max_n;
186e0d50daSMaxime Ripard 	unsigned long	k, min_k, max_k;
196e0d50daSMaxime Ripard 	unsigned long	m, min_m, max_m;
20df6561e6SMaxime Ripard };
21df6561e6SMaxime Ripard 
22df6561e6SMaxime Ripard static void ccu_nkm_find_best(unsigned long parent, unsigned long rate,
23df6561e6SMaxime Ripard 			      struct _ccu_nkm *nkm)
24df6561e6SMaxime Ripard {
25df6561e6SMaxime Ripard 	unsigned long best_rate = 0;
26df6561e6SMaxime Ripard 	unsigned long best_n = 0, best_k = 0, best_m = 0;
27df6561e6SMaxime Ripard 	unsigned long _n, _k, _m;
28df6561e6SMaxime Ripard 
296e0d50daSMaxime Ripard 	for (_k = nkm->min_k; _k <= nkm->max_k; _k++) {
306e0d50daSMaxime Ripard 		for (_n = nkm->min_n; _n <= nkm->max_n; _n++) {
316e0d50daSMaxime Ripard 			for (_m = nkm->min_m; _m <= nkm->max_m; _m++) {
32df6561e6SMaxime Ripard 				unsigned long tmp_rate;
33df6561e6SMaxime Ripard 
34df6561e6SMaxime Ripard 				tmp_rate = parent * _n * _k / _m;
35df6561e6SMaxime Ripard 
36df6561e6SMaxime Ripard 				if (tmp_rate > rate)
37df6561e6SMaxime Ripard 					continue;
38df6561e6SMaxime Ripard 				if ((rate - tmp_rate) < (rate - best_rate)) {
39df6561e6SMaxime Ripard 					best_rate = tmp_rate;
40df6561e6SMaxime Ripard 					best_n = _n;
41df6561e6SMaxime Ripard 					best_k = _k;
42df6561e6SMaxime Ripard 					best_m = _m;
43df6561e6SMaxime Ripard 				}
44df6561e6SMaxime Ripard 			}
45ee28648cSMaxime Ripard 		}
46ee28648cSMaxime Ripard 	}
47df6561e6SMaxime Ripard 
48df6561e6SMaxime Ripard 	nkm->n = best_n;
49df6561e6SMaxime Ripard 	nkm->k = best_k;
50df6561e6SMaxime Ripard 	nkm->m = best_m;
51df6561e6SMaxime Ripard }
52df6561e6SMaxime Ripard 
53df6561e6SMaxime Ripard static void ccu_nkm_disable(struct clk_hw *hw)
54df6561e6SMaxime Ripard {
55df6561e6SMaxime Ripard 	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
56df6561e6SMaxime Ripard 
57df6561e6SMaxime Ripard 	return ccu_gate_helper_disable(&nkm->common, nkm->enable);
58df6561e6SMaxime Ripard }
59df6561e6SMaxime Ripard 
60df6561e6SMaxime Ripard static int ccu_nkm_enable(struct clk_hw *hw)
61df6561e6SMaxime Ripard {
62df6561e6SMaxime Ripard 	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
63df6561e6SMaxime Ripard 
64df6561e6SMaxime Ripard 	return ccu_gate_helper_enable(&nkm->common, nkm->enable);
65df6561e6SMaxime Ripard }
66df6561e6SMaxime Ripard 
67df6561e6SMaxime Ripard static int ccu_nkm_is_enabled(struct clk_hw *hw)
68df6561e6SMaxime Ripard {
69df6561e6SMaxime Ripard 	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
70df6561e6SMaxime Ripard 
71df6561e6SMaxime Ripard 	return ccu_gate_helper_is_enabled(&nkm->common, nkm->enable);
72df6561e6SMaxime Ripard }
73df6561e6SMaxime Ripard 
74df6561e6SMaxime Ripard static unsigned long ccu_nkm_recalc_rate(struct clk_hw *hw,
75df6561e6SMaxime Ripard 					unsigned long parent_rate)
76df6561e6SMaxime Ripard {
77df6561e6SMaxime Ripard 	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
78*a6653773SIcenowy Zheng 	unsigned long n, m, k, rate;
79df6561e6SMaxime Ripard 	u32 reg;
80df6561e6SMaxime Ripard 
81df6561e6SMaxime Ripard 	reg = readl(nkm->common.base + nkm->common.reg);
82df6561e6SMaxime Ripard 
83df6561e6SMaxime Ripard 	n = reg >> nkm->n.shift;
84df6561e6SMaxime Ripard 	n &= (1 << nkm->n.width) - 1;
85e66f81bbSMaxime Ripard 	n += nkm->n.offset;
86e66f81bbSMaxime Ripard 	if (!n)
87e66f81bbSMaxime Ripard 		n++;
88df6561e6SMaxime Ripard 
89df6561e6SMaxime Ripard 	k = reg >> nkm->k.shift;
90df6561e6SMaxime Ripard 	k &= (1 << nkm->k.width) - 1;
91e66f81bbSMaxime Ripard 	k += nkm->k.offset;
92e66f81bbSMaxime Ripard 	if (!k)
93e66f81bbSMaxime Ripard 		k++;
94df6561e6SMaxime Ripard 
95df6561e6SMaxime Ripard 	m = reg >> nkm->m.shift;
96df6561e6SMaxime Ripard 	m &= (1 << nkm->m.width) - 1;
97e66f81bbSMaxime Ripard 	m += nkm->m.offset;
98e66f81bbSMaxime Ripard 	if (!m)
99e66f81bbSMaxime Ripard 		m++;
100df6561e6SMaxime Ripard 
101*a6653773SIcenowy Zheng 	rate = parent_rate * n  * k / m;
102*a6653773SIcenowy Zheng 
103*a6653773SIcenowy Zheng 	if (nkm->common.features & CCU_FEATURE_FIXED_POSTDIV)
104*a6653773SIcenowy Zheng 		rate /= nkm->fixed_post_div;
105*a6653773SIcenowy Zheng 
106*a6653773SIcenowy Zheng 	return rate;
107df6561e6SMaxime Ripard }
108df6561e6SMaxime Ripard 
109a3658359SChen-Yu Tsai static unsigned long ccu_nkm_round_rate(struct ccu_mux_internal *mux,
11010a8d9b9SMaxime Ripard 					struct clk_hw *hw,
11110a8d9b9SMaxime Ripard 					unsigned long *parent_rate,
112a3658359SChen-Yu Tsai 					unsigned long rate,
113a3658359SChen-Yu Tsai 					void *data)
114df6561e6SMaxime Ripard {
115a3658359SChen-Yu Tsai 	struct ccu_nkm *nkm = data;
116df6561e6SMaxime Ripard 	struct _ccu_nkm _nkm;
117df6561e6SMaxime Ripard 
1184162c5ceSChen-Yu Tsai 	_nkm.min_n = nkm->n.min ?: 1;
1190c3c8e13SMaxime Ripard 	_nkm.max_n = nkm->n.max ?: 1 << nkm->n.width;
1204162c5ceSChen-Yu Tsai 	_nkm.min_k = nkm->k.min ?: 1;
1210c3c8e13SMaxime Ripard 	_nkm.max_k = nkm->k.max ?: 1 << nkm->k.width;
1226e0d50daSMaxime Ripard 	_nkm.min_m = 1;
12387ba9e59SMaxime Ripard 	_nkm.max_m = nkm->m.max ?: 1 << nkm->m.width;
124df6561e6SMaxime Ripard 
125*a6653773SIcenowy Zheng 	if (nkm->common.features & CCU_FEATURE_FIXED_POSTDIV)
126*a6653773SIcenowy Zheng 		rate *= nkm->fixed_post_div;
127*a6653773SIcenowy Zheng 
12810a8d9b9SMaxime Ripard 	ccu_nkm_find_best(*parent_rate, rate, &_nkm);
129df6561e6SMaxime Ripard 
130*a6653773SIcenowy Zheng 	rate = *parent_rate * _nkm.n * _nkm.k / _nkm.m;
131*a6653773SIcenowy Zheng 
132*a6653773SIcenowy Zheng 	if (nkm->common.features & CCU_FEATURE_FIXED_POSTDIV)
133*a6653773SIcenowy Zheng 		rate /= nkm->fixed_post_div;
134*a6653773SIcenowy Zheng 
135*a6653773SIcenowy Zheng 	return rate;
136a3658359SChen-Yu Tsai }
137a3658359SChen-Yu Tsai 
138a3658359SChen-Yu Tsai static int ccu_nkm_determine_rate(struct clk_hw *hw,
139a3658359SChen-Yu Tsai 				  struct clk_rate_request *req)
140a3658359SChen-Yu Tsai {
141a3658359SChen-Yu Tsai 	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
142a3658359SChen-Yu Tsai 
143a3658359SChen-Yu Tsai 	return ccu_mux_helper_determine_rate(&nkm->common, &nkm->mux,
144a3658359SChen-Yu Tsai 					     req, ccu_nkm_round_rate, nkm);
145df6561e6SMaxime Ripard }
146df6561e6SMaxime Ripard 
147df6561e6SMaxime Ripard static int ccu_nkm_set_rate(struct clk_hw *hw, unsigned long rate,
148df6561e6SMaxime Ripard 			   unsigned long parent_rate)
149df6561e6SMaxime Ripard {
150df6561e6SMaxime Ripard 	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
151df6561e6SMaxime Ripard 	struct _ccu_nkm _nkm;
152df6561e6SMaxime Ripard 	unsigned long flags;
153df6561e6SMaxime Ripard 	u32 reg;
154df6561e6SMaxime Ripard 
155*a6653773SIcenowy Zheng 	if (nkm->common.features & CCU_FEATURE_FIXED_POSTDIV)
156*a6653773SIcenowy Zheng 		rate *= nkm->fixed_post_div;
157*a6653773SIcenowy Zheng 
1584162c5ceSChen-Yu Tsai 	_nkm.min_n = nkm->n.min ?: 1;
1590c3c8e13SMaxime Ripard 	_nkm.max_n = nkm->n.max ?: 1 << nkm->n.width;
1604162c5ceSChen-Yu Tsai 	_nkm.min_k = nkm->k.min ?: 1;
1610c3c8e13SMaxime Ripard 	_nkm.max_k = nkm->k.max ?: 1 << nkm->k.width;
1626e0d50daSMaxime Ripard 	_nkm.min_m = 1;
16387ba9e59SMaxime Ripard 	_nkm.max_m = nkm->m.max ?: 1 << nkm->m.width;
164df6561e6SMaxime Ripard 
165df6561e6SMaxime Ripard 	ccu_nkm_find_best(parent_rate, rate, &_nkm);
166df6561e6SMaxime Ripard 
167df6561e6SMaxime Ripard 	spin_lock_irqsave(nkm->common.lock, flags);
168df6561e6SMaxime Ripard 
169df6561e6SMaxime Ripard 	reg = readl(nkm->common.base + nkm->common.reg);
170df6561e6SMaxime Ripard 	reg &= ~GENMASK(nkm->n.width + nkm->n.shift - 1, nkm->n.shift);
171df6561e6SMaxime Ripard 	reg &= ~GENMASK(nkm->k.width + nkm->k.shift - 1, nkm->k.shift);
172df6561e6SMaxime Ripard 	reg &= ~GENMASK(nkm->m.width + nkm->m.shift - 1, nkm->m.shift);
173df6561e6SMaxime Ripard 
174e66f81bbSMaxime Ripard 	reg |= (_nkm.n - nkm->n.offset) << nkm->n.shift;
175e66f81bbSMaxime Ripard 	reg |= (_nkm.k - nkm->k.offset) << nkm->k.shift;
176e66f81bbSMaxime Ripard 	reg |= (_nkm.m - nkm->m.offset) << nkm->m.shift;
177df6561e6SMaxime Ripard 	writel(reg, nkm->common.base + nkm->common.reg);
178df6561e6SMaxime Ripard 
179df6561e6SMaxime Ripard 	spin_unlock_irqrestore(nkm->common.lock, flags);
180df6561e6SMaxime Ripard 
181df6561e6SMaxime Ripard 	ccu_helper_wait_for_lock(&nkm->common, nkm->lock);
182df6561e6SMaxime Ripard 
183df6561e6SMaxime Ripard 	return 0;
184df6561e6SMaxime Ripard }
185df6561e6SMaxime Ripard 
186a3658359SChen-Yu Tsai static u8 ccu_nkm_get_parent(struct clk_hw *hw)
187a3658359SChen-Yu Tsai {
188a3658359SChen-Yu Tsai 	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
189a3658359SChen-Yu Tsai 
190a3658359SChen-Yu Tsai 	return ccu_mux_helper_get_parent(&nkm->common, &nkm->mux);
191a3658359SChen-Yu Tsai }
192a3658359SChen-Yu Tsai 
193a3658359SChen-Yu Tsai static int ccu_nkm_set_parent(struct clk_hw *hw, u8 index)
194a3658359SChen-Yu Tsai {
195a3658359SChen-Yu Tsai 	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
196a3658359SChen-Yu Tsai 
197a3658359SChen-Yu Tsai 	return ccu_mux_helper_set_parent(&nkm->common, &nkm->mux, index);
198a3658359SChen-Yu Tsai }
199a3658359SChen-Yu Tsai 
200df6561e6SMaxime Ripard const struct clk_ops ccu_nkm_ops = {
201df6561e6SMaxime Ripard 	.disable	= ccu_nkm_disable,
202df6561e6SMaxime Ripard 	.enable		= ccu_nkm_enable,
203df6561e6SMaxime Ripard 	.is_enabled	= ccu_nkm_is_enabled,
204df6561e6SMaxime Ripard 
205a3658359SChen-Yu Tsai 	.get_parent	= ccu_nkm_get_parent,
206a3658359SChen-Yu Tsai 	.set_parent	= ccu_nkm_set_parent,
207a3658359SChen-Yu Tsai 
208a3658359SChen-Yu Tsai 	.determine_rate	= ccu_nkm_determine_rate,
209df6561e6SMaxime Ripard 	.recalc_rate	= ccu_nkm_recalc_rate,
210df6561e6SMaxime Ripard 	.set_rate	= ccu_nkm_set_rate,
211df6561e6SMaxime Ripard };
212