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