xref: /freebsd/sys/dev/qcom_clk/qcom_clk_branch2.c (revision 924226fba12cc9a228c73b956e1b7fa24c60b055)
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/cdefs.h>
27 __FBSDID("$FreeBSD$");
28 
29 #include <sys/param.h>
30 #include <sys/systm.h>
31 #include <sys/bus.h>
32 #include <sys/lock.h>
33 #include <sys/mutex.h>
34 #include <sys/rman.h>
35 #include <machine/bus.h>
36 
37 #include <dev/extres/clk/clk.h>
38 #include <dev/extres/clk/clk_div.h>
39 #include <dev/extres/clk/clk_fixed.h>
40 #include <dev/extres/clk/clk_mux.h>
41 
42 #include "qcom_clk_branch2.h"
43 #include "qcom_clk_branch2_reg.h"
44 
45 #include "clkdev_if.h"
46 
47 /*
48  * This is a combination gate/status and dynamic hardware clock gating with
49  * voting.
50  */
51 
52 #if 0
53 #define DPRINTF(dev, msg...) device_printf(dev, msg);
54 #else
55 #define DPRINTF(dev, msg...)
56 #endif
57 
58 struct qcom_clk_branch2_sc {
59 	struct clknode *clknode;
60 	uint32_t flags;
61 	uint32_t enable_offset;
62 	uint32_t enable_shift;
63 	uint32_t hwcg_reg;
64 	uint32_t hwcg_bit;
65 	uint32_t halt_reg;
66 	uint32_t halt_check_type;
67 	bool halt_check_voted;
68 };
69 #if 0
70 static bool
71 qcom_clk_branch2_get_gate_locked(struct qcom_clk_branch2_sc *sc)
72 {
73 	uint32_t reg;
74 
75 	CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->enable_offset,
76 	    &reg);
77 
78 	DPRINTF(clknode_get_device(sc->clknode),
79 	    "%s: offset=0x%x, reg=0x%x\n", __func__,
80 	    sc->enable_offset, reg);
81 
82 	return (!! (reg & (1U << sc->enable_shift)));
83 }
84 #endif
85 
86 static int
87 qcom_clk_branch2_init(struct clknode *clk, device_t dev)
88 {
89 
90 	clknode_init_parent_idx(clk, 0);
91 
92 	return (0);
93 }
94 
95 static bool
96 qcom_clk_branch2_in_hwcg_mode_locked(struct qcom_clk_branch2_sc *sc)
97 {
98 	uint32_t reg;
99 
100 	if (sc->hwcg_reg == 0)
101 		return (false);
102 
103 	CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->hwcg_reg,
104 	    &reg);
105 
106 	return (!! (reg & (1U << sc->hwcg_bit)));
107 }
108 
109 static bool
110 qcom_clk_branch2_check_halt_locked(struct qcom_clk_branch2_sc *sc, bool enable)
111 {
112 	uint32_t reg;
113 
114 	CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->halt_reg, &reg);
115 
116 	if (enable) {
117 		/*
118 		 * The upstream Linux code is .. unfortunate.
119 		 *
120 		 * Here it says "return true if BRANCH_CLK_OFF is not set,
121 		 * or if the status field = FSM_STATUS_ON AND
122 		 * the clk_off field is 0.
123 		 *
124 		 * Which .. is weird, because I can't currently see
125 		 * how we'd ever need to check FSM_STATUS_ON - the only
126 		 * valid check for the FSM status also requires clk_off=0.
127 		 */
128 		return !! ((reg & QCOM_CLK_BRANCH2_CLK_OFF) == 0);
129 	} else {
130 		return !! (reg & QCOM_CLK_BRANCH2_CLK_OFF);
131 	}
132 }
133 
134 /*
135  * Check if the given type/voted flag match what is configured.
136  */
137 static bool
138 qcom_clk_branch2_halt_check_type(struct qcom_clk_branch2_sc *sc,
139     uint32_t type, bool voted)
140 {
141 	return ((sc->halt_check_type == type) &&
142 	    (sc->halt_check_voted == voted));
143 }
144 
145 static bool
146 qcom_clk_branch2_wait_locked(struct qcom_clk_branch2_sc *sc, bool enable)
147 {
148 
149 	if (qcom_clk_branch2_halt_check_type(sc,
150 	    QCOM_CLK_BRANCH2_BRANCH_HALT_SKIP, false))
151 		return (true);
152 	if (qcom_clk_branch2_in_hwcg_mode_locked(sc))
153 		return (true);
154 
155 	if ((qcom_clk_branch2_halt_check_type(sc,
156 	      QCOM_CLK_BRANCH2_BRANCH_HALT_DELAY, false)) ||
157 	    (enable == false && sc->halt_check_voted)) {
158 		DELAY(10);
159 		return (true);
160 	}
161 
162 	if ((qcom_clk_branch2_halt_check_type(sc,
163 	      QCOM_CLK_BRANCH2_BRANCH_HALT_INVERTED, false)) ||
164 	    (qcom_clk_branch2_halt_check_type(sc,
165 	      QCOM_CLK_BRANCH2_BRANCH_HALT, false)) ||
166 	    (enable && sc->halt_check_voted)) {
167 		int count;
168 
169 		for (count = 0; count < 200; count++) {
170 			if (qcom_clk_branch2_check_halt_locked(sc, enable))
171 				return (true);
172 			DELAY(1);
173 		}
174 		DPRINTF(clknode_get_device(sc->clknode),
175 		    "%s: enable stuck (%d)!\n", __func__, enable);
176 		return (false);
177 	}
178 
179 	/* Default */
180 	return (true);
181 }
182 
183 static int
184 qcom_clk_branch2_set_gate(struct clknode *clk, bool enable)
185 {
186 	struct qcom_clk_branch2_sc *sc;
187 	uint32_t reg;
188 
189 	sc = clknode_get_softc(clk);
190 
191 	DPRINTF(clknode_get_device(sc->clknode), "%s: called\n", __func__);
192 
193 	if (sc->enable_offset == 0) {
194 		DPRINTF(clknode_get_device(sc->clknode),
195 		    "%s: no enable_offset", __func__);
196 		return (ENXIO);
197 	}
198 
199 	DPRINTF(clknode_get_device(sc->clknode),
200 	    "%s: called; enable=%d\n", __func__, enable);
201 
202 	CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
203 	CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->enable_offset,
204 	    &reg);
205 	if (enable) {
206 		reg |= (1U << sc->enable_shift);
207 	} else {
208 		reg &= ~(1U << sc->enable_shift);
209 	}
210 	CLKDEV_WRITE_4(clknode_get_device(sc->clknode), sc->enable_offset,
211 	    reg);
212 
213 	/*
214 	 * Now wait for the clock branch to update!
215 	 */
216 	if (! qcom_clk_branch2_wait_locked(sc, enable)) {
217 		CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
218 		DPRINTF(clknode_get_device(sc->clknode),
219 		    "%s: failed to wait!\n", __func__);
220 		return (ENXIO);
221 	}
222 
223 	CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
224 
225 	return (0);
226 }
227 
228 static int
229 qcom_clk_branch2_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
230     int flags, int *stop)
231 {
232 	struct qcom_clk_branch2_sc *sc;
233 
234 	sc = clknode_get_softc(clk);
235 
236 	/* We only support what our parent clock is currently set as */
237 	*fout = fin;
238 
239 	/* .. and stop here if we don't have SET_RATE_PARENT */
240 	if (sc->flags & QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT)
241 		*stop = 0;
242 	else
243 		*stop = 1;
244 	return (0);
245 }
246 
247 
248 static clknode_method_t qcom_clk_branch2_methods[] = {
249 	/* Device interface */
250 	CLKNODEMETHOD(clknode_init,		qcom_clk_branch2_init),
251 	CLKNODEMETHOD(clknode_set_gate,		qcom_clk_branch2_set_gate),
252 	CLKNODEMETHOD(clknode_set_freq,		qcom_clk_branch2_set_freq),
253 	CLKNODEMETHOD_END
254 };
255 
256 DEFINE_CLASS_1(qcom_clk_branch2, qcom_clk_branch2_class,
257     qcom_clk_branch2_methods, sizeof(struct qcom_clk_branch2_sc),
258     clknode_class);
259 
260 int
261 qcom_clk_branch2_register(struct clkdom *clkdom,
262     struct qcom_clk_branch2_def *clkdef)
263 {
264 	struct clknode *clk;
265 	struct qcom_clk_branch2_sc *sc;
266 
267 	if (clkdef->flags & QCOM_CLK_BRANCH2_FLAGS_CRITICAL)
268 		clkdef->clkdef.flags |= CLK_NODE_CANNOT_STOP;
269 
270 	clk = clknode_create(clkdom, &qcom_clk_branch2_class,
271 	    &clkdef->clkdef);
272 	if (clk == NULL)
273 		return (1);
274 
275 	sc = clknode_get_softc(clk);
276 	sc->clknode = clk;
277 
278 	sc->enable_offset = clkdef->enable_offset;
279 	sc->enable_shift = clkdef->enable_shift;
280 	sc->halt_reg = clkdef->halt_reg;
281 	sc->hwcg_reg = clkdef->hwcg_reg;
282 	sc->hwcg_bit = clkdef->hwcg_bit;
283 	sc->halt_check_type = clkdef->halt_check_type;
284 	sc->halt_check_voted = clkdef->halt_check_voted;
285 	sc->flags = clkdef->flags;
286 
287 	clknode_register(clkdom, clk);
288 
289 	return (0);
290 }
291