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