xref: /netbsd-src/sys/arch/arm/sunxi/sunxi_ccu_fractional.c (revision fa0a6254561fc2f4fe4eab5986bc774f4fff263d)
1 /* $NetBSD: sunxi_ccu_fractional.c,v 1.6 2019/11/23 21:30:41 jmcneill Exp $ */
2 
3 /*-
4  * Copyright (c) 2017 Jared McNeill <jmcneill@invisible.ca>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __KERNEL_RCSID(0, "$NetBSD: sunxi_ccu_fractional.c,v 1.6 2019/11/23 21:30:41 jmcneill Exp $");
31 
32 #include <sys/param.h>
33 #include <sys/bus.h>
34 
35 #include <dev/clk/clk_backend.h>
36 
37 #include <arm/sunxi/sunxi_ccu.h>
38 
39 int
sunxi_ccu_fractional_enable(struct sunxi_ccu_softc * sc,struct sunxi_ccu_clk * clk,int enable)40 sunxi_ccu_fractional_enable(struct sunxi_ccu_softc *sc,
41     struct sunxi_ccu_clk *clk, int enable)
42 {
43 	struct sunxi_ccu_fractional *fractional = &clk->u.fractional;
44 	uint32_t val;
45 
46 	KASSERT(clk->type == SUNXI_CCU_FRACTIONAL);
47 
48 	if (!fractional->enable)
49 		return enable ? 0 : EINVAL;
50 
51 	val = CCU_READ(sc, fractional->reg);
52 	if (enable)
53 		val |= fractional->enable;
54 	else
55 		val &= ~fractional->enable;
56 	CCU_WRITE(sc, fractional->reg, val);
57 
58 	return 0;
59 }
60 
61 u_int
sunxi_ccu_fractional_get_rate(struct sunxi_ccu_softc * sc,struct sunxi_ccu_clk * clk)62 sunxi_ccu_fractional_get_rate(struct sunxi_ccu_softc *sc,
63     struct sunxi_ccu_clk *clk)
64 {
65 	struct sunxi_ccu_fractional *fractional = &clk->u.fractional;
66 	struct clk *clkp, *clkp_parent;
67 	u_int rate, m;
68 	uint32_t val;
69 
70 	KASSERT(clk->type == SUNXI_CCU_FRACTIONAL);
71 
72 	clkp = &clk->base;
73 	clkp_parent = clk_get_parent(clkp);
74 	if (clkp_parent == NULL)
75 		return 0;
76 
77 	rate = clk_get_rate(clkp_parent);
78 	if (rate == 0)
79 		return 0;
80 
81 	val = CCU_READ(sc, fractional->reg);
82 
83 	if (fractional->prediv != 0) {
84 		rate = rate / (__SHIFTOUT(val, fractional->prediv) + 1);
85 	} else if (fractional->prediv_val > 0) {
86 		rate = rate / fractional->prediv_val;
87 	}
88 
89 	if (fractional->enable && !(val & fractional->enable))
90 		return 0;
91 
92 	if ((val & fractional->div_en) == 0) {
93 		int sel = __SHIFTOUT(val, fractional->frac_sel);
94 		return fractional->frac[sel];
95 	}
96 	m = __SHIFTOUT(val, fractional->m);
97 
98 	if (fractional->flags & SUNXI_CCU_FRACTIONAL_PLUSONE)
99 		m++;
100 
101 	return rate * m;
102 }
103 
104 int
sunxi_ccu_fractional_set_rate(struct sunxi_ccu_softc * sc,struct sunxi_ccu_clk * clk,u_int new_rate)105 sunxi_ccu_fractional_set_rate(struct sunxi_ccu_softc *sc,
106     struct sunxi_ccu_clk *clk, u_int new_rate)
107 {
108 	struct sunxi_ccu_fractional *fractional = &clk->u.fractional;
109 	struct clk *clkp, *clkp_parent;
110 	u_int parent_rate, best_rate, best_m;
111 	u_int m, rate;
112 	int best_diff;
113 	uint32_t val;
114 	int i;
115 
116 	clkp = &clk->base;
117 	clkp_parent = clk_get_parent(clkp);
118 	if (clkp_parent == NULL)
119 		return ENXIO;
120 
121 	parent_rate = clk_get_rate(clkp_parent);
122 	if (parent_rate == 0)
123 		return (new_rate == 0) ? 0 : ERANGE;
124 
125 	val = CCU_READ(sc, fractional->reg);
126 
127 	if (fractional->prediv != 0) {
128 		if (fractional->prediv_val > 0) {
129 			val &= ~fractional->prediv;
130 			val |= __SHIFTIN(fractional->prediv_val - 1,
131 					 fractional->prediv);
132 		}
133 		parent_rate = parent_rate / (__SHIFTOUT(val, fractional->prediv) + 1);
134 	} else if (fractional->prediv_val > 0) {
135 		parent_rate = parent_rate / fractional->prediv_val;
136 	}
137 
138 	for (i = 0; i < __arraycount(fractional->frac); i++) {
139 		if (fractional->frac[i] == new_rate) {
140 			val &= ~fractional->prediv;
141 			val &= ~fractional->div_en;
142 			val &= ~fractional->frac_sel;
143 			val |= __SHIFTIN(i, fractional->frac_sel);
144 			if (fractional->flags & SUNXI_CCU_FRACTIONAL_SET_ENABLE)
145 				val |= fractional->enable;
146 			CCU_WRITE(sc, fractional->reg, val);
147 			return 0;
148 		}
149 	}
150 	val |= fractional->div_en;
151 
152 	best_rate = 0;
153 	best_diff = INT_MAX;
154 
155 	for (m = fractional->m_min; m <= fractional->m_max; m++) {
156 		rate = parent_rate * m;
157 		const int diff = abs(new_rate - rate);
158 		if (diff < best_diff) {
159 			best_diff = diff;
160 			best_rate = rate;
161 			best_m = m;
162 			if (diff == 0)
163 				break;
164 		}
165 	}
166 
167 	if (best_rate == 0)
168 		return ERANGE;
169 
170 	if (fractional->flags & SUNXI_CCU_FRACTIONAL_PLUSONE)
171 		best_m--;
172 
173 	val &= ~fractional->m;
174 	val |= __SHIFTIN(best_m, fractional->m);
175 	if (fractional->flags & SUNXI_CCU_FRACTIONAL_SET_ENABLE)
176 		val |= fractional->enable;
177 	CCU_WRITE(sc, fractional->reg, val);
178 
179 
180 	return 0;
181 }
182 
183 u_int
sunxi_ccu_fractional_round_rate(struct sunxi_ccu_softc * sc,struct sunxi_ccu_clk * clk,u_int try_rate)184 sunxi_ccu_fractional_round_rate(struct sunxi_ccu_softc *sc,
185     struct sunxi_ccu_clk *clk, u_int try_rate)
186 {
187 	struct sunxi_ccu_fractional *fractional = &clk->u.fractional;
188 	struct clk *clkp, *clkp_parent;
189 	u_int parent_rate, best_rate;
190 	u_int m, rate;
191 	int best_diff;
192 	uint32_t val;
193 	int i;
194 
195 	clkp = &clk->base;
196 	clkp_parent = clk_get_parent(clkp);
197 	if (clkp_parent == NULL)
198 		return 0;
199 
200 	parent_rate = clk_get_rate(clkp_parent);
201 	if (parent_rate == 0)
202 		return 0;
203 
204 	val = CCU_READ(sc, fractional->reg);
205 
206 	if (fractional->prediv_val > 0) {
207 		parent_rate = parent_rate / fractional->prediv_val;
208 	} else if (fractional->prediv != 0) {
209 		val = CCU_READ(sc, fractional->reg);
210 		parent_rate = parent_rate / (__SHIFTOUT(val, fractional->prediv) + 1);
211 	}
212 
213 	for (i = 0; i < __arraycount(fractional->frac); i++) {
214 		if (fractional->frac[i] == try_rate) {
215 			return try_rate;
216 		}
217 	}
218 
219 	best_rate = 0;
220 	best_diff = INT_MAX;
221 
222 	for (m = fractional->m_min; m <= fractional->m_max; m++) {
223 		rate = parent_rate * m;
224 		const int diff = abs(try_rate - rate);
225 		if (diff < best_diff) {
226 			best_diff = diff;
227 			best_rate = rate;
228 			if (diff == 0)
229 				break;
230 		}
231 	}
232 
233 	return best_rate;
234 }
235 
236 const char *
sunxi_ccu_fractional_get_parent(struct sunxi_ccu_softc * sc,struct sunxi_ccu_clk * clk)237 sunxi_ccu_fractional_get_parent(struct sunxi_ccu_softc *sc,
238     struct sunxi_ccu_clk *clk)
239 {
240 	struct sunxi_ccu_fractional *fractional = &clk->u.fractional;
241 
242 	KASSERT(clk->type == SUNXI_CCU_FRACTIONAL);
243 
244 	return fractional->parent;
245 }
246