xref: /openbmc/linux/drivers/clk/meson/sclk-div.c (revision 9be08a27)
1 // SPDX-License-Identifier: (GPL-2.0 OR MIT)
2 /*
3  * Copyright (c) 2018 BayLibre, SAS.
4  * Author: Jerome Brunet <jbrunet@baylibre.com>
5  *
6  * Sample clock generator divider:
7  * This HW divider gates with value 0 but is otherwise a zero based divider:
8  *
9  * val >= 1
10  * divider = val + 1
11  *
12  * The duty cycle may also be set for the LR clock variant. The duty cycle
13  * ratio is:
14  *
15  * hi = [0 - val]
16  * duty_cycle = (1 + hi) / (1 + val)
17  */
18 
19 #include "clkc-audio.h"
20 
21 static inline struct meson_sclk_div_data *
22 meson_sclk_div_data(struct clk_regmap *clk)
23 {
24 	return (struct meson_sclk_div_data *)clk->data;
25 }
26 
27 static int sclk_div_maxval(struct meson_sclk_div_data *sclk)
28 {
29 	return (1 << sclk->div.width) - 1;
30 }
31 
32 static int sclk_div_maxdiv(struct meson_sclk_div_data *sclk)
33 {
34 	return sclk_div_maxval(sclk) + 1;
35 }
36 
37 static int sclk_div_getdiv(struct clk_hw *hw, unsigned long rate,
38 			   unsigned long prate, int maxdiv)
39 {
40 	int div = DIV_ROUND_CLOSEST_ULL((u64)prate, rate);
41 
42 	return clamp(div, 2, maxdiv);
43 }
44 
45 static int sclk_div_bestdiv(struct clk_hw *hw, unsigned long rate,
46 			    unsigned long *prate,
47 			    struct meson_sclk_div_data *sclk)
48 {
49 	struct clk_hw *parent = clk_hw_get_parent(hw);
50 	int bestdiv = 0, i;
51 	unsigned long maxdiv, now, parent_now;
52 	unsigned long best = 0, best_parent = 0;
53 
54 	if (!rate)
55 		rate = 1;
56 
57 	maxdiv = sclk_div_maxdiv(sclk);
58 
59 	if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT))
60 		return sclk_div_getdiv(hw, rate, *prate, maxdiv);
61 
62 	/*
63 	 * The maximum divider we can use without overflowing
64 	 * unsigned long in rate * i below
65 	 */
66 	maxdiv = min(ULONG_MAX / rate, maxdiv);
67 
68 	for (i = 2; i <= maxdiv; i++) {
69 		/*
70 		 * It's the most ideal case if the requested rate can be
71 		 * divided from parent clock without needing to change
72 		 * parent rate, so return the divider immediately.
73 		 */
74 		if (rate * i == *prate)
75 			return i;
76 
77 		parent_now = clk_hw_round_rate(parent, rate * i);
78 		now = DIV_ROUND_UP_ULL((u64)parent_now, i);
79 
80 		if (abs(rate - now) < abs(rate - best)) {
81 			bestdiv = i;
82 			best = now;
83 			best_parent = parent_now;
84 		}
85 	}
86 
87 	if (!bestdiv)
88 		bestdiv = sclk_div_maxdiv(sclk);
89 	else
90 		*prate = best_parent;
91 
92 	return bestdiv;
93 }
94 
95 static long sclk_div_round_rate(struct clk_hw *hw, unsigned long rate,
96 				unsigned long *prate)
97 {
98 	struct clk_regmap *clk = to_clk_regmap(hw);
99 	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
100 	int div;
101 
102 	div = sclk_div_bestdiv(hw, rate, prate, sclk);
103 
104 	return DIV_ROUND_UP_ULL((u64)*prate, div);
105 }
106 
107 static void sclk_apply_ratio(struct clk_regmap *clk,
108 			     struct meson_sclk_div_data *sclk)
109 {
110 	unsigned int hi = DIV_ROUND_CLOSEST(sclk->cached_div *
111 					    sclk->cached_duty.num,
112 					    sclk->cached_duty.den);
113 
114 	if (hi)
115 		hi -= 1;
116 
117 	meson_parm_write(clk->map, &sclk->hi, hi);
118 }
119 
120 static int sclk_div_set_duty_cycle(struct clk_hw *hw,
121 				   struct clk_duty *duty)
122 {
123 	struct clk_regmap *clk = to_clk_regmap(hw);
124 	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
125 
126 	if (MESON_PARM_APPLICABLE(&sclk->hi)) {
127 		memcpy(&sclk->cached_duty, duty, sizeof(*duty));
128 		sclk_apply_ratio(clk, sclk);
129 	}
130 
131 	return 0;
132 }
133 
134 static int sclk_div_get_duty_cycle(struct clk_hw *hw,
135 				   struct clk_duty *duty)
136 {
137 	struct clk_regmap *clk = to_clk_regmap(hw);
138 	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
139 	int hi;
140 
141 	if (!MESON_PARM_APPLICABLE(&sclk->hi)) {
142 		duty->num = 1;
143 		duty->den = 2;
144 		return 0;
145 	}
146 
147 	hi = meson_parm_read(clk->map, &sclk->hi);
148 	duty->num = hi + 1;
149 	duty->den = sclk->cached_div;
150 	return 0;
151 }
152 
153 static void sclk_apply_divider(struct clk_regmap *clk,
154 			       struct meson_sclk_div_data *sclk)
155 {
156 	if (MESON_PARM_APPLICABLE(&sclk->hi))
157 		sclk_apply_ratio(clk, sclk);
158 
159 	meson_parm_write(clk->map, &sclk->div, sclk->cached_div - 1);
160 }
161 
162 static int sclk_div_set_rate(struct clk_hw *hw, unsigned long rate,
163 			     unsigned long prate)
164 {
165 	struct clk_regmap *clk = to_clk_regmap(hw);
166 	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
167 	unsigned long maxdiv = sclk_div_maxdiv(sclk);
168 
169 	sclk->cached_div = sclk_div_getdiv(hw, rate, prate, maxdiv);
170 
171 	if (clk_hw_is_enabled(hw))
172 		sclk_apply_divider(clk, sclk);
173 
174 	return 0;
175 }
176 
177 static unsigned long sclk_div_recalc_rate(struct clk_hw *hw,
178 					  unsigned long prate)
179 {
180 	struct clk_regmap *clk = to_clk_regmap(hw);
181 	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
182 
183 	return DIV_ROUND_UP_ULL((u64)prate, sclk->cached_div);
184 }
185 
186 static int sclk_div_enable(struct clk_hw *hw)
187 {
188 	struct clk_regmap *clk = to_clk_regmap(hw);
189 	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
190 
191 	sclk_apply_divider(clk, sclk);
192 
193 	return 0;
194 }
195 
196 static void sclk_div_disable(struct clk_hw *hw)
197 {
198 	struct clk_regmap *clk = to_clk_regmap(hw);
199 	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
200 
201 	meson_parm_write(clk->map, &sclk->div, 0);
202 }
203 
204 static int sclk_div_is_enabled(struct clk_hw *hw)
205 {
206 	struct clk_regmap *clk = to_clk_regmap(hw);
207 	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
208 
209 	if (meson_parm_read(clk->map, &sclk->div))
210 		return 1;
211 
212 	return 0;
213 }
214 
215 static void sclk_div_init(struct clk_hw *hw)
216 {
217 	struct clk_regmap *clk = to_clk_regmap(hw);
218 	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
219 	unsigned int val;
220 
221 	val = meson_parm_read(clk->map, &sclk->div);
222 
223 	/* if the divider is initially disabled, assume max */
224 	if (!val)
225 		sclk->cached_div = sclk_div_maxdiv(sclk);
226 	else
227 		sclk->cached_div = val + 1;
228 
229 	sclk_div_get_duty_cycle(hw, &sclk->cached_duty);
230 }
231 
232 const struct clk_ops meson_sclk_div_ops = {
233 	.recalc_rate	= sclk_div_recalc_rate,
234 	.round_rate	= sclk_div_round_rate,
235 	.set_rate	= sclk_div_set_rate,
236 	.enable		= sclk_div_enable,
237 	.disable	= sclk_div_disable,
238 	.is_enabled	= sclk_div_is_enabled,
239 	.get_duty_cycle	= sclk_div_get_duty_cycle,
240 	.set_duty_cycle = sclk_div_set_duty_cycle,
241 	.init		= sclk_div_init,
242 };
243 EXPORT_SYMBOL_GPL(meson_sclk_div_ops);
244