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 ®);
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 ®);
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, ®);
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 ®);
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 }
Cache object: b22bd2ae51fd2679dd26f82f1fe98ee0
|