1 /*-
2 * Copyright (c) 2021 Adrian Chadd <[email protected]>.
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 ®);
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
qcom_clk_branch2_init(struct clknode * clk,device_t dev)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
qcom_clk_branch2_in_hwcg_mode_locked(struct qcom_clk_branch2_sc * sc)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 ®);
103
104 return (!! (reg & (1U << sc->hwcg_bit)));
105 }
106
107 static bool
qcom_clk_branch2_check_halt_locked(struct qcom_clk_branch2_sc * sc,bool enable)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, ®);
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
qcom_clk_branch2_halt_check_type(struct qcom_clk_branch2_sc * sc,uint32_t type,bool voted)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
qcom_clk_branch2_wait_locked(struct qcom_clk_branch2_sc * sc,bool enable)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
qcom_clk_branch2_set_gate(struct clknode * clk,bool enable)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 ®);
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
qcom_clk_branch2_set_freq(struct clknode * clk,uint64_t fin,uint64_t * fout,int flags,int * stop)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
qcom_clk_branch2_register(struct clkdom * clkdom,struct qcom_clk_branch2_def * clkdef)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