1 /*-
2 * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23 * SUCH DAMAGE.
24 */
25
26 #include <sys/param.h>
27 #include <sys/systm.h>
28 #include <sys/bus.h>
29 #include <sys/lock.h>
30 #include <sys/mutex.h>
31 #include <sys/rman.h>
32 #include <machine/bus.h>
33
34 #include <dev/clk/clk.h>
35 #include <dev/clk/clk_div.h>
36 #include <dev/clk/clk_fixed.h>
37 #include <dev/clk/clk_mux.h>
38
39 #include "qcom_clk_freqtbl.h"
40 #include "qcom_clk_apssdiv.h"
41
42 #include "clkdev_if.h"
43
44 /*
45 * This is a combination gate, divisor/PLL configuration
46 * for the APSS CPU clock.
47 */
48
49 #if 0
50 #define DPRINTF(dev, msg...) device_printf(dev, "cpufreq_dt: " msg);
51 #else
52 #define DPRINTF(dev, msg...)
53 #endif
54
55 struct qcom_clk_apssdiv_sc {
56 struct clknode *clknode;
57 uint32_t div_offset;
58 uint32_t div_width;
59 uint32_t div_shift;
60 uint32_t enable_offset;
61 uint32_t enable_shift;
62 const struct qcom_clk_freq_tbl *freq_tbl;
63 };
64
65 static uint64_t
qcom_clk_apssdiv_calc_rate(struct clknode * clk,uint64_t freq,uint32_t cdiv)66 qcom_clk_apssdiv_calc_rate(struct clknode *clk, uint64_t freq, uint32_t cdiv)
67 {
68 uint32_t pre_div;
69
70 /*
71 * The divisor isn't a linear map with a linear pre-divisor.
72 */
73 if (cdiv > 10) {
74 pre_div = (cdiv + 1) * 2;
75 } else {
76 pre_div = cdiv + 12;
77 }
78 /*
79 * Multiplier is a fixed "2" here.
80 */
81 return (freq * 2L) / pre_div;
82 }
83
84 static int
qcom_clk_apssdiv_recalc(struct clknode * clk,uint64_t * freq)85 qcom_clk_apssdiv_recalc(struct clknode *clk, uint64_t *freq)
86 {
87 struct qcom_clk_apssdiv_sc *sc;
88 uint32_t reg, cdiv;
89
90 sc = clknode_get_softc(clk);
91
92 if (freq == NULL || *freq == 0) {
93 printf("%s: called; NULL or 0 frequency\n", __func__);
94 return (ENXIO);
95 }
96
97 CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
98 CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->div_offset, ®);
99 CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
100 cdiv = (reg >> sc->div_shift) & ((1U << sc->div_width) - 1);
101
102 DPRINTF(clknode_get_device(sc->clknode),
103 "%s: called; cdiv=0x%x, freq=%llu\n", __func__, cdiv, *freq);
104
105 *freq = qcom_clk_apssdiv_calc_rate(clk, *freq, cdiv);
106
107 DPRINTF(clknode_get_device(sc->clknode),
108 "%s: called; freq is %llu\n", __func__, *freq);
109 return (0);
110 }
111
112 #if 0
113 static bool
114 qcom_clk_apssdiv_get_gate_locked(struct qcom_clk_apssdiv_sc *sc)
115 {
116 uint32_t reg;
117
118 if (sc->enable_offset == 0)
119 return (false);
120
121 CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->enable_offset,
122 ®);
123
124 return (!! (reg & (1U << sc->enable_shift)));
125 }
126 #endif
127
128 static int
qcom_clk_apssdiv_init(struct clknode * clk,device_t dev)129 qcom_clk_apssdiv_init(struct clknode *clk, device_t dev)
130 {
131
132 /*
133 * There's only a single parent here for an fixed divisor,
134 * so just set it to 0; the caller doesn't need to supply it.
135 *
136 * Note that the freqtbl entries have an upstream clock,
137 * but the APSS div/gate only has a single upstream and we
138 * don't program anything else specific in here.
139 */
140 clknode_init_parent_idx(clk, 0);
141
142 return (0);
143 }
144
145 static int
qcom_clk_apssdiv_set_gate(struct clknode * clk,bool enable)146 qcom_clk_apssdiv_set_gate(struct clknode *clk, bool enable)
147 {
148 struct qcom_clk_apssdiv_sc *sc;
149 uint32_t reg;
150
151 sc = clknode_get_softc(clk);
152
153 if (sc->enable_offset == 0) {
154 return (ENXIO);
155 }
156
157 DPRINTF(clknode_get_device(sc->clknode),
158 "%s: called; enable=%d\n", __func__, enable);
159
160 CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
161 CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->enable_offset,
162 ®);
163 if (enable) {
164 reg |= (1U << sc->enable_shift);
165 } else {
166 reg &= ~(1U << sc->enable_shift);
167 }
168 CLKDEV_WRITE_4(clknode_get_device(sc->clknode), sc->enable_offset,
169 reg);
170 CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
171
172 return (0);
173 }
174
175 /*
176 * Set frequency
177 *
178 * fin - the parent frequency, if exists
179 * fout - starts as the requested frequency, ends with the configured
180 * or dry-run frequency
181 * Flags - CLK_SET_DRYRUN, CLK_SET_ROUND_UP, CLK_SET_ROUND_DOWN
182 * retval - 0, ERANGE
183 */
184 static int
qcom_clk_apssdiv_set_freq(struct clknode * clk,uint64_t fin,uint64_t * fout,int flags,int * stop)185 qcom_clk_apssdiv_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
186 int flags, int *stop)
187 {
188 const struct qcom_clk_freq_tbl *f;
189 struct qcom_clk_apssdiv_sc *sc;
190 uint64_t f_freq;
191 uint32_t reg;
192
193 sc = clknode_get_softc(clk);
194
195 /* There are no further PLLs to set in this chain */
196 *stop = 1;
197
198 /* Search the table for a suitable frequency */
199 f = qcom_clk_freq_tbl_lookup(sc->freq_tbl, *fout);
200 if (f == NULL) {
201 return (ERANGE);
202 }
203
204 /*
205 * Calculate what the resultant frequency would be based on the
206 * parent PLL.
207 */
208 f_freq = qcom_clk_apssdiv_calc_rate(clk, fin, f->pre_div);
209
210 DPRINTF(clknode_get_device(sc->clknode),
211 "%s: dryrun: %d, fin=%llu fout=%llu f_freq=%llu pre_div=%u"
212 " target_freq=%llu\n",
213 __func__,
214 !! (flags & CLK_SET_DRYRUN),
215 fin, *fout, f_freq, f->pre_div, f->freq);
216
217 if (flags & CLK_SET_DRYRUN) {
218 *fout = f_freq;
219 return (0);
220 }
221
222 /*
223 * Program in the new pre-divisor.
224 */
225 CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
226 CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->div_offset, ®);
227 reg &= ~(((1U << sc->div_width) - 1) << sc->div_shift);
228 reg |= (f->pre_div << sc->div_shift);
229 CLKDEV_WRITE_4(clknode_get_device(sc->clknode), sc->div_offset, reg);
230 CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
231
232 /*
233 * The linux driver notes there's no status/completion bit to poll.
234 * So sleep for a bit and hope that's enough time for it to
235 * settle.
236 */
237 DELAY(1);
238
239 *fout = f_freq;
240
241 return (0);
242 }
243
244 static clknode_method_t qcom_clk_apssdiv_methods[] = {
245 /* Device interface */
246 CLKNODEMETHOD(clknode_init, qcom_clk_apssdiv_init),
247 CLKNODEMETHOD(clknode_recalc_freq, qcom_clk_apssdiv_recalc),
248 CLKNODEMETHOD(clknode_set_gate, qcom_clk_apssdiv_set_gate),
249 CLKNODEMETHOD(clknode_set_freq, qcom_clk_apssdiv_set_freq),
250 CLKNODEMETHOD_END
251 };
252
253 DEFINE_CLASS_1(qcom_clk_apssdiv, qcom_clk_apssdiv_class,
254 qcom_clk_apssdiv_methods, sizeof(struct qcom_clk_apssdiv_sc),
255 clknode_class);
256
257 int
qcom_clk_apssdiv_register(struct clkdom * clkdom,struct qcom_clk_apssdiv_def * clkdef)258 qcom_clk_apssdiv_register(struct clkdom *clkdom,
259 struct qcom_clk_apssdiv_def *clkdef)
260 {
261 struct clknode *clk;
262 struct qcom_clk_apssdiv_sc *sc;
263
264 clk = clknode_create(clkdom, &qcom_clk_apssdiv_class, &clkdef->clkdef);
265 if (clk == NULL)
266 return (1);
267
268 sc = clknode_get_softc(clk);
269 sc->clknode = clk;
270
271 sc->div_offset = clkdef->div_offset;
272 sc->div_width = clkdef->div_width;
273 sc->div_shift = clkdef->div_shift;
274 sc->freq_tbl = clkdef->freq_tbl;
275 sc->enable_offset = clkdef->enable_offset;
276 sc->enable_shift = clkdef->enable_shift;
277
278 clknode_register(clkdom, clk);
279
280 return (0);
281 }
282