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